package io.budgetapp.crypto; /** * */ public class PasswordEncoder { private static final int DEFAULT_ITERATIONS = 1024; private static final String DEFAULT_ALGORITHM = "SHA-256"; private final SaltGenerator saltGenerator; private final Digester digester; private final byte[] secret; public PasswordEncoder() { this(""); } public PasswordEncoder(String secret) { this.digester = new Digester(DEFAULT_ALGORITHM, DEFAULT_ITERATIONS); this.secret = Utf8.encode(secret); this.saltGenerator = new SaltGenerator(); } public String encode(CharSequence rawPassword) { return encode(rawPassword, saltGenerator.generateKey()); } public boolean matches(CharSequence rawPassword, String encodedPassword) { byte[] digested = decode(encodedPassword); byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength()); return matches(digested, digest(rawPassword, salt)); } /** * Constant time comparison to prevent against timing attacks. */ private boolean matches(byte[] expected, byte[] actual) { if (expected.length != actual.length) { return false; } int result = 0; for (int i = 0; i < expected.length; i++) { result |= expected[i] ^ actual[i]; } return result == 0; } private String encode(CharSequence rawPassword, byte[] salt) { byte[] digest = digest(rawPassword, salt); return new String(Hex.encode(digest)); } private byte[] decode(CharSequence encodedPassword) { return Hex.decode(encodedPassword); } private byte[] digest(CharSequence rawPassword, byte[] salt) { byte[] digest = digester.digest(concatenate(salt, secret, Utf8.encode(rawPassword))); return concatenate(salt, digest); } /** * Combine the individual byte arrays into one array. */ public static byte[] concatenate(byte[]... arrays) { int length = 0; for (byte[] array : arrays) { length += array.length; } byte[] newArray = new byte[length]; int destPos = 0; for (byte[] array : arrays) { System.arraycopy(array, 0, newArray, destPos, array.length); destPos += array.length; } return newArray; } /** * Extract a sub array of bytes out of the byte array. * @param array the byte array to extract from * @param beginIndex the beginning index of the sub array, inclusive * @param endIndex the ending index of the sub array, exclusive */ public static byte[] subArray(byte[] array, int beginIndex, int endIndex) { int length = endIndex - beginIndex; byte[] subarray = new byte[length]; System.arraycopy(array, beginIndex, subarray, 0, length); return subarray; } }