1/*
2 * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.security.InvalidKeyException;
25import java.security.NoSuchAlgorithmException;
26import java.security.spec.InvalidKeySpecException;
27import java.util.Arrays;
28import java.util.Random;
29import javax.crypto.SecretKey;
30import javax.crypto.SecretKeyFactory;
31import javax.crypto.interfaces.PBEKey;
32import javax.crypto.spec.PBEKeySpec;
33import javax.security.auth.DestroyFailedException;
34
35import static java.lang.System.out;
36
37/*
38 * @test
39 * @bug 8048820
40 * @summary The test verifies if the SecretKeyFactory.translateKey() method
41 *  works as expected for the PBKDF2 algorithms.
42 */
43
44public class PBKDF2TranslateTest {
45
46    private static final String PASS_PHRASE = "some hidden string";
47    private static final int ITERATION_COUNT = 1000;
48    private static final int KEY_SIZE = 128;
49    private static final String[] TEST_ALGOS = { "PBKDF2WithHmacSHA1",
50            "PBKDF2WithHmacSHA224", "PBKDF2WithHmacSHA256",
51            "PBKDF2WithHmacSHA384", "PBKDF2WithHmacSHA512" };
52    private final String algoForTest;
53
54    public static void main(String[] args) throws Exception {
55        for (String algo : TEST_ALGOS) {
56            PBKDF2TranslateTest theTest = new PBKDF2TranslateTest(algo);
57            byte[] salt = new byte[8];
58            new Random().nextBytes(salt);
59            theTest.testMyOwnSecretKey(salt);
60            theTest.generateAndTranslateKey(salt);
61            theTest.translateSpoiledKey(salt);
62        }
63    }
64
65    public PBKDF2TranslateTest(String algo) {
66        algoForTest = algo;
67    }
68
69    /**
70     * The test case scenario implemented in the method: - derive PBKDF2 key
71     * using the given algorithm; - translate the key - check if the translated
72     * and original keys have the same key value.
73     *
74     */
75    public void generateAndTranslateKey(byte[] salt)
76            throws NoSuchAlgorithmException, InvalidKeySpecException,
77            InvalidKeyException {
78        // derive PBKDF2 key
79        SecretKey key1 = getSecretKeyForPBKDF2(algoForTest, salt);
80
81        // translate key
82        SecretKeyFactory skf = SecretKeyFactory.getInstance(algoForTest);
83        SecretKey key2 = skf.translateKey(key1);
84
85        // Check if it still the same after translation
86        if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
87            System.out.println("Key1=" + new String(key1.getEncoded())
88                    + " key2=" + new String(key2.getEncoded()) + " salt="
89                    + new String(salt));
90            throw new RuntimeException(
91                    "generateAndTranslateKey test case failed: the  key1 and"
92                            + " key2 values in its primary encoding format are"
93                            + " not the same for " + algoForTest
94                            + " algorithm.");
95        }
96    }
97
98    /**
99     * The test case scenario implemented in the method: - derive Key1 for the
100     * given PBKDF2 algorithm - create my own secret Key2 as an instance of a
101     * class implements PBEKey - translate Key2 - check if the key value of the
102     * translated key and Key1 are the same.
103     */
104    private void testMyOwnSecretKey(byte[] salt)
105            throws NoSuchAlgorithmException, InvalidKeySpecException,
106            InvalidKeyException {
107        SecretKey key1 = getSecretKeyForPBKDF2(algoForTest, salt);
108        SecretKey key2 = getMyOwnSecretKey(salt);
109
110        // Is it actually the same?
111        if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
112            throw new RuntimeException(
113                    "We shouldn't be here. The key1 and key2 values in its"
114                            + " primary encoding format have to be the same!");
115        }
116
117        // translate key
118        SecretKeyFactory skf = SecretKeyFactory.getInstance(algoForTest);
119        SecretKey key3 = skf.translateKey(key2);
120
121        // Check if it still the same after translation
122        if (!Arrays.equals(key1.getEncoded(), key3.getEncoded())) {
123            System.out.println("Key1=" + new String(key1.getEncoded())
124                    + " key3=" + new String(key3.getEncoded()) + " salt="
125                    + new String(salt));
126            throw new RuntimeException(
127                    "testMyOwnSecretKey test case failed: the key1  and key3"
128                            + " values in its primary encoding format are not"
129                            + " the same for " + algoForTest + " algorithm.");
130        }
131
132    }
133
134    /**
135     * The test case scenario implemented in the method: - create my own secret
136     * Key2 as an instance of a class implements PBEKey - spoil the key (set
137     * iteration count to 0, for example) - try to translate key -
138     * InvalidKeyException is expected.
139     */
140    public void translateSpoiledKey(byte[] salt)
141            throws NoSuchAlgorithmException, InvalidKeySpecException {
142        // derive the key
143        SecretKey key1 = getMyOwnSecretKey(salt);
144
145        // spoil the key
146        ((MyPBKDF2SecretKey) key1).spoil();
147
148        // translate key
149        SecretKeyFactory skf = SecretKeyFactory.getInstance(algoForTest);
150        try {
151            skf.translateKey(key1);
152            throw new RuntimeException(
153                    "translateSpoiledKey test case failed, should throw"
154                            + " InvalidKeyException when spoil the key");
155        } catch (InvalidKeyException ike) {
156            out.println("Expected exception when spoil the key");
157        }
158
159    }
160
161    /**
162     * Generate a PBKDF2 secret key using given algorithm.
163     */
164    private SecretKey getSecretKeyForPBKDF2(String algoDeriveKey, byte[] salt)
165            throws NoSuchAlgorithmException, InvalidKeySpecException {
166
167        SecretKeyFactory skf = SecretKeyFactory.getInstance(algoDeriveKey);
168        PBEKeySpec spec = new PBEKeySpec(PASS_PHRASE.toCharArray(), salt,
169                ITERATION_COUNT, KEY_SIZE);
170
171        return skf.generateSecret(spec);
172    }
173
174    /**
175     * Generate a secrete key as an instance of a class implements PBEKey.
176     */
177    private SecretKey getMyOwnSecretKey(byte[] salt)
178            throws InvalidKeySpecException, NoSuchAlgorithmException {
179        return new MyPBKDF2SecretKey(PASS_PHRASE, algoForTest, salt,
180                ITERATION_COUNT, KEY_SIZE);
181    }
182
183    /**
184     * An utility class to check the SecretKeyFactory.translateKey() method.
185     */
186    class MyPBKDF2SecretKey implements PBEKey {
187        private final byte[] key;
188        private final byte[] salt;
189        private final String algorithm;
190        private final int keyLength;
191        private final String pass;
192        private int itereationCount;
193
194        /**
195         * The key is generating by SecretKeyFactory and its value just copying
196         * in the key field of MySecretKey class. So, this is real key derived
197         * using the given algo.
198         */
199        public MyPBKDF2SecretKey(String passPhrase, String algo, byte[] salt1,
200                int iterationCount, int keySize)
201                throws InvalidKeySpecException, NoSuchAlgorithmException {
202            algorithm = algo;
203            salt = salt1;
204            itereationCount = iterationCount;
205            pass = passPhrase;
206
207            PBEKeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), salt,
208                    iterationCount, keySize);
209
210            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algo);
211
212            SecretKey realKey = keyFactory.generateSecret(spec);
213
214            keyLength = realKey.getEncoded().length;
215
216            key = new byte[keyLength];
217            System.arraycopy(realKey.getEncoded(), 0, key, 0, keyLength);
218        }
219
220        @Override
221        public String getAlgorithm() {
222            return algorithm;
223        }
224
225        @Override
226        public String getFormat() {
227            return "RAW";
228        }
229
230        @Override
231        public byte[] getEncoded() {
232            byte[] copy = new byte[keyLength];
233            System.arraycopy(key, 0, copy, 0, keyLength);
234            return copy;
235        }
236
237        @Override
238        public int getIterationCount() {
239            return itereationCount;
240        }
241
242        @Override
243        public byte[] getSalt() {
244            return salt;
245        }
246
247        @Override
248        public char[] getPassword() {
249            return pass.toCharArray();
250        }
251
252        /**
253         * Spoil the generated key (before translation) to cause an
254         * InvalidKeyException
255         */
256        public void spoil() {
257            itereationCount = -1;
258        }
259
260        @Override
261        public void destroy() throws DestroyFailedException {
262        }
263
264        @Override
265        public boolean isDestroyed() {
266            return false;
267        }
268
269    }
270}
271