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.spec.InvalidKeySpecException;
  38 import javax.crypto.Mac;
  39 import javax.crypto.SecretKey;
  40 import javax.crypto.spec.PBEKeySpec;
  41 import javax.crypto.spec.SecretKeySpec;
  42 
  43 /**
  44  * This class represents a PBE key derived using PBKDF2 defined
  45  * in PKCS#5 v2.0. meaning that
  46  * 1) the password must consist of characters which will be converted
  47  *    to bytes using UTF-8 character encoding.
  48  * 2) salt, iteration count, and to be derived key length are supplied
  49  *
  50  * @author Valerie Peng
  51  *
  52  */
  53 final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey {
  54 
  55     static final long serialVersionUID = -2234868909660948157L;
  56 
  57     private char[] passwd;
  58     private byte[] salt;
  59     private int iterCount;
  60     private byte[] key;
  61 
  62     private Mac prf;
  63 
  64     private static byte[] getPasswordBytes(char[] passwd) {
  65         Charset utf8 = Charset.forName("UTF-8");
  66         CharBuffer cb = CharBuffer.wrap(passwd);
  67         ByteBuffer bb = utf8.encode(cb);
  68 
  69         int len = bb.limit();
  70         byte[] passwdBytes = new byte[len];
  71         bb.get(passwdBytes, 0, len);
  72 
  73         return passwdBytes;
  74     }
  75 
  76     /**
  77      * Creates a PBE key from a given PBE key specification.
  78      *
  79      * @param key the given PBE key specification
  80      */
  81     PBKDF2KeyImpl(PBEKeySpec keySpec, String prfAlgo)
  82         throws InvalidKeySpecException {
  83         char[] passwd = keySpec.getPassword();
  84         if (passwd == null) {
  85             // Should allow an empty password.
  86             this.passwd = new char[0];
  87         } else {
  88             this.passwd = passwd.clone();
  89         }
  90         // Convert the password from char[] to byte[]
  91         byte[] passwdBytes = getPasswordBytes(this.passwd);
  92 
  93         this.salt = keySpec.getSalt();
  94         if (salt == null) {
  95             throw new InvalidKeySpecException("Salt not found");
  96         }
  97         this.iterCount = keySpec.getIterationCount();
  98         if (iterCount == 0) {
  99             throw new InvalidKeySpecException("Iteration count not found");
 100         } else if (iterCount < 0) {
 101             throw new InvalidKeySpecException("Iteration count is negative");
 102         }
 103         int keyLength = keySpec.getKeyLength();
 104         if (keyLength == 0) {
 105             throw new InvalidKeySpecException("Key length not found");
 106         } else if (keyLength == 0) {
 107             throw new InvalidKeySpecException("Key length is negative");
 108         }
 109         try {
 110             this.prf = Mac.getInstance(prfAlgo, new SunJCE());
 111         } catch (NoSuchAlgorithmException nsae) {
 112             // not gonna happen; re-throw just in case
 113             InvalidKeySpecException ike = new InvalidKeySpecException();
 114             ike.initCause(nsae);
 115             throw ike;
 116         }
 117         this.key = deriveKey(prf, passwdBytes, salt, iterCount, keyLength);
 118     }
 119 
 120     private static byte[] deriveKey(Mac prf, byte[] password, byte[] salt,
 121                                     int iterCount, int keyLengthInBit) {
 122         int keyLength = keyLengthInBit/8;
 123         byte[] key = new byte[keyLength];
 124         try {
 125             int hlen = prf.getMacLength();
 126             int intL = (keyLength + hlen - 1)/hlen; // ceiling
 127             int intR = keyLength - (intL - 1)*hlen; // residue
 128             byte[] ui = new byte[hlen];
 129             byte[] ti = new byte[hlen];
 130             SecretKey macKey = new SecretKeySpec(password, prf.getAlgorithm());
 131             prf.init(macKey);
 132 
 133             byte[] ibytes = new byte[4];
 134             for (int i = 1; i <= intL; i++) {
 135                 prf.update(salt);
 136                 ibytes[3] = (byte) i;
 137                 ibytes[2] = (byte) ((i >> 8) & 0xff);
 138                 ibytes[1] = (byte) ((i >> 16) & 0xff);
 139                 ibytes[0] = (byte) ((i >> 24) & 0xff);
 140                 prf.update(ibytes);
 141                 prf.doFinal(ui, 0);
 142                 System.arraycopy(ui, 0, ti, 0, ui.length);
 143 
 144                 for (int j = 2; j <= iterCount; j++) {
 145                     prf.update(ui);
 146                     prf.doFinal(ui, 0);
 147                     // XOR the intermediate Ui's together.
 148                     for (int k = 0; k < ui.length; k++) {
 149                         ti[k] ^= ui[k];
 150                     }
 151                 }
 152                 if (i == intL) {
 153                     System.arraycopy(ti, 0, key, (i-1)*hlen, intR);
 154                 } else {
 155                     System.arraycopy(ti, 0, key, (i-1)*hlen, hlen);
 156                 }
 157             }
 158         } catch (GeneralSecurityException gse) {
 159             throw new RuntimeException("Error deriving PBKDF2 keys");
 160         }
 161         return key;
 162     }
 163 
 164     public byte[] getEncoded() {
 165         return (byte[]) key.clone();
 166     }
 167 
 168     public String getAlgorithm() {
 169         return "PBKDF2With" + prf.getAlgorithm();
 170     }
 171 
 172     public int getIterationCount() {
 173         return iterCount;
 174     }
 175 
 176     public char[] getPassword() {
 177         return (char[]) passwd.clone();
 178     }
 179 
 180     public byte[] getSalt() {
 181         return salt.clone();
 182     }
 183 
 184     public String getFormat() {
 185         return "RAW";
 186     }
 187 
 188     /**
 189      * Calculates a hash code value for the object.
 190      * Objects that are equal will also have the same hashcode.
 191      */
 192     public int hashCode() {
 193         int retval = 0;
 194         for (int i = 1; i < this.key.length; i++) {
 195             retval += this.key[i] * i;
 196         }
 197         return(retval ^= getAlgorithm().toLowerCase().hashCode());
 198     }
 199 
 200     public boolean equals(Object obj) {
 201         if (obj == this)
 202             return true;
 203 
 204         if (!(obj instanceof SecretKey))
 205             return false;
 206 
 207         SecretKey that = (SecretKey) obj;
 208 
 209         if (!(that.getAlgorithm().equalsIgnoreCase(getAlgorithm())))
 210             return false;
 211         if (!(that.getFormat().equalsIgnoreCase("RAW")))
 212             return false;
 213         byte[] thatEncoded = that.getEncoded();
 214         boolean ret = Arrays.equals(key, that.getEncoded());
 215         java.util.Arrays.fill(thatEncoded, (byte)0x00);
 216         return ret;
 217     }
 218 
 219     /**
 220      * Replace the PBE key to be serialized.
 221      *
 222      * @return the standard KeyRep object to be serialized
 223      *
 224      * @throws ObjectStreamException if a new object representing
 225      * this PBE key could not be created
 226      */
 227     private Object writeReplace() throws java.io.ObjectStreamException {
 228             return new KeyRep(KeyRep.Type.SECRET, getAlgorithm(),
 229                               getFormat(), getEncoded());
 230     }
 231 
 232     /**
 233      * Ensures that the password bytes of this key are
 234      * erased when there are no more references to it.
 235      */
 236     protected void finalize() throws Throwable {
 237         try {
 238             if (this.passwd != null) {
 239                 java.util.Arrays.fill(this.passwd, (char) '0');
 240                 this.passwd = null;
 241             }
 242             if (this.key != null) {
 243                 java.util.Arrays.fill(this.key, (byte)0x00);
 244                 this.key = null;
 245             }
 246         } finally {
 247             super.finalize();
 248         }
 249     }
 250 }