CryptoUtils.java
package org.flasby.crypto;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.DestroyFailedException;
import org.mindrot.jbcrypt.BCrypt;
public final class CryptoUtils {
public static final String DEFAULT_KEY_ALGORITHM = "PBKDF2WithHmacSHA512";
public static final String DEFAULT_CIPHER = "AES/CBC/PKCS5Padding";
public static final String ENCODING_CHARSET = "UTF-8";
private static final byte[] DEFAULT_SALT = {
(byte) 0xA9,
(byte) 0x9B,
(byte) 0xC8,
(byte) 0x32,
(byte) 0x56,
(byte) 0x35,
(byte) 0xE3,
(byte) 0x03
};
private static final int ITERATION_COUNT = 1024;
private static final int KEY_LENGTH = 128;
public static void main(final String[] args) {
final String x = hashPassword("123");
System.out.println("Check 1: " + checkPassword("123", x));
System.out.println("Check 2: " + checkPassword("12x", x));
}
public static class CipherPair {
public final Cipher enCipher;
public final Cipher deCipher;
public CipherPair(final Cipher enCipher, final Cipher deCipher) {
this.enCipher = enCipher;
this.deCipher = deCipher;
}
}
/**
* generates a new CipherPair which can be used to encrypt and decrypt stuff. Importantly, the IV
* is random so you can't decrypt anything using a new pair with the same passPhrase without first
* initialising the decrypt IV.
*
* @param passPhrase
* @return
* @throws SecurityException
*/
public static CipherPair generateCipherPair(char[] passPhrase) {
return generateCipherPair(passPhrase, null);
}
public static final CipherPair generateCipherPair(final char[] passPhrase, byte[] iv)
throws SecurityException {
return generateCipherPair(passPhrase, DEFAULT_SALT, DEFAULT_KEY_ALGORITHM, iv);
}
public static final CipherPair generateCipherPair(
final char[] passPhrase, final byte[] salt, final String keyAlgorithm, byte[] iv)
throws SecurityException {
KeySpec spec;
{
// Copy the salt and pass phrase
final byte[] mySalt = Arrays.copyOf(salt, salt.length);
final char[] myPassPhrase = Arrays.copyOf(passPhrase, passPhrase.length);
// Generate the KeySpec
spec = new PBEKeySpec(myPassPhrase, mySalt, ITERATION_COUNT, KEY_LENGTH);
// And now destroy my copy of the salt & passPhrase, it's up to the caller to manage their
// inputs
Arrays.fill(mySalt, Byte.MAX_VALUE);
Arrays.fill(myPassPhrase, 'X');
}
try {
final SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgorithm);
final SecretKey tmp = factory.generateSecret(spec);
final SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
final Cipher ecipher = Cipher.getInstance(CryptoUtils.DEFAULT_CIPHER);
if (iv == null || iv.length == 0) {
ecipher.init(Cipher.ENCRYPT_MODE, secret);
} else {
ecipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
}
iv = ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
final Cipher dcipher = generateDeCipher(passPhrase, salt, keyAlgorithm, iv);
return new CipherPair(ecipher, dcipher);
} catch (NoSuchAlgorithmException
| InvalidKeySpecException
| NoSuchPaddingException
| InvalidKeyException
| InvalidParameterSpecException
| InvalidAlgorithmParameterException e) {
throw new SecurityException(e);
}
}
public static Cipher generateDeCipher(
final char[] passPhrase, final byte[] salt, final String keyAlgorithm, final byte[] iv)
throws NoSuchAlgorithmException,
InvalidKeySpecException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException {
final char[] myPassPhrase = Arrays.copyOf(passPhrase, passPhrase.length);
final byte[] mySalt = Arrays.copyOf(salt, salt.length);
// Generate the KeySpec
KeySpec spec = new PBEKeySpec(myPassPhrase, mySalt, ITERATION_COUNT, KEY_LENGTH);
// And now destroy my copy of the salt & passPhrase, it's up to the caller to manage their
// inputs
Arrays.fill(mySalt, Byte.MAX_VALUE);
Arrays.fill(myPassPhrase, 'X');
final SecretKeyFactory factory = SecretKeyFactory.getInstance(keyAlgorithm);
final SecretKey tmp = factory.generateSecret(spec);
final SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
final Cipher dcipher = Cipher.getInstance(CryptoUtils.DEFAULT_CIPHER);
dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
return dcipher;
}
public static final String hashPassword(final String password) {
final String hashed = BCrypt.hashpw(password, BCrypt.gensalt(12));
return hashed;
}
public static boolean checkPassword(final String candidatePassword, final String hash) {
return (BCrypt.checkpw(candidatePassword, hash));
}
public final byte[] hashPassword(
final char[] password, final byte[] salt, final int iterations, final int keyLength) {
try {
final SecretKeyFactory skf = SecretKeyFactory.getInstance(DEFAULT_KEY_ALGORITHM);
final PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
final SecretKey key = skf.generateSecret(spec);
final byte[] res = key.getEncoded();
key.destroy();
spec.clearPassword();
return res;
} catch (DestroyFailedException | NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
}
public static String toBase64(byte[] toEncode) {
return Base64.getEncoder().encodeToString(toEncode);
}
public static byte[] fromBase64(String toDecode) {
return Base64.getDecoder().decode(toDecode);
}
}