package model; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import javax.crypto.BadPaddingException; import org.apache.log4j.Logger; public class Key3DBParser { private static final byte[] VERSION_STR = "Version".getBytes(); private static final byte[] GLOBAL_SALT_STR = "global-salt".getBytes(); private static final byte[] PASSWORD_CHECK_STR = "password-check" .getBytes(); private final static Logger logger = Logger.getLogger(Key3DBParser.class); public static String NEWLINE = System.getProperty("line.separator"); @SuppressWarnings("unused") // TODO use for decrypting signons.sqlite private static final byte[] DEFAULT_SIGNONS_PASSWORD = Key3DBParser .convertHextoByte("f8000000000000000000000000000001"); private static final int ENC_PASSWORD_CHECK_LENGTH = 16; private static final int GLOBAL_KEY_LENGTH = 20; private final byte[] key3Bytes; private byte[] globalSalt; private Integer globalSaltIndex; private byte[] encPasswordCheck; private byte[] entrySalt; public Key3DBParser(String key3Path) throws IOException { key3Bytes = FileIO.getBytesFromFile(new File(key3Path)); } public String parse() { StringBuilder b = new StringBuilder(); b.append("Version: " + parseVersion() + NEWLINE); b.append("Global salt: " + parseGlobalSalt() + NEWLINE); b.append("Entry salt: " + parseEntrySalt() + NEWLINE); b.append("Encrypted PasswordCheck: " + parseEncryptedPasswordCheck() + NEWLINE); return b.toString(); } public boolean isPassword(String masterPassword) { String result = performPasswordCheck(masterPassword); if (result == null) { return false; } return result.equals(new String(PASSWORD_CHECK_STR)); } private String performPasswordCheck(String masterPassword) { if (entrySalt != null && encPasswordCheck != null) { try { return decryptPasswordCheck(masterPassword.getBytes("utf-8"), entrySalt, globalSalt, encPasswordCheck); } catch (UnsupportedEncodingException e) { e.printStackTrace(); logger.error(e.getMessage()); } } return "entry-salt or password-check not found"; } private String parseVersion() { Integer index = indexOf(VERSION_STR, key3Bytes); if (index != null) { return String.valueOf(key3Bytes[index - 1]); } return "No Version found"; } /** * This is tested and works well. * * @param password * @param es * @param gs * @param text * @return result of decryption */ private static String decryptPasswordCheck(byte[] password, byte[] es, byte[] gs, byte[] text) { try { // HP = SHA1(global-salt||password) byte[] hp = SHA.sha1(appendArray(gs, password)); logger.debug("HP: " + convertByteToHex(hp)); byte[] pes = Arrays.copyOf(es, 20); logger.debug("PES: " + convertByteToHex(pes)); // CHP = SHA1(HP||ES) byte[] chp = SHA.sha1(appendArray(hp, es)); logger.debug("CHP: " + convertByteToHex(chp)); // k1 = CHMAC(PES||ES) byte[] k1 = SHA.sha1Hmac(appendArray(pes, es), chp); logger.debug("k1: " + convertByteToHex(k1)); // tk = CHMAC(PES) byte[] tk = SHA.sha1Hmac(pes, chp); logger.debug("tk: " + convertByteToHex(tk)); // k2 = CHMAC(tk||ES) byte[] k2 = SHA.sha1Hmac(appendArray(tk, es), chp); logger.debug("k2: " + convertByteToHex(k2)); // k = k1||k2 byte[] k = appendArray(k1, k2); byte[] desKey = Arrays.copyOf(k, 24); logger.debug("key: " + convertByteToHex(desKey)); byte[] desIV = Arrays.copyOfRange(k, k.length - 8, k.length); logger.debug("iv: " + convertByteToHex(desIV) + "\n"); return new TripleDES(desKey, desIV).decrypt(text); } catch (NoSuchAlgorithmException e) { logger.fatal(e.getMessage()); e.printStackTrace(); } catch (BadPaddingException e) { logger.debug(e.getMessage() + ". Probably wrong key."); } return null; } private String parseEncryptedPasswordCheck() { Integer index = indexOf(PASSWORD_CHECK_STR, key3Bytes); if (index != null) { int from = index - ENC_PASSWORD_CHECK_LENGTH; int to = index; encPasswordCheck = Arrays.copyOfRange(key3Bytes, from, to); return convertByteToHex(encPasswordCheck); } return null; } private String parseEntrySalt() { if (globalSaltIndex != null) { int saltLength = key3Bytes[globalSaltIndex + GLOBAL_SALT_STR.length + 1]; logger.debug("Salt length: " + saltLength); int from = globalSaltIndex + GLOBAL_SALT_STR.length + 3; int to = from + saltLength; entrySalt = Arrays.copyOfRange(key3Bytes, from, to); return convertByteToHex(entrySalt); } return null; } private String parseGlobalSalt() { globalSaltIndex = indexOf(GLOBAL_SALT_STR, key3Bytes); if (globalSaltIndex != null) { int from = globalSaltIndex - GLOBAL_KEY_LENGTH; int to = globalSaltIndex; globalSalt = Arrays.copyOfRange(key3Bytes, from, to); return convertByteToHex(globalSalt); } return "No global-salt found"; } private static Integer indexOf(byte[] subarray, byte[] array) { if (subarray.length > array.length) { return null; } for (int i = 0; i < array.length; i++) { // possible starting index of subarray? if (array[i] == subarray[0]) { boolean found = true; // test all other indices of subarray for (int j = 1; j < subarray.length; j++) { if ((i + j) >= array.length || array[i + j] != subarray[j]) { found = false; break; } } // all other values where equal, so return starting index if (found) { return i; } } } return null; } /** * Helping method to convert a byte array to a hex String * * @param array * @return */ private static String convertByteToHex(byte array[]) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < array.length; i++) { if ((array[i] & 0xff) < 0x10) { buffer.append("0"); } buffer.append(Integer.toString(array[i] & 0xff, 16) + " "); } return buffer.toString(); } private static byte[] appendArray(byte[] arr1, byte[] arr2) { byte[] ret = new byte[arr1.length + arr2.length]; for (int i = 0; i < arr1.length; i++) { ret[i] = arr1[i]; } for (int i = 0; i < arr2.length; i++) { ret[i + arr1.length] = arr2[i]; } return ret; } /** * Helping method to convert a hex String to a byte array * * @param hexString * @return */ private static byte[] convertHextoByte(String hexString) { char[] hex = hexString.toCharArray(); byte[] result = new byte[hex.length / 2]; for (int i = 0; i < result.length; i++) { result[i] = (byte) ((Character.digit(hex[i * 2], 16) << 4) + Character .digit(hex[i * 2 + 1], 16)); } return result; } @SuppressWarnings("unused") private static boolean testDecryption() { byte[] password = Key3DBParser.convertHextoByte("70617373776f7264"); byte[] entrySalt = Key3DBParser .convertHextoByte("1596bb8112652a43e7bdfb2fdc8799e5"); byte[] globalSalt = Key3DBParser .convertHextoByte("5aac8e0439e8d69ea0fe1bc013cd5af8"); byte[] data = Key3DBParser .convertHextoByte("c0846848fe6e3524fdd4a6e3e783cf38"); String result = decryptPasswordCheck(password, entrySalt, globalSalt, data); System.out.println("result string: " + result); return result.equals(new String(PASSWORD_CHECK_STR)); } }