/*******************************************************************************
* Copyright (c) 2013 Lectorius, Inc.
* Authors:
* Vijay Pandurangan (vijayp@mitro.co)
* Evan Jones (ej@mitro.co)
* Adam Hilss (ahilss@mitro.co)
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can contact the authors at inbound@mitro.co.
*******************************************************************************/
package co.mitro.keyczar;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import java.io.UnsupportedEncodingException;
import javax.crypto.spec.PBEKeySpec;
import org.junit.Test;
import org.keyczar.DefaultKeyType;
import org.keyczar.GenericKeyczar;
import org.keyczar.KeyczarKey;
import org.keyczar.enums.KeyPurpose;
import org.keyczar.exceptions.KeyczarException;
import org.keyczar.interfaces.KeyczarReader;
import org.keyczar.util.Base64Coder;
import co.mitro.keyczar.KeyczarPBEReader.PBEKeyczarKey;
public class KeyczarPBEReaderTest {
// The example output from C++ implementation on the web site is wrong
// This was generated with a local copy of OpenSSL
private static final String JSON_KEY = "{\n" +
"\"cipher\": \"AES128\"," +
"\"hmac\": \"HMAC_SHA1\"," +
"\"iterationCount\": 4096," +
"\"iv\": \"z3BdMSyqfrh-qmv1YLXvFg\"," +
"\"key\": \"ZoavQvg_IRDGG57NQIn4kjuBRyRsX3p6JPWX1jnNUBtUQyAHTd381CzKyqOZIIVt8nIkkzdN3JjtTyYMQSEE9ZTVZ_RVC1enTzLZuEL5gZCbmJyRzX1eBdpTN1bFbIt3aOMiFxjFzP-O67ErGnUHpBHmmCxmau-MBUpCd6Su-eum3SIERsaMzsMDEcivCrd5SW20HokMWCxu_GImVxyQuA\"," +
"\"salt\": \"EwDMYR65XcUmvggvoW1GMw\"" +
"}";
private static final String PASSPHRASE = "pass";
@Test
public void testKeySpecFromJson() {
// read the metadata, generate the key
KeyczarPBEReader.PBEKeyczarKey pbeKey = KeyczarPBEReader.parsePBEMetadata(JSON_KEY);
PBEKeySpec spec = KeyczarPBEReader.keySpecFromJson(pbeKey, PASSPHRASE);
assertEquals(16*8, spec.getKeyLength());
assertEquals(4096, spec.getIterationCount());
final byte[] salt = {19, 0, -52, 97, 30, -71, 93, -59, 38, -66, 8, 47, -95, 109, 70, 51};
assertArrayEquals(salt, spec.getSalt());
}
private static byte[] convertHex(String hex) {
String[] parts = hex.split(" ");
byte[] out = new byte[parts.length];
for (int i = 0; i < parts.length; ++i) {
out[i] = (byte) Integer.parseInt(parts[i], 16);
}
return out;
}
@Test
public void testPBKDF2() throws UnsupportedEncodingException {
// Test vectors from http://tools.ietf.org/html/rfc6070
assertKey(1, "0c 60 c8 0f 96 1f 0e 71 f3 a9 b5 24 af 60 12 06 2f e0 37 a6");
assertKey(2, "ea 6c 01 4d c7 2d 6f 8c cd 1e d9 2a ce 1d 41 f0 d8 de 89 57");
assertKey(4096, "4b 00 79 01 b7 65 48 9a be ad 49 d9 26 f7 21 d0 65 a4 29 c1");
// This runs too slow; I wasn't patient enough to verify this :)
// assertKey(16777216, "ee fe 3d 61 cd 4d a4 e4 e9 94 5b 3d 6b a2 15 8c 26 34 e9 84");
String password = "passwordPASSWORDpassword";
String salt = "saltSALTsaltSALTsaltSALTsaltSALTsalt";
assertKey(4096, password, salt, "3d 2e ec 4f e4 1c 84 9b 80 c8 d8 36 62 c0 e4 4a 8b 29 1a 96 4c f2 f0 70 38");
password = "pass\0word";
salt = "sa\0lt";
assertKey(4096, password, salt, "56 fa 6a a7 55 48 09 9d cc 37 d7 f0 34 25 e0 c3");
}
private void assertKey(int iterations, String expected) throws UnsupportedEncodingException {
assertKey(iterations, "password", "salt", expected);
}
private void assertKey(int iterations, String password, String salt, String expected) throws UnsupportedEncodingException {
byte[] expectedBytes = convertHex(expected);
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes("UTF-8"), iterations, expectedBytes.length*8);
byte[] key = KeyczarPBEReader.pbkdf2(spec);
assertArrayEquals(expectedBytes, key);
}
// As generated by the C++ implementation
private final static String METADATA = "{\n" +
" \"encrypted\": true,\n" +
" \"name\": \"Test\",\n" +
" \"purpose\": \"DECRYPT_AND_ENCRYPT\",\n" +
" \"type\": \"AES\",\n" +
" \"versions\": [ {\n" +
" \"exportable\": false,\n" +
" \"status\": \"PRIMARY\",\n" +
" \"versionNumber\": 1\n" +
" } ]\n" +
"}\n";
@Test
public void testReadEncryptedKey() throws KeyczarException {
KeyczarReader staticReader = new KeyczarReader() {
@Override
public String getKey() throws KeyczarException {
return JSON_KEY;
}
@Override
public String getKey(int version) throws KeyczarException {
assert version == 1;
return JSON_KEY;
}
@Override
public String getMetadata() throws KeyczarException {
return METADATA;
}
};
KeyczarPBEReader encrypted = new KeyczarPBEReader(staticReader, PASSPHRASE);
String prefix = "{\"aesKeyString\":\"oThFEDqkkLyp80hhh1QFjA\"";
assertEquals(prefix, encrypted.getKey().substring(0, prefix.length()));
GenericKeyczar keyczar = new GenericKeyczar(encrypted);
assertEquals(1, keyczar.getVersions().size());
}
@Test
public void testEncryptKey() throws KeyczarException {
GenericKeyczar key = Util.createKey(DefaultKeyType.AES, KeyPurpose.DECRYPT_AND_ENCRYPT);
KeyczarKey k = key.getKey(key.getMetadata().getPrimaryVersion());
String unencryptedKey = k.toString();
String encrypted = KeyczarPBEReader.encryptKey(unencryptedKey, PASSPHRASE);
PBEKeyczarKey pbeKey = KeyczarPBEReader.parsePBEMetadata(encrypted);
assertEquals(KeyczarPBEReader.DEFAULT_ITERATION_COUNT, pbeKey.iterationCount);
assertEquals(KeyczarPBEReader.SALT_BYTES, Base64Coder.decodeWebSafe(pbeKey.salt).length);
assertEquals(KeyczarPBEReader.PBE_AES_KEY_BYTES, Base64Coder.decodeWebSafe(pbeKey.iv).length);
// decrypt the key
KeyczarPBEReader reader = new KeyczarPBEReader(null, PASSPHRASE);
String out = reader.decryptKey(encrypted);
assertEquals(unencryptedKey, out);
}
}