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 } --- EOF ---