/******************************************************************************* * 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); } }