/* * Copyright (c) 2006-2009 Massachusetts General Hospital * All rights reserved. This program and the accompanying materials * are made available under the terms of the i2b2 Software License v2.1 * which accompanies this distribution. * * Contributors: * * */ package edu.harvard.i2b2.im.util; import java.io.FileNotFoundException; import java.io.FileInputStream; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.Hashtable; import java.sql.*; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.Log; import edu.harvard.i2b2.common.exception.I2B2Exception; /** * A class that recursively scans a directory and its sub_dirs, The class * implements Runnable to allow to use this within a Thread in a larger program. */ public class HighEncryption { private static Log log = LogFactory.getLog(HighEncryption.class.getName()); private Hashtable ht = new Hashtable(); private RijndaelAlgorithm cipher; // Cipher for mrnrs // medical record number encryption parameters private String m_sBWH_IdentifyingFirstCharacter = "B"; private String m_sMGH_IdentifyingFirstCharacter = "M"; private String m_sEMPI_IdentifyingFirstCharacter = "Z"; private String m_sENCYPT_IdentifyingFirstCharacter = ")"; private String m_sTheFillCharacter = "X"; private String m_sEncryptionErrorValue = ""; // medical record number decryption parameters private int m_iBWH_de_standard_length = 8; private int m_iMGH_de_standard_length = 7; private int m_iEMPI_de_standard_length = 9; private String m_sTheDeFillCharacter = "0"; public HighEncryption(String key) throws Exception { cipher = new RijndaelAlgorithm(key, 128); // , "AES"); //long } /* public HighEncryption(String inFileName, String keys, Connection sConn) throws Exception { cipher = new RijndaelAlgorithm(keys, 128); // , "AES"); Statement stmt = sConn.createStatement(); ResultSet rs = stmt .executeQuery("SELECT description FROM key WHERE NAME='" + inFileName + "'"); while (rs.next()) { ht.put(inFileName, cipher.decrypt(rs.getString(1))); // ConvertBaseNToDecDecrypt(rs.getString(1)); } rs.close(); } */ public HighEncryption(String inFileName, Hashtable keys) throws Exception { String key = null; // makearray64(); // create the array regardless of how key will be // acquired AHA@20011016 if ((keys != null) && (keys.size() > 0)) key = keys.get(inFileName).toString(); if ((key == null) || (key.length() == 0)) { byte baBuffer[] = new byte[2056]; try { FileInputStream oFileIn = new FileInputStream(inFileName); // int iBytes = oFileIn.read(baBuffer,0,2056); String sString = new String(baBuffer); // new String(baBuffer,0,0,iBytes); key = sString; } catch (FileNotFoundException fnfe) { log.fatal("HighEncryption initialization file-not-found error"); throw new Exception("HighEncryption initialization error"); } catch (Exception e) { log.fatal("HighEncryption initialization error"); throw new Exception("HighEncryption initialization error"); } } try { key = key.trim(); cipher = new RijndaelAlgorithm(key, 128); // , "AES"); //long // version for dates } catch (Exception ex) { // Lib.TError("HighEncryption initialization error"); ex.printStackTrace(); throw new Exception("HighEncryption initialization error"); } } /** * Turns array of bytes into string * * @param buf * Array of bytes to convert to hex string * @return Generated hex string */ public static String asHex(byte buf[]) { StringBuffer strbuf = new StringBuffer(buf.length * 2); int i; for (i = 0; i < buf.length; i++) { if ((buf[i] & 0xff) < 0x10) strbuf.append("0"); strbuf.append(Long.toString(buf[i] & 0xff, 16)); } return strbuf.toString(); } /** * mrn_encrypt makes the encrypted medical record number. * * All empi numbers are 9 digits long, in the form 100xxxxxx. Therefore, no * padding needs to be added to the front of the number. The output empi * number is an octal of length 11, as set by the variable: * empi_stardard_length. * * All bwh numbers are 8 digits long, but sometimes when they start with * zero's they are shorter (as though they were true numbers). This routine * does not care, it always removes leading zero's. The output bwh LMRN is * an octal of length set by the variable m_iBWH_stardard_length. * * All mgh numbers are 7 digits long, but sometimes when they start with * zero's they are shorter (as though they were true numbers). This routine * does not care, it always removes leading zero's, however note that the * encrypt routine will add them back again as part of the standard * incryption method. The output mgh LMRN is an octal of length set by the * variable m_iMGH_stardard_length. * * @param theInput * String is the cleartext bwh LMRN number. * @param standardLength * Boolean if TRUE pads a return octal out with X's to make the * total length what m_iXXX_stardard_length is set to. * @param theSite * String is the name of the owner of the medical record number. * @returns the encrypted medical record number. * @exception the * program returns a String as defined by the variable * m_sEncryptionErrorValue. */ public String mrn_encrypt(String theInput, boolean standardLength, String theSite) { if ((theInput == null) || (theInput.length() == 0)) return m_sEncryptionErrorValue; StringBuffer theOutput = new StringBuffer(16); if (theSite.equalsIgnoreCase("BWH")) { theOutput.append(m_sBWH_IdentifyingFirstCharacter); } else if (theSite.equalsIgnoreCase("MGH")) { theOutput.append(m_sMGH_IdentifyingFirstCharacter); } else if (theSite.equalsIgnoreCase("EMP") || theSite.equalsIgnoreCase("EMPI")) { theOutput.append(m_sEMPI_IdentifyingFirstCharacter); } //else { // Lib.TError("A valid site was not passed to mrn_encrypt function"); // return m_sEncryptionErrorValue; // } String theLong = null; try { // Strip off extra 0 in front theLong = ConvertDecToBaseNEncrypt(Integer.toString(Integer .parseInt(theInput)), 32); // theLong = cipher.encrypt(theInput); } catch (Exception e) { // Lib.TError("Parsing error in mrn_encrypt, message was: "+e.getMessage()); return m_sEncryptionErrorValue; } theOutput.append(theLong); return theOutput.toString(); // RAJ:Following lines commented because exception is thrown // in length comparison // /* * if (!standardLength) return (theOutput.append(theLong).toString()); * try { int addXes = standard_length - theLong.length(); if (addXes<0) * {//Lib.TError( * "A number to encrypt exceeded the maximum allowable length, the number was: '" * +theLong+"'."); return m_sEncryptionErrorValue; } else { * theOutput.append(theLong); for (int i=0; i<addXes; i++) { * theOutput.append(m_sTheFillCharacter); } return theOutput.toString(); * } } catch (Exception e) { * //Lib.TError("Unhandled error in mrn_encrypt: "+e.getMessage()); * return m_sEncryptionErrorValue; } */ } public String bwh_encnum_encrypt(String theInput) { if ((theInput == null) || (theInput.length() == 0)) return m_sEncryptionErrorValue; try { // If AES just encrypt the whole input string, do not split of up // and attach the // remaining at the end. return m_sENCYPT_IdentifyingFirstCharacter + m_sBWH_IdentifyingFirstCharacter + ConvertDecToBaseNEncrypt(theInput, 32); } catch (Exception e) { log.fatal("Unhandled error in bwh_encnum_encrypt: " + e.getMessage()); return m_sEncryptionErrorValue; } } public String bwh_encnum_decrypt(String theInput) { if ((theInput == null) || (theInput.length() == 0)) return m_sEncryptionErrorValue; try { return "TSI-BWH-" + ConvertBaseNToDecDecrypt(theInput.substring(2), 32); } catch (Exception e) { log.fatal("Unhandled error in bwh_encnum_decrypt: " + e.getMessage()); return m_sEncryptionErrorValue; } } public String mgh_encnum_encrypt(String theInput) { if ((theInput == null) || (theInput.length() == 0)) return m_sEncryptionErrorValue; try { // If AES just encrypt the whole input string, do not split of up // and attach the // remaining at the end. return m_sENCYPT_IdentifyingFirstCharacter + m_sMGH_IdentifyingFirstCharacter + ConvertDecToBaseNEncrypt(theInput, 32); } catch (Exception e) { log.fatal("Unhandled error in mgh_encnum_encrypt: " + e.getMessage()); return m_sEncryptionErrorValue; } } public String mgh_encnum_decrypt(String theInput) { if ((theInput == null) || (theInput.length() == 0)) return m_sEncryptionErrorValue; try { return "TSI-MGH-" + ConvertBaseNToDecDecrypt(theInput.substring(2), 32); } catch (Exception e) { log.fatal("Unhandled error in mgh_encnum_decrypt: " + e.getMessage()); return m_sEncryptionErrorValue; } } public String generic_encnum_encrypt(String theInput) throws I2B2Exception { return generic_encrypt(theInput); } public String generic_encrypt(String theInput) throws I2B2Exception { if ((theInput == null) || (theInput.length() == 0)) { log.warn("Empty input to generic encryption: "); return m_sEncryptionErrorValue; } try { String encryptedAccession = ConvertDecToBaseNEncrypt(theInput, 32); return encryptedAccession; } catch (Exception e) { log.fatal("Unhandled error in generic_encrypt: " + e.getMessage()); throw new I2B2Exception(e.getMessage(), e); } // // return m_sEncryptionErrorValue; // } } public String generic_encnum_derypt(String theInput) throws I2B2Exception { return generic_decrypt(theInput); } public String generic_decrypt(String theInput) throws I2B2Exception { if ((theInput == null) || (theInput.length() == 0)) { log.warn("Empty input to generic decryption: "); return m_sEncryptionErrorValue; } String accession64 = theInput; try { accession64 = ConvertBaseNToDecDecrypt(accession64, 32); return accession64; } catch (Exception e) { log.fatal("Unhandled error in generic_decrypt: " + e.getMessage()); throw new I2B2Exception(e.getMessage(), e); // return m_sEncryptionErrorValue; } } public String mrn_decrypt(String theInput, boolean standardLength) { if ((theInput == null) || (theInput.length() == 0)) return m_sEncryptionErrorValue; int standard_length; // StringBuffer theOutput = new StringBuffer(16); if (theInput.startsWith(m_sMGH_IdentifyingFirstCharacter)) { standard_length = m_iMGH_de_standard_length; } else if (theInput.startsWith(m_sBWH_IdentifyingFirstCharacter)) { standard_length = m_iBWH_de_standard_length; } else if (theInput.startsWith(m_sEMPI_IdentifyingFirstCharacter)) { standard_length = m_iEMPI_de_standard_length; } else { // Lib.TError("A valid site was not passed to mrn_decrypt function"); return m_sEncryptionErrorValue; } String theLong = null; String sTempNumber = theInput.substring(1); sTempNumber = sTempNumber.replaceAll(m_sTheFillCharacter, ""); // Lib.StrFindAndReplace(m_sTheFillCharacter,"",sTempNumber); try { theLong = ConvertBaseNToDecDecrypt(sTempNumber, 32); theLong = leftPad(theLong, standard_length, m_sTheDeFillCharacter); } catch (Exception e) { log.fatal("Parsing error in mrn_decrypt, message was: " + e.getMessage()); return m_sEncryptionErrorValue; } return theLong; } public static String leftPad(String stringToPad, int size, String padder) { if (padder.length() == 0) { return stringToPad; } StringBuffer strb = new StringBuffer(size); StringCharacterIterator sci = new StringCharacterIterator(padder); while (strb.length() < (size - stringToPad.length())) { for (char ch = sci.first(); ch != CharacterIterator.DONE; ch = sci .next()) { if (strb.length() < size - stringToPad.length()) { strb.insert(strb.length(), String.valueOf(ch)); } } } return strb.append(stringToPad).toString(); } public String rightPad(String s, int length, char pad) { StringBuffer buffer = new StringBuffer(s); int curLen = s.length(); if (curLen < length) { for (int i = 0; i < length; i++) { buffer.append(pad); } } return buffer.toString(); } // * B220 // Samples: Y229033779X3XXXXXXXX, Y96289732X10003XXXXX public String encnum_decrypt(String sEncNumber) { if ((sEncNumber == null) || (sEncNumber.length() == 0)) { // log.fatal(("No IDX main number to decrypt"); return m_sEncryptionErrorValue; } try { // int theLength = sEncNumber.length(); int theGroupNumberStartsOn = sEncNumber.indexOf('X'); // get the encounter number part, start after the 'Y' String sIdxEncNumber = sEncNumber.substring(1, theGroupNumberStartsOn); // decrypt the encounter number String sDecryptIdxEncNumber = cipher.decrypt(sIdxEncNumber); // get the group number part, start after the 'X' String sIdxGroupNumber = sEncNumber.substring( theGroupNumberStartsOn + 1, sEncNumber.length()); // take off the trailing 'X's char sTheFillCharacter = m_sTheFillCharacter.charAt(0); int iLastX = sIdxGroupNumber.length(); for (int i = 0; i < sIdxGroupNumber.length(); i++) { if (sIdxGroupNumber.charAt(i) == sTheFillCharacter) { iLastX = i; break; } } sIdxGroupNumber = sIdxGroupNumber.substring(0, iLastX); // make the encounter number in the form: // ... IDX-<group number>-<encounter number> String sFinalNumber = "IDX-MGH-" + sIdxGroupNumber + "-" + sDecryptIdxEncNumber; return sFinalNumber; } catch (Exception e) { log.fatal("Error in idxmgh_encnum_decrypt: " + e.getMessage()); return m_sEncryptionErrorValue; } } // * 3231 public String encnum_encrypt(String sExtraNumber, String sMainNumber) { int standard_length = 20; StringBuffer theOutput = new StringBuffer(standard_length); if ((sMainNumber == null) || (sMainNumber.length() == 0)) { // log.fatal("No IDX main number to encrypt"); return m_sEncryptionErrorValue; } if ((sExtraNumber == null) || (sExtraNumber.length() == 0)) { log.error("No IDX extra number with main number " + sMainNumber); return m_sEncryptionErrorValue; } String sEncMainNumber = null; try { sEncMainNumber = cipher.encrypt(sMainNumber); } catch (Exception e) { log.fatal("Parsing error in idxmgh_encnum_encrypt, message was: " + e.getMessage()); return m_sEncryptionErrorValue; } String theLong = sEncMainNumber + "X" + sExtraNumber; try { int addXes = standard_length - 1 - theLong.length(); if (addXes < 0) { log .error("A number to encrypt exceeded the maximum allowable length, the number was: '" + theLong + "'."); return m_sEncryptionErrorValue; } else { theOutput.append("Z"); theOutput.append(theLong); for (int i = 0; i < addXes; i++) { theOutput.append(m_sTheFillCharacter); } return theOutput.toString(); } } catch (Exception e) { log.fatal("Unhandled error in mrn_encrypt: " + e.getMessage()); return m_sEncryptionErrorValue; } } /* * ConvertDecToBaseNEncrypt makes the encrypted string in base N * * The string is first encrypted into AES than converted to base N * * @param bVale String is the cleartext string of any length. * * @param byBase Int is the base to use, between 2 and 36 * * @returns the ciphertext string in base N */ public String ConvertDecToBaseNEncrypt(String bValue, int byBase) throws Exception { String sValue = ConvertDecToBaseNClear(cipher.bencrypt(bValue), byBase); if (!bValue.equals(ConvertBaseNToDecDecrypt(sValue, byBase))) throw new Exception("Encryption check failed for Base N: " + bValue); // String sValue = cipher.bencrypt(bValue).toString(); return sValue; } /* * ConvertDecToBaseNClear makes the string in base N * * The reason for calling Clear is because to does not call any encryption * routines. The input string is converted into multiple 8 byte arrays and * converted into int64 which is than converted into the selected base. The * routine will add '!' for padding if the returned string is to small so * that all the strings are the same length. * * @param bVale Byte[] is the byte array of any length. * * @param byBase Int is the base to use, between 2 and 36 * * @returns the base N string */ private String ConvertDecToBaseNClear(byte[] bValue, int byBase) throws Exception { double bValueLen = bValue.length; double divValue = bValueLen / 8; double celing = Math.ceil(divValue); int rounds = (int) celing; StringBuilder dValue = new StringBuilder(26); int count = 0; for (int i = 0; i < rounds; i++) { int endLen = 8; if ((count + 8) > bValue.length) endLen = bValue.length - count; byte[] b = new byte[8]; System.arraycopy(bValue, count, b, 0, endLen); // this is slow UInt64 myGuidInt = BitConverter(b); StringBuilder sValue = new StringBuilder(); sValue.append(ConvertDecToBaseNClear(myGuidInt, 32)); // Deal with short strings if (sValue.length() < 13) { int addXes = 13 - sValue.length(); for (int j = 0; j < addXes; j++) { sValue.append("!"); } } dValue.append(sValue); count = count + 8; } return dValue.toString(); } /* * ConvertDecToBaseNClear makes the int in base N * * The reason for calling Clear is because to does not call any encryption * routines. This is the actual routine that does the conversation into the * base N. The reason for using unsigned is because the conversation using * AES would have negative inte values which would get lost when converting * into the base representation. * * @param dValue uint64 is a valid unsigned integer * * @param byBase Int is the base to use, between 2 and 36 * * @returns the base N string */ private String ConvertDecToBaseNClear(UInt64 dValue, long byBase) throws Exception { String BaseNums = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // String sResult = ""; StringBuffer sResult = new StringBuffer(); UInt64 dRemainder; // sResult = ""; if ((byBase >= 2) && (byBase <= 36)) { while (dValue.compareTo(0) > 0) // x > 0 { dRemainder = dValue.divideAndRemainder(byBase); // sResult = BaseNums.substring(dRemainder.intValue(), // dRemainder.intValue()+1) + sResult; sResult.insert(0, BaseNums.substring(dRemainder.intValue(), dRemainder.intValue() + 1)); } return sResult.toString(); } else { throw new Exception("Base should be between 2 and 36."); } } /* * ConvertBaseNToDecClear makes the string from base N * * The reason for calling Clear is because to does not call any encryption * routines. This is the actual routine that does the conversation from the * base N. The reason for using unsigned is because the conversation using * AES would have negative inte values which would get lost when converting * into the base representation. The routine will remove any '!' used for * padding * * @param dValue string is a valid base N string of length of a valid int64 * base * * @param byBase Int is the base to use, between 2 and 36 * * @returns the string */ private UInt64 ConvertBaseNToDecClear(int byBase, String origValue) throws Exception { // Deal with short strings String dValue = origValue; while (dValue.endsWith("!")) dValue = dValue.substring(0, dValue.length() - 1); String BaseNums = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; UInt64 lReturn = new UInt64(0); int n; if ((byBase >= 2) && (byBase <= 36)) { n = 0; while (n != dValue.length()) { // lReturn = (long) // ((BaseNums.indexOf(dValue.substring(((dValue.length() - n) - // 1), dValue.length() - 1))) // * (Math.pow(byBase,n))) + lReturn; // Half working // lReturn.add( // new java.math.BigInteger(Long.toString((BaseNums.indexOf( // dValue.substring(((dValue.length() - n) - 1), dValue.length() // - n))) // * (long) (Math.pow(byBase,n))))); UInt64 a = new UInt64(Long.toString((BaseNums.indexOf(dValue .substring(((dValue.length() - n) - 1), dValue.length() - n))))); a.multiply((long) (Math.pow(byBase, n))); lReturn.add(a.bigIntValue()); n++; } return lReturn; } else { throw new Exception("Base should be between 2 and 36."); } } /* * ConvertBaseNToDecDecrypt makes the string from base N into the original * string * * The input string is split into multiple chuncks. Each chunck is split * into 2 sections. Both are converted back into the original string and * appeneded together The new string is decrypted and returned. * * @param dValue string is a valid base N string of any length * * @param byBase Int is the base to use, between 2 and 36 * * @returns the cleartext string in a string. */ public String ConvertBaseNToDecDecrypt(String dValue, int byBase) throws Exception { int rounds = dValue.length() / 13; StringBuffer sValue = new StringBuffer(); byte[] cipherB = new byte[16]; for (int i = 0; i < rounds; i = i + 2) { UInt64 myInt = ConvertBaseNToDecClear(32, dValue.substring(i * 13, (i + 1) * 13)); byte[] b = BitConverter(myInt); System.arraycopy(b, 0, cipherB, 0, 8); myInt = ConvertBaseNToDecClear(32, dValue.substring((i + 1) * 13, (i + 2) * 13)); b = BitConverter(myInt); System.arraycopy(b, 0, cipherB, 8, 8); // String a = new String( cipher.decrypt(cipherB)); // a = a.substring(0, a.indexOf(0)); byte[] ciph = cipher.decrypt(cipherB); int len = ciph[15]; if ((len < 0) || (len > 16)) throw new Exception("Invalid key"); sValue.append(new String(ciph, 0, len)); // .trim(); } return sValue.toString(); } // Converts a double into an array of bytes with length // eight. private byte[] BitConverter(UInt64 a) { UInt64 value = new UInt64(a.bigIntValue()); byte[] w = new byte[8]; w[0] = value.byteValue(); value.shiftRight(8); w[1] = (value.byteValue()); value = new UInt64(a.bigIntValue()); value.shiftRight(16); w[2] = (value.byteValue()); value = new UInt64(a.bigIntValue()); value.shiftRight(24); w[3] = (value.byteValue()); value = new UInt64(a.bigIntValue()); value.shiftRight(32); w[4] = (value.byteValue()); value = new UInt64(a.bigIntValue()); value.shiftRight(40); w[5] = (value.byteValue()); value = new UInt64(a.bigIntValue()); value.shiftRight(48); w[6] = (value.byteValue()); value = new UInt64(a.bigIntValue()); value.shiftRight(56); w[7] = (value.byteValue()); return w; } private UInt64 BitConverter(byte[] a) { UInt64 accum = new UInt64("0"); long fff = 0; for (int shiftBy = 0; shiftBy < 64; shiftBy += 8) { long b = a[shiftBy / 8] & 0xff; b = b << shiftBy; fff = fff | b; } if (fff < 0) { accum = new UInt64(Long.toString(Long.MAX_VALUE)); accum.add(new java.math.BigInteger(Long.toString(fff + 2))); accum.add(new java.math.BigInteger(Long.toString(Long.MAX_VALUE))); } else { accum = new UInt64(Long.toString(fff)); } return accum; } public static void main(String[] args) { } }