package ch.ge.ve.commons.crypto;
/*-
* #%L
* Common crypto utilities
* %%
* Copyright (C) 2015 - 2016 République et Canton de Genève
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import ch.ge.ve.commons.crypto.utils.SecureRandomFactory;
import com.google.common.base.Joiner;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.crypto.SecretKey;
import java.io.*;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;
import java.util.Random;
import static ch.ge.ve.commons.crypto.SensitiveDataCryptoUtils.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertTrue;
/**
* This test suit aims at covering the {@link SensitiveDataCryptoUtils} utility class.
*/
public class SensitiveDataCryptoUtilsTest {
private static final int SECRETKEY_LENGTH = 256;
private static final TestSensitiveDataCryptoUtilsConfiguration configuration = new TestSensitiveDataCryptoUtilsConfiguration();
@BeforeClass
public static void init() {
Security.addProvider(new BouncyCastleProvider());
SensitiveDataCryptoUtils.configure(configuration);
}
/**
* saving a key and retrieving it from a file should give the same object
*/
@Test
public void saveAndRetrieveSecretKey() throws IOException, ClassNotFoundException {
SecretKey sK = SensitiveDataCryptoUtils.buildSecretKey(SECRETKEY_LENGTH, configuration.getPbkdf2Algorithm());
String secretKeyFingerPrint = Base64.getEncoder().encodeToString(sK.getEncoded());
File keyFile = File.createTempFile("keyfile", "key");
keyFile.deleteOnExit();
saveInFile(keyFile, sK);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(keyFile));
SecretKey readKey = (SecretKey) ois.readObject();
ois.close();
assertTrue(readKey.getEncoded().length * 8 == SECRETKEY_LENGTH);
assertTrue(Base64.getEncoder().encodeToString(readKey.getEncoded()).equals(secretKeyFingerPrint));
}
private void saveInFile(File keyFile, Object sK) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(keyFile));
oos.writeObject(sK);
oos.flush();
oos.close();
}
/**
* buildMAC() should generate a correct MAC
*/
@Test
public void testMac() throws InvalidKeyException, NoSuchAlgorithmException, IOException, ClassNotFoundException {
String inputData = "This is the text we want to get the MAC for";
String hmac = Base64.getEncoder().encodeToString(buildMAC(inputData));
assertTrue(hmac.equals("kJ5sdJg8C490B16kmZhE+druaVTWTUXvEKwL643w6dI="));
}
/**
* verifyMAC() should validate a message with its correct MAC
*/
@Test
public void testMessageAuthenticated() {
String message = "This is the text we want to get the MAC for";
final String mac = buildMACAsBase64String(message);
assertTrue("message should be authenticated", verifyMAC(message, mac));
}
/**
* verifySaltedMAC() should validate a message with its correct salted MAC
*/
@Test
public void testMessageAuthenticatedWithSalt() {
String message = "This is the text we want to get the MAC for";
final String mac = buildSaltedMACAsBase64String(message);
assertTrue("message should be authenticated", verifySaltedMAC(message, mac));
}
/**
* encryption followed by a decryption should return the same object
*/
@Test
public void encrypt_then_decrypt_byte_array_should_be_identity() {
byte[] input = "The lazy fox jumps over the ".getBytes();
byte[] cipherText = encrypt(input);
assertThat(decrypt(cipherText), is(input));
}
/**
* encryption followed by a decryption of a large object should return the same object
*/
@Test
public void encrypt_then_decrypt_large_byte_array_should_be_identity() {
byte[] input = new byte[4080];
Random random = SecureRandomFactory.createPRNG();
random.nextBytes(input);
byte[] cipherText = encrypt(input);
assertThat(decrypt(cipherText), is(input));
}
/**
* encryption followed by a decryption of a long object should return the same object
*/
@Test
public void encrypt_then_decrypt_long_should_be_identity() {
long input = 123456789L;
String cipherText = encryptAsBase64String(input);
assertThat(decryptAsLong(cipherText), is(input));
}
/**
* encryption followed by a decryption of a string object should return the same object
*/
@Test
public void encrypt_then_decrypt_string_should_be_identity() {
String input = "This is a very secret plaintext";
String cipherText = encryptAsBase64String(input);
assertThat(decryptAsString(cipherText), is(input));
}
/**
* HMAC of a long representation and a sting representation of the same number should be the same
*/
@Test
public void hmacOfCardNumberShouldBeStable() {
String cardNumberString = "1234567890123456";
String hmac = buildMACAsBase64String(cardNumberString);
long cardNumber = Long.valueOf(cardNumberString);
assertThat(buildMACAsBase64String(String.valueOf(cardNumber)), is(hmac));
}
/**
* encryption of the date of birth should be randomized
*/
@Test
public void encryptionOfDateOfBirthShouldBeRandomized() {
Long dob = 19850101L;
String encryptedDOB = encryptAsBase64String(dob);
assertThat(encryptAsBase64String(dob), not(encryptedDOB));
}
/**
* encryption followed by decryption of the date of birth should return the same object
*/
@Test
public void encryptThenDecryptDateOfBirthShouldBeStable() {
Long dob = 19850101L;
String encryptedDOB = encryptAsBase64String(dob);
assertThat(decryptAsLong(encryptedDOB), is(dob));
}
/**
* validation of a secure password should work
*/
@Test
public void securePasswordHashShouldValidate() {
char[] passwd = "A random te$ting p#s$w0rd!!!".toCharArray();
String passwordHash = generateStrongPasswordHash(passwd);
assertThat("The validation of the password should work", validateStrongPasswordHash(passwd, passwordHash));
}
/**
* validation of a password hash with the wrong password should fail
*/
@Test
public void securePasswordHashShouldNotValidateWrongPassword() {
char[] passwd = "A random te$ting p#s$w0rd!!!".toCharArray();
String passwordHash = generateStrongPasswordHash(passwd);
char[] wrongPasswd = "Not this one!".toCharArray();
assertThat("A wrong password should not be validated", !validateStrongPasswordHash(wrongPasswd, passwordHash));
}
/**
* alidation of the password should fail when iteration count has been altered
*/
@Test
public void securePasswordHashWithWrongIterationsCountShouldNotValidate() {
char[] passwd = "The lazy fox jumps over...".toCharArray();
String passwordHash = generateStrongPasswordHash(passwd);
String[] parts = passwordHash.split(":");
int iterations = Integer.parseInt(parts[0]);
String manipulatedIterationsHash = Joiner.on(":").join(iterations + 1, parts[1], parts[2]);
assertThat("The validation of the password should fail when iteration count has been altered", !validateStrongPasswordHash(passwd, manipulatedIterationsHash));
}
/**
* validation of the password should fail when the salt has been altered
*/
@Test
public void securePasswordHashWithWrongSaltShouldNotValidate() {
char[] passwd = "A random te$ting p#s$w0rd!!!".toCharArray();
String passwordHash = generateStrongPasswordHash(passwd);
String[] parts = passwordHash.split(":");
String salt = new BigInteger(parts[1].length() / 2, new SecureRandom()).toString(16);
if (salt.length() < parts[1].length()) {
salt = String.format("%0" + (parts[1].length() - salt.length()) + "d%s", 0, salt);
}
String manipulatedSaltHash = Joiner.on(":").join(parts[0], salt, parts[2]);
assertThat("The validation of the password should fail when the salt has been altered", !validateStrongPasswordHash(passwd, manipulatedSaltHash));
}
}