1 /* 2 * Copyright (c) 2005, 2019, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.crypto.provider; 27 28 import java.io.ObjectStreamException; 29 import java.lang.ref.Reference; 30 import java.nio.ByteBuffer; 31 import java.nio.CharBuffer; 32 import java.nio.charset.Charset; 33 import java.util.Arrays; 34 import java.util.Locale; 35 import java.security.MessageDigest; 36 import java.security.KeyRep; 37 import java.security.GeneralSecurityException; 38 import java.security.NoSuchAlgorithmException; 39 import java.security.spec.InvalidKeySpecException; 40 import javax.crypto.Mac; 41 import javax.crypto.SecretKey; 42 import javax.crypto.spec.PBEKeySpec; 43 44 import jdk.internal.ref.CleanerFactory; 45 46 /** 47 * This class represents a PBE key derived using PBKDF2 defined 48 * in PKCS#5 v2.0. meaning that 49 * 1) the password must consist of characters which will be converted 50 * to bytes using UTF-8 character encoding. 51 * 2) salt, iteration count, and to be derived key length are supplied 52 * 53 * @author Valerie Peng 54 * 55 */ 56 final class PBKDF2KeyImpl implements javax.crypto.interfaces.PBEKey { 57 58 static final long serialVersionUID = -2234868909660948157L; 59 60 private char[] passwd; 61 private byte[] salt; 62 private int iterCount; 63 private byte[] key; 64 65 private Mac prf; 66 getPasswordBytes(char[] passwd)67 private static byte[] getPasswordBytes(char[] passwd) { 68 Charset utf8 = Charset.forName("UTF-8"); 69 CharBuffer cb = CharBuffer.wrap(passwd); 70 ByteBuffer bb = utf8.encode(cb); 71 72 int len = bb.limit(); 73 byte[] passwdBytes = new byte[len]; 74 bb.get(passwdBytes, 0, len); 75 76 return passwdBytes; 77 } 78 79 /** 80 * Creates a PBE key from a given PBE key specification. 81 * 82 * @param keySpec the given PBE key specification 83 * @param prfAlgo the given PBE key algorithm 84 */ PBKDF2KeyImpl(PBEKeySpec keySpec, String prfAlgo)85 PBKDF2KeyImpl(PBEKeySpec keySpec, String prfAlgo) 86 throws InvalidKeySpecException { 87 char[] passwd = keySpec.getPassword(); 88 if (passwd == null) { 89 // Should allow an empty password. 90 this.passwd = new char[0]; 91 } else { 92 this.passwd = passwd.clone(); 93 } 94 // Convert the password from char[] to byte[] 95 byte[] passwdBytes = getPasswordBytes(this.passwd); 96 // remove local copy 97 if (passwd != null) Arrays.fill(passwd, '\0'); 98 99 try { 100 this.salt = keySpec.getSalt(); 101 if (salt == null) { 102 throw new InvalidKeySpecException("Salt not found"); 103 } 104 this.iterCount = keySpec.getIterationCount(); 105 if (iterCount == 0) { 106 throw new InvalidKeySpecException("Iteration count not found"); 107 } else if (iterCount < 0) { 108 throw new InvalidKeySpecException("Iteration count is negative"); 109 } 110 int keyLength = keySpec.getKeyLength(); 111 if (keyLength == 0) { 112 throw new InvalidKeySpecException("Key length not found"); 113 } else if (keyLength < 0) { 114 throw new InvalidKeySpecException("Key length is negative"); 115 } 116 this.prf = Mac.getInstance(prfAlgo, SunJCE.getInstance()); 117 this.key = deriveKey(prf, passwdBytes, salt, iterCount, keyLength); 118 } catch (NoSuchAlgorithmException nsae) { 119 // not gonna happen; re-throw just in case 120 InvalidKeySpecException ike = new InvalidKeySpecException(); 121 ike.initCause(nsae); 122 throw ike; 123 } finally { 124 Arrays.fill(passwdBytes, (byte) 0x00); 125 126 // Use the cleaner to zero the key when no longer referenced 127 final byte[] k = this.key; 128 final char[] p = this.passwd; 129 CleanerFactory.cleaner().register(this, 130 () -> { 131 Arrays.fill(k, (byte) 0x00); 132 Arrays.fill(p, '\0'); 133 }); 134 } 135 } 136 deriveKey(final Mac prf, final byte[] password, byte[] salt, int iterCount, int keyLengthInBit)137 private static byte[] deriveKey(final Mac prf, final byte[] password, 138 byte[] salt, int iterCount, int keyLengthInBit) { 139 int keyLength = keyLengthInBit/8; 140 byte[] key = new byte[keyLength]; 141 try { 142 int hlen = prf.getMacLength(); 143 int intL = (keyLength + hlen - 1)/hlen; // ceiling 144 int intR = keyLength - (intL - 1)*hlen; // residue 145 byte[] ui = new byte[hlen]; 146 byte[] ti = new byte[hlen]; 147 // SecretKeySpec cannot be used, since password can be empty here. 148 SecretKey macKey = new SecretKey() { 149 private static final long serialVersionUID = 7874493593505141603L; 150 @Override 151 public String getAlgorithm() { 152 return prf.getAlgorithm(); 153 } 154 @Override 155 public String getFormat() { 156 return "RAW"; 157 } 158 @Override 159 public byte[] getEncoded() { 160 return password; 161 } 162 @Override 163 public int hashCode() { 164 return Arrays.hashCode(password) * 41 + 165 prf.getAlgorithm().toLowerCase(Locale.ENGLISH).hashCode(); 166 } 167 @Override 168 public boolean equals(Object obj) { 169 if (this == obj) return true; 170 if (this.getClass() != obj.getClass()) return false; 171 SecretKey sk = (SecretKey)obj; 172 return prf.getAlgorithm().equalsIgnoreCase( 173 sk.getAlgorithm()) && 174 MessageDigest.isEqual(password, sk.getEncoded()); 175 } 176 }; 177 prf.init(macKey); 178 179 byte[] ibytes = new byte[4]; 180 for (int i = 1; i <= intL; i++) { 181 prf.update(salt); 182 ibytes[3] = (byte) i; 183 ibytes[2] = (byte) ((i >> 8) & 0xff); 184 ibytes[1] = (byte) ((i >> 16) & 0xff); 185 ibytes[0] = (byte) ((i >> 24) & 0xff); 186 prf.update(ibytes); 187 prf.doFinal(ui, 0); 188 System.arraycopy(ui, 0, ti, 0, ui.length); 189 190 for (int j = 2; j <= iterCount; j++) { 191 prf.update(ui); 192 prf.doFinal(ui, 0); 193 // XOR the intermediate Ui's together. 194 for (int k = 0; k < ui.length; k++) { 195 ti[k] ^= ui[k]; 196 } 197 } 198 if (i == intL) { 199 System.arraycopy(ti, 0, key, (i-1)*hlen, intR); 200 } else { 201 System.arraycopy(ti, 0, key, (i-1)*hlen, hlen); 202 } 203 } 204 } catch (GeneralSecurityException gse) { 205 throw new RuntimeException("Error deriving PBKDF2 keys", gse); 206 } 207 return key; 208 } 209 getEncoded()210 public byte[] getEncoded() { 211 // The key is zeroized by finalize() 212 // The reachability fence ensures finalize() isn't called early 213 byte[] result = key.clone(); 214 Reference.reachabilityFence(this); 215 return result; 216 } 217 getAlgorithm()218 public String getAlgorithm() { 219 return "PBKDF2With" + prf.getAlgorithm(); 220 } 221 getIterationCount()222 public int getIterationCount() { 223 return iterCount; 224 } 225 getPassword()226 public char[] getPassword() { 227 // The password is zeroized by finalize() 228 // The reachability fence ensures finalize() isn't called early 229 char[] result = passwd.clone(); 230 Reference.reachabilityFence(this); 231 return result; 232 } 233 getSalt()234 public byte[] getSalt() { 235 return salt.clone(); 236 } 237 getFormat()238 public String getFormat() { 239 return "RAW"; 240 } 241 242 /** 243 * Calculates a hash code value for the object. 244 * Objects that are equal will also have the same hashcode. 245 */ hashCode()246 public int hashCode() { 247 int retval = 0; 248 for (int i = 1; i < this.key.length; i++) { 249 retval += this.key[i] * i; 250 } 251 return(retval ^= getAlgorithm().toLowerCase(Locale.ENGLISH).hashCode()); 252 } 253 equals(Object obj)254 public boolean equals(Object obj) { 255 if (obj == this) 256 return true; 257 258 if (!(obj instanceof SecretKey)) 259 return false; 260 261 SecretKey that = (SecretKey) obj; 262 263 if (!(that.getAlgorithm().equalsIgnoreCase(getAlgorithm()))) 264 return false; 265 if (!(that.getFormat().equalsIgnoreCase("RAW"))) 266 return false; 267 byte[] thatEncoded = that.getEncoded(); 268 boolean ret = MessageDigest.isEqual(key, thatEncoded); 269 Arrays.fill(thatEncoded, (byte)0x00); 270 return ret; 271 } 272 273 /** 274 * Replace the PBE key to be serialized. 275 * 276 * @return the standard KeyRep object to be serialized 277 * 278 * @throws ObjectStreamException if a new object representing 279 * this PBE key could not be created 280 */ writeReplace()281 private Object writeReplace() throws ObjectStreamException { 282 return new KeyRep(KeyRep.Type.SECRET, getAlgorithm(), 283 getFormat(), getEncoded()); 284 } 285 } 286