1 /*
   2  * Copyright (c) 2012, 2014, 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 
  24 import java.security.InvalidKeyException;
  25 import java.security.NoSuchAlgorithmException;
  26 import java.security.spec.InvalidKeySpecException;
  27 import java.util.Arrays;
  28 import java.util.Random;
  29 import javax.crypto.SecretKey;
  30 import javax.crypto.SecretKeyFactory;
  31 import javax.crypto.interfaces.PBEKey;
  32 import javax.crypto.spec.PBEKeySpec;
  33 
  34 /**
  35  * @test
  36  * @bug 8041781
  37  * @summary Verify if the SecretKeyFactory.translateKey() method works
  38  * @author Alexander Fomin
  39  * @run main PBKDF2Translate
  40  */
  41 public class PBKDF2Translate {
  42 
  43     private static final String[] ALGO_TO_TEST = {
  44         "PBKDF2WithHmacSHA1",
  45         "PBKDF2WithHmacSHA224",
  46         "PBKDF2WithHmacSHA256",
  47         "PBKDF2WithHmacSHA384",
  48         "PBKDF2WithHmacSHA512"
  49     };
  50 
  51     private static final String PASS_PHRASE = "some hidden string";
  52     private static final int ITERATION_COUNT = 1000;
  53     private static final int KEY_SIZE = 128;
  54 
  55     private final String algoToTest;
  56     private final byte[] salt = new byte[8];
  57 
  58     public static void main(String[] args) throws Exception {
  59 
  60         boolean failed = false;
  61 
  62         for (String algo : ALGO_TO_TEST) {
  63 
  64             System.out.println("Testing " + algo + ":");
  65             PBKDF2Translate theTest = new PBKDF2Translate(algo);
  66 
  67             try {
  68                 if (!theTest.testMyOwnSecretKey()
  69                         || !theTest.generateAndTranslateKey()
  70                         || !theTest.translateSpoiledKey()) {
  71                     // we don't want to set failed to false
  72                     failed = true;
  73                 }
  74             } catch (InvalidKeyException | NoSuchAlgorithmException |
  75                     InvalidKeySpecException e) {
  76                 e.printStackTrace(System.err);
  77                 failed = true;
  78             }
  79         }
  80 
  81         if (failed) {
  82             throw new RuntimeException("One or more tests failed....");
  83         }
  84     }
  85 
  86     public PBKDF2Translate(String algoToTest) {
  87         this.algoToTest = algoToTest;
  88         new Random().nextBytes(this.salt);
  89     }
  90 
  91     /**
  92      * The test case scenario implemented in the method: - derive PBKDF2 key
  93      * using the given algorithm; - translate the key - check if the translated
  94      * and original keys have the same key value.
  95      *
  96      * @return true if the test case passed; false - otherwise.
  97      * @throws NoSuchAlgorithmException
  98      * @throws InvalidKeySpecException
  99      * @throws InvalidKeyException
 100      */
 101     public boolean generateAndTranslateKey() throws NoSuchAlgorithmException,
 102             InvalidKeySpecException, InvalidKeyException {
 103         // derive PBKDF2 key
 104         SecretKey key1 = getSecretKeyForPBKDF2(algoToTest);
 105 
 106         // translate key
 107         SecretKeyFactory skf = SecretKeyFactory.getInstance(algoToTest);
 108         SecretKey key2 = skf.translateKey(key1);
 109 
 110         // check if it still the same after translation
 111         if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
 112             System.err.println("generateAndTranslateKey test case failed: the "
 113                     + "key1 and key2 values in its primary encoding format are "
 114                     + "not the same for " + algoToTest + "algorithm.");
 115             return false;
 116         }
 117 
 118         return true;
 119     }
 120 
 121     /**
 122      * The test case scenario implemented in the method: - derive Key1 for the
 123      * given PBKDF2 algorithm - create my own secret Key2 as an instance of a
 124      * class implements PBEKey - translate Key2 - check if the key value of the
 125      * translated key and Key1 are the same.
 126      *
 127      * @return true if the test case passed; false - otherwise.
 128      * @throws NoSuchAlgorithmException
 129      * @throws InvalidKeySpecException
 130      * @throws InvalidKeyException
 131      */
 132     public boolean testMyOwnSecretKey()
 133             throws NoSuchAlgorithmException, InvalidKeySpecException,
 134             InvalidKeyException {
 135         SecretKey key1 = getSecretKeyForPBKDF2(algoToTest);
 136         SecretKey key2 = getMyOwnSecretKey();
 137 
 138         // Is it actually the same?
 139         if (!Arrays.equals(key1.getEncoded(), key2.getEncoded())) {
 140             System.err.println("We shouldn't be here. The key1 and key2 values "
 141                     + "in its primary encoding format have to be the same!");
 142             return false;
 143         }
 144 
 145         // Translate key
 146         SecretKeyFactory skf = SecretKeyFactory.getInstance(algoToTest);
 147         SecretKey key3 = skf.translateKey(key2);
 148 
 149         // Check if it still the same after translation
 150         if (!Arrays.equals(key1.getEncoded(), key3.getEncoded())) {
 151             System.err.println("testMyOwnSecretKey test case failed: the key1 "
 152                     + "and key3 values in its primary encoding format are not "
 153                     + "the same for " + algoToTest + "algorithm.");
 154             return false;
 155         }
 156 
 157         return true;
 158     }
 159 
 160     /**
 161      * The test case scenario implemented in the method: - create my own secret
 162      * Key2 as an instance of a class implements PBEKey - spoil the key (set
 163      * iteration count to 0, for example) - try to translate key -
 164      * InvalidKeyException is expected.
 165      *
 166      * @return true if InvalidKeyException occurred; false - otherwise.
 167      * @throws NoSuchAlgorithmException
 168      * @throws InvalidKeySpecException
 169      */
 170     public boolean translateSpoiledKey() throws NoSuchAlgorithmException,
 171             InvalidKeySpecException {
 172         // derive the key
 173         SecretKey key1 = getMyOwnSecretKey();
 174 
 175         // spoil the key
 176         ((MyPBKDF2SecretKey) key1).spoil();
 177 
 178         // translate key
 179         SecretKeyFactory skf = SecretKeyFactory.getInstance(algoToTest);
 180         try {
 181             SecretKey key2 = skf.translateKey(key1);
 182         } catch (InvalidKeyException ike) {
 183             // this is expected
 184             return true;
 185         }
 186 
 187         return false;
 188     }
 189 
 190     /**
 191      * Generate a PBKDF2 secret key using given algorithm.
 192      *
 193      * @param algoToDeriveKey PBKDF2 algorithm
 194      * @return PBKDF2 secret key
 195      * @throws NoSuchAlgorithmException
 196      * @throws InvalidKeySpecException
 197      */
 198     private SecretKey getSecretKeyForPBKDF2(String algoToDeriveKey)
 199             throws NoSuchAlgorithmException, InvalidKeySpecException {
 200         SecretKeyFactory skf = SecretKeyFactory.getInstance(algoToDeriveKey);
 201 
 202         PBEKeySpec spec = new PBEKeySpec(PASS_PHRASE.toCharArray(),
 203                 this.salt, ITERATION_COUNT, KEY_SIZE);
 204 
 205         return skf.generateSecret(spec);
 206     }
 207 
 208     /**
 209      * Generate a secrete key as an instance of a class implements PBEKey.
 210      *
 211      * @return secrete key
 212      * @throws InvalidKeySpecException
 213      * @throws NoSuchAlgorithmException
 214      */
 215     private SecretKey getMyOwnSecretKey() throws InvalidKeySpecException,
 216             NoSuchAlgorithmException {
 217         return new MyPBKDF2SecretKey(PASS_PHRASE, this.algoToTest, this.salt,
 218                 ITERATION_COUNT, KEY_SIZE);
 219     }
 220 }
 221 
 222 /**
 223  * An utility class to check the SecretKeyFactory.translateKey() method.
 224  */
 225 class MyPBKDF2SecretKey implements PBEKey {
 226 
 227     private final byte[] key;
 228     private final byte[] salt;
 229     private final String algorithm;
 230     private final int keySize, keyLength;
 231     private int itereationCount;
 232     private final String pass;
 233 
 234     @Override
 235     public String getAlgorithm() {
 236         return algorithm;
 237     }
 238 
 239     @Override
 240     public String getFormat() {
 241         return "RAW";
 242     }
 243 
 244     @Override
 245     public byte[] getEncoded() {
 246         byte[] copy = new byte[keyLength];
 247         System.arraycopy(this.key, 0, copy, 0, keyLength);
 248         return copy;
 249     }
 250 
 251     /**
 252      * The key is generating by SecretKeyFactory and its value just copying in
 253      * the key field of MySecretKey class. So, this is real key derived using
 254      * the given algorithm.
 255      *
 256      * @param passPhrase some string intended to be a password
 257      * @param algo PBKDF2 algorithm
 258      * @param salt slat for PBKDF2
 259      * @param iterationCount iteration count
 260      * @param keySize key size in bits
 261      * @throws InvalidKeySpecException
 262      * @throws NoSuchAlgorithmException
 263      */
 264     public MyPBKDF2SecretKey(String passPhrase, String algo, byte[] salt,
 265             int iterationCount, int keySize)
 266             throws InvalidKeySpecException, NoSuchAlgorithmException {
 267         this.algorithm = algo;
 268         this.salt = salt;
 269         this.itereationCount = iterationCount;
 270         this.keySize = keySize;
 271         this.pass = passPhrase;
 272 
 273         PBEKeySpec spec = new PBEKeySpec(passPhrase.toCharArray(),
 274                 this.salt, iterationCount, this.keySize);
 275 
 276         SecretKeyFactory keyFactory
 277                 = SecretKeyFactory.getInstance(algo);
 278 
 279         SecretKey realKey = keyFactory.generateSecret(spec);
 280 
 281         this.keyLength = realKey.getEncoded().length;
 282 
 283         this.key = new byte[this.keyLength];
 284         System.arraycopy(realKey.getEncoded(), 0, this.key, 0,
 285                 this.keyLength);
 286     }
 287 
 288     @Override
 289     public int getIterationCount() {
 290         return itereationCount;
 291     }
 292 
 293     @Override
 294     public byte[] getSalt() {
 295         return salt;
 296     }
 297 
 298     @Override
 299     public char[] getPassword() {
 300         return this.pass.toCharArray();
 301     }
 302 
 303     /**
 304      * Spoil the generated key (before translation) to cause an
 305      * InvalidKeyException
 306      */
 307     public void spoil() {
 308         this.itereationCount = -1;
 309     }
 310 
 311 }