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