Skip to content

Instantly share code, notes, and snippets.

@garcia-jj
Last active August 29, 2015 13:58
Show Gist options
  • Save garcia-jj/10048294 to your computer and use it in GitHub Desktop.
Save garcia-jj/10048294 to your computer and use it in GitHub Desktop.
Strong encryption with PBE
package foo;
import static java.lang.System.arraycopy;
import static java.util.Arrays.copyOf;
import static java.util.Arrays.copyOfRange;
import static java.util.Arrays.fill;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.security.auth.DestroyFailedException;
public class PasswordBasedEncryption {
private static final String ALGORITHM = "PBEWITHSHA256AND256BITAES-CBC-BC";
private static final String CIPHER_TRANSFORMATION = "AES/CTR/PKCS7Padding";
private static final String BOUNCY_CASTLE_PROVIDER = "BC";
private static final int ITERATIONS = 2000, KEY_LENGTH = 4096, SALT_LENGTH = 100;
public static byte[] doEncryption(char[] passphrase, byte[] input) throws GeneralSecurityException {
byte[] salt = generateRandomSalt();
SecretKey key = generateSaltedSecretKey(passphrase, salt);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION, BOUNCY_CASTLE_PROVIDER);
cipher.init(Cipher.ENCRYPT_MODE, key, generateIV(cipher), new SecureRandom());
byte[] ciphertext = cipher.doFinal(input);
byte[] output = copyOf(salt, salt.length + ciphertext.length);
arraycopy(ciphertext, 0, output, salt.length, ciphertext.length);
destroyQuietly(key);
fillWithZeroes(ciphertext);
fillWithZeroes(salt);
return output;
}
public static byte[] doDecryption(char[] passphrase, byte[] input) throws GeneralSecurityException {
byte[] retrivedSalt = copyOfRange(input, 0, SALT_LENGTH);
byte[] unsaltedInput = copyOfRange(input, SALT_LENGTH, input.length);
SecretKey key = generateSaltedSecretKey(passphrase, retrivedSalt);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION, BOUNCY_CASTLE_PROVIDER);
cipher.init(Cipher.DECRYPT_MODE, key, generateIV(cipher), new SecureRandom());
byte[] out = cipher.doFinal(unsaltedInput);
destroyQuietly(key);
fillWithZeroes(retrivedSalt);
return out;
}
private static SecretKey generateSaltedSecretKey(char[] passphrase, byte[] salt) {
KeySpec keySpec = new PBEKeySpec(passphrase, salt, ITERATIONS, KEY_LENGTH);
try {
return SecretKeyFactory.getInstance(ALGORITHM).generateSecret(keySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new UnsupportedOperationException(e);
}
}
private static IvParameterSpec generateIV(Cipher cipher) {
byte[] ivData = new byte[cipher.getBlockSize()];
fillWithRandomData(ivData);
return new IvParameterSpec(ivData);
}
public static byte[] generateRandomSalt() {
byte[] bytes = new byte[SALT_LENGTH];
fillWithRandomData(bytes);
return bytes;
}
private static void fillWithRandomData(byte[] data) {
new SecureRandom().nextBytes(data);
}
private static void destroyQuietly(SecretKey key) {
try {
if (!key.isDestroyed()) {
key.destroy();
}
} catch (DestroyFailedException ignored) {
}
}
private static void fillWithZeroes(byte[] arr) {
fill(arr, (byte) 0x0);
}
}
package foo;
import static foo.PasswordBasedEncryption.doDecryption;
import static foo.PasswordBasedEncryption.doEncryption;
import static java.util.UUID.randomUUID;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import org.junit.Test;
public class PasswordBasedEncryptionTest {
private static final int NUM_OF_TRIES = 100;
private final char[] passphrase = "The quick brown fox jumps over the lazy dog".toCharArray();
@Test
public void inputStringShouldNotTheSameOutput() throws Exception {
for (int i = 0; i < NUM_OF_TRIES; i++) {
String plaintext = randomUUID().toString();
byte[] ciphertext = doEncryption(passphrase, plaintext.getBytes());
assertThat(plaintext, not(new String(ciphertext)));
}
}
@Test
public void shouldRecoveryOriginalText() throws Exception {
for (int i = 0; i < NUM_OF_TRIES; i++) {
String plaintext = randomUUID().toString();
byte[] ciphertext = doEncryption(passphrase, plaintext.getBytes());
byte[] recoveredtext = doDecryption(passphrase, ciphertext);
assertThat(plaintext, is(new String(recoveredtext)));
}
}
}
package security;
import static foo.PasswordBasedEncryption.doDecryption;
import static foo.PasswordBasedEncryption.doEncryption;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.security.GeneralSecurityException;
import java.util.Base64;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import javax.persistence.PersistenceException;
@Converter
public class StringEncrypted implements AttributeConverter<String, String> {
private final char[] passphrase = "The quick brown fox jumps over the lazy dog".toCharArray(); // only for test purposes
@Override
public String convertToDatabaseColumn(String attribute) {
if (isNullOrEmpty(attribute)) {
return attribute;
}
try {
byte[] data = doEncryption(passphrase, attribute.getBytes(UTF_8));
return Base64.getEncoder().encodeToString(data);
} catch (UnsupportedOperationException | GeneralSecurityException e) {
throw new PersistenceException(e);
}
}
@Override
public String convertToEntityAttribute(String dbData) {
if (isNullOrEmpty(dbData)) {
return dbData;
}
try {
byte[] data = doDecryption(passphrase, dbData.getBytes(UTF_8));
return new String(data, UTF_8);
} catch (UnsupportedOperationException | GeneralSecurityException e) {
throw new PersistenceException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment