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 }