1 /*
   2  * Copyright 2005-2007 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package com.sun.crypto.provider;
  27 
  28 import java.io.*;
  29 import java.nio.ByteBuffer;
  30 import java.nio.CharBuffer;
  31 import java.nio.charset.Charset;
  32 import java.util.Arrays;
  33 import java.security.KeyRep;
  34 import java.security.GeneralSecurityException;
  35 import java.security.InvalidKeyException;
  36 import java.security.NoSuchAlgorithmException;
  37 import java.security.NoSuchProviderException;
  38 import java.security.spec.InvalidKeySpecException;
  39 import javax.crypto.Mac;
  40 import javax.crypto.SecretKey;
  41 import javax.crypto.spec.PBEKeySpec;
  42 import javax.crypto.spec.SecretKeySpec;
  43 
  44 /**
  45  * This class represents a PBE key derived using PBKDF2 defined
  46  * in PKCS#5 v2.0. meaning that
  47  * 1) the password must consist of characters which will be converted
  48  *    to bytes using UTF-8 character encoding.
  49  * 2) salt, iteration count, and to be derived key length are supplied
  50  *
  51  * @author Valerie Peng
  52  *
  53  */
  54 final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
  55 
  56     static final long serialVersionUID = -2234868909660948157L;
  57 
  58     private char[] passwd;
  59     private byte[] salt;
  60     private int iterCount;
  61     private byte[] key;
  62 
  63     private Mac prf;
  64 
  65     private static byte[] getPasswordBytes(char[] passwd) {
  66         Charset utf8 = Charset.forName("UTF-8");
  67         CharBuffer cb = CharBuffer.wrap(passwd);
  68         ByteBuffer bb = utf8.encode(cb);
  69 
  70         int len = bb.limit();
  71         byte[] passwdBytes = new byte[len];
  72         bb.get(passwdBytes, 0, len);
  73 
  74         return passwdBytes;
  75     }
  76 
  77     /**
  78      * Creates a PBE key from a given PBE key specification.
  79      *
  80      * @param key the given PBE key specification
  81      */
  82     PBKDF2KeyImpl(PBEKeySpec keySpec, String prfAlgo)
  83         throws InvalidKeySpecException {
  84         char[] passwd = keySpec.getPassword();
  85         if (passwd == null) {
  86             // Should allow an empty password.
  87             this.passwd = new char[0];
  88         } else {
  89             this.passwd = passwd.clone();
  90         }
  91         // Convert the password from char[] to byte[]
  92         byte[] passwdBytes = getPasswordBytes(this.passwd);
  93 
  94         this.salt = keySpec.getSalt();
  95         if (salt == null) {
  96             throw new InvalidKeySpecException("Salt not found");
  97         }
  98         this.iterCount = keySpec.getIterationCount();
  99         if (iterCount == 0) {
 100             throw new InvalidKeySpecException("Iteration count not found");
 101         } else if (iterCount < 0) {
 102             throw new InvalidKeySpecException("Iteration count is negative");
 103         }
 104         int keyLength = keySpec.getKeyLength();
 105         if (keyLength == 0) {
 106             throw new InvalidKeySpecException("Key length not found");
 107         } else if (keyLength == 0) {
 108             throw new InvalidKeySpecException("Key length is negative");
 109         }
 110         try {
 111             this.prf = Mac.getInstance(prfAlgo);
 112         } catch (NoSuchAlgorithmException nsae) {
 113             // not gonna happen; re-throw just in case
 114             InvalidKeySpecException ike = new InvalidKeySpecException();
 115             ike.initCause(nsae);
 116             throw ike;
 117         }
 118         this.key = deriveKey(prf, passwdBytes, salt, iterCount, keyLength);
 119     }
 120 
 121     private static byte[] deriveKey(Mac prf, byte[] password, byte[] salt,
 122                                     int iterCount, int keyLengthInBit) {
 123         int keyLength = keyLengthInBit/8;
 124         byte[] key = new byte[keyLength];
 125         try {
 126             int hlen = prf.getMacLength();
 127             int intL = (keyLength + hlen - 1)/hlen; // ceiling
 128             int intR = keyLength - (intL - 1)*hlen; // residue
 129             byte[] ui = new byte[hlen];
 130             byte[] ti = new byte[hlen];
 131             SecretKey macKey = new SecretKeySpec(password, prf.getAlgorithm());
 132             prf.init(macKey);
 133 
 134             byte[] ibytes = new byte[4];
 135             for (int i = 1; i <= intL; i++) {
 136                 prf.update(salt);
 137                 ibytes[3] = (byte) i;
 138                 ibytes[2] = (byte) ((i >> 8) & 0xff);
 139                 ibytes[1] = (byte) ((i >> 16) & 0xff);
 140                 ibytes[0] = (byte) ((i >> 24) & 0xff);
 141                 prf.update(ibytes);
 142                 prf.doFinal(ui, 0);
 143                 System.arraycopy(ui, 0, ti, 0, ui.length);
 144 
 145                 for (int j = 2; j <= iterCount; j++) {
 146                     prf.update(ui);
 147                     prf.doFinal(ui, 0);
 148                     // XOR the intermediate Ui's together.
 149                     for (int k = 0; k < ui.length; k++) {
 150                         ti[k] ^= ui[k];
 151                     }
 152                 }
 153                 if (i == intL) {
 154                     System.arraycopy(ti, 0, key, (i-1)*hlen, intR);
 155                 } else {
 156                     System.arraycopy(ti, 0, key, (i-1)*hlen, hlen);
 157                 }
 158             }
 159         } catch (GeneralSecurityException gse) {
 160             throw new RuntimeException("Error deriving PBKDF2 keys");
 161         }
 162         return key;
 163     }
 164 
 165     public byte[] getEncoded() {
 166         return (byte[]) key.clone();
 167     }
 168 
 169     public String getAlgorithm() {
 170         return "PBKDF2With" + prf.getAlgorithm();
 171     }
 172 
 173     public int getIterationCount() {
 174         return iterCount;
 175     }
 176 
 177     public char[] getPassword() {
 178         return (char[]) passwd.clone();
 179     }
 180 
 181     public byte[] getSalt() {
 182         return salt.clone();
 183     }
 184 
 185     public String getFormat() {
 186         return "RAW";
 187     }
 188 
 189     /**
 190      * Calculates a hash code value for the object.
 191      * Objects that are equal will also have the same hashcode.
 192      */
 193     public int hashCode() {
 194         int retval = 0;
 195         for (int i = 1; i < this.key.length; i++) {
 196             retval += this.key[i] * i;
 197         }
 198         return(retval ^= getAlgorithm().toLowerCase().hashCode());
 199     }
 200 
 201     public boolean equals(Object obj) {
 202         if (obj == this)
 203             return true;
 204 
 205         if (!(obj instanceof SecretKey))
 206             return false;
 207 
 208         SecretKey that = (SecretKey) obj;
 209 
 210         if (!(that.getAlgorithm().equalsIgnoreCase(getAlgorithm())))
 211             return false;
 212         if (!(that.getFormat().equalsIgnoreCase("RAW")))
 213             return false;
 214         byte[] thatEncoded = that.getEncoded();
 215         boolean ret = Arrays.equals(key, that.getEncoded());
 216         java.util.Arrays.fill(thatEncoded, (byte)0x00);
 217         return ret;
 218     }
 219 
 220     /**
 221      * Replace the PBE key to be serialized.
 222      *
 223      * @return the standard KeyRep object to be serialized
 224      *
 225      * @throws ObjectStreamException if a new object representing
 226      * this PBE key could not be created
 227      */
 228     private Object writeReplace() throws java.io.ObjectStreamException {
 229             return new KeyRep(KeyRep.Type.SECRET, getAlgorithm(),
 230                               getFormat(), getEncoded());
 231     }
 232 
 233     /**
 234      * Ensures that the password bytes of this key are
 235      * erased when there are no more references to it.
 236      */
 237     protected void finalize() throws Throwable {
 238         try {
 239             if (this.passwd != null) {
 240                 java.util.Arrays.fill(this.passwd, (char) '0');
 241                 this.passwd = null;
 242             }
 243             if (this.key != null) {
 244                 java.util.Arrays.fill(this.key, (byte)0x00);
 245                 this.key = null;
 246             }
 247         } finally {
 248             super.finalize();
 249         }
 250     }
 251 }