KeyProtector.java revision 10444:f08705540498
1/* 2 * Copyright (c) 1997, 2006, 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 26package sun.security.provider; 27 28import java.io.IOException; 29import java.io.UnsupportedEncodingException; 30import java.security.Key; 31import java.security.KeyStoreException; 32import java.security.MessageDigest; 33import java.security.NoSuchAlgorithmException; 34import java.security.SecureRandom; 35import java.security.UnrecoverableKeyException; 36import java.util.*; 37 38import sun.security.pkcs.PKCS8Key; 39import sun.security.pkcs.EncryptedPrivateKeyInfo; 40import sun.security.x509.AlgorithmId; 41import sun.security.util.ObjectIdentifier; 42import sun.security.util.DerValue; 43 44/** 45 * This is an implementation of a Sun proprietary, exportable algorithm 46 * intended for use when protecting (or recovering the cleartext version of) 47 * sensitive keys. 48 * This algorithm is not intended as a general purpose cipher. 49 * 50 * This is how the algorithm works for key protection: 51 * 52 * p - user password 53 * s - random salt 54 * X - xor key 55 * P - to-be-protected key 56 * Y - protected key 57 * R - what gets stored in the keystore 58 * 59 * Step 1: 60 * Take the user's password, append a random salt (of fixed size) to it, 61 * and hash it: d1 = digest(p, s) 62 * Store d1 in X. 63 * 64 * Step 2: 65 * Take the user's password, append the digest result from the previous step, 66 * and hash it: dn = digest(p, dn-1). 67 * Store dn in X (append it to the previously stored digests). 68 * Repeat this step until the length of X matches the length of the private key 69 * P. 70 * 71 * Step 3: 72 * XOR X and P, and store the result in Y: Y = X XOR P. 73 * 74 * Step 4: 75 * Store s, Y, and digest(p, P) in the result buffer R: 76 * R = s + Y + digest(p, P), where "+" denotes concatenation. 77 * (NOTE: digest(p, P) is stored in the result buffer, so that when the key is 78 * recovered, we can check if the recovered key indeed matches the original 79 * key.) R is stored in the keystore. 80 * 81 * The protected key is recovered as follows: 82 * 83 * Step1 and Step2 are the same as above, except that the salt is not randomly 84 * generated, but taken from the result R of step 4 (the first length(s) 85 * bytes). 86 * 87 * Step 3 (XOR operation) yields the plaintext key. 88 * 89 * Then concatenate the password with the recovered key, and compare with the 90 * last length(digest(p, P)) bytes of R. If they match, the recovered key is 91 * indeed the same key as the original key. 92 * 93 * @author Jan Luehe 94 * 95 * 96 * @see java.security.KeyStore 97 * @see JavaKeyStore 98 * @see KeyTool 99 * 100 * @since 1.2 101 */ 102 103final class KeyProtector { 104 105 private static final int SALT_LEN = 20; // the salt length 106 private static final String DIGEST_ALG = "SHA"; 107 private static final int DIGEST_LEN = 20; 108 109 // defined by JavaSoft 110 private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1"; 111 112 // The password used for protecting/recovering keys passed through this 113 // key protector. We store it as a byte array, so that we can digest it. 114 private byte[] passwdBytes; 115 116 private MessageDigest md; 117 118 119 /** 120 * Creates an instance of this class, and initializes it with the given 121 * password. 122 * 123 * <p>The password is expected to be in printable ASCII. 124 * Normal rules for good password selection apply: at least 125 * seven characters, mixed case, with punctuation encouraged. 126 * Phrases or words which are easily guessed, for example by 127 * being found in dictionaries, are bad. 128 */ 129 public KeyProtector(char[] password) 130 throws NoSuchAlgorithmException 131 { 132 int i, j; 133 134 if (password == null) { 135 throw new IllegalArgumentException("password can't be null"); 136 } 137 md = MessageDigest.getInstance(DIGEST_ALG); 138 // Convert password to byte array, so that it can be digested 139 passwdBytes = new byte[password.length * 2]; 140 for (i=0, j=0; i<password.length; i++) { 141 passwdBytes[j++] = (byte)(password[i] >> 8); 142 passwdBytes[j++] = (byte)password[i]; 143 } 144 } 145 146 /** 147 * Ensures that the password bytes of this key protector are 148 * set to zero when there are no more references to it. 149 */ 150 protected void finalize() { 151 if (passwdBytes != null) { 152 Arrays.fill(passwdBytes, (byte)0x00); 153 passwdBytes = null; 154 } 155 } 156 157 /* 158 * Protects the given plaintext key, using the password provided at 159 * construction time. 160 */ 161 public byte[] protect(Key key) throws KeyStoreException 162 { 163 int i; 164 int numRounds; 165 byte[] digest; 166 int xorOffset; // offset in xorKey where next digest will be stored 167 int encrKeyOffset = 0; 168 169 if (key == null) { 170 throw new IllegalArgumentException("plaintext key can't be null"); 171 } 172 173 if (!"PKCS#8".equalsIgnoreCase(key.getFormat())) { 174 throw new KeyStoreException( 175 "Cannot get key bytes, not PKCS#8 encoded"); 176 } 177 178 byte[] plainKey = key.getEncoded(); 179 if (plainKey == null) { 180 throw new KeyStoreException( 181 "Cannot get key bytes, encoding not supported"); 182 } 183 184 // Determine the number of digest rounds 185 numRounds = plainKey.length / DIGEST_LEN; 186 if ((plainKey.length % DIGEST_LEN) != 0) 187 numRounds++; 188 189 // Create a random salt 190 byte[] salt = new byte[SALT_LEN]; 191 SecureRandom random = new SecureRandom(); 192 random.nextBytes(salt); 193 194 // Set up the byte array which will be XORed with "plainKey" 195 byte[] xorKey = new byte[plainKey.length]; 196 197 // Compute the digests, and store them in "xorKey" 198 for (i = 0, xorOffset = 0, digest = salt; 199 i < numRounds; 200 i++, xorOffset += DIGEST_LEN) { 201 md.update(passwdBytes); 202 md.update(digest); 203 digest = md.digest(); 204 md.reset(); 205 // Copy the digest into "xorKey" 206 if (i < numRounds - 1) { 207 System.arraycopy(digest, 0, xorKey, xorOffset, 208 digest.length); 209 } else { 210 System.arraycopy(digest, 0, xorKey, xorOffset, 211 xorKey.length - xorOffset); 212 } 213 } 214 215 // XOR "plainKey" with "xorKey", and store the result in "tmpKey" 216 byte[] tmpKey = new byte[plainKey.length]; 217 for (i = 0; i < tmpKey.length; i++) { 218 tmpKey[i] = (byte)(plainKey[i] ^ xorKey[i]); 219 } 220 221 // Store salt and "tmpKey" in "encrKey" 222 byte[] encrKey = new byte[salt.length + tmpKey.length + DIGEST_LEN]; 223 System.arraycopy(salt, 0, encrKey, encrKeyOffset, salt.length); 224 encrKeyOffset += salt.length; 225 System.arraycopy(tmpKey, 0, encrKey, encrKeyOffset, tmpKey.length); 226 encrKeyOffset += tmpKey.length; 227 228 // Append digest(password, plainKey) as an integrity check to "encrKey" 229 md.update(passwdBytes); 230 Arrays.fill(passwdBytes, (byte)0x00); 231 passwdBytes = null; 232 md.update(plainKey); 233 digest = md.digest(); 234 md.reset(); 235 System.arraycopy(digest, 0, encrKey, encrKeyOffset, digest.length); 236 237 // wrap the protected private key in a PKCS#8-style 238 // EncryptedPrivateKeyInfo, and returns its encoding 239 AlgorithmId encrAlg; 240 try { 241 encrAlg = new AlgorithmId(new ObjectIdentifier(KEY_PROTECTOR_OID)); 242 return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded(); 243 } catch (IOException ioe) { 244 throw new KeyStoreException(ioe.getMessage()); 245 } 246 } 247 248 /* 249 * Recovers the plaintext version of the given key (in protected format), 250 * using the password provided at construction time. 251 */ 252 public Key recover(EncryptedPrivateKeyInfo encrInfo) 253 throws UnrecoverableKeyException 254 { 255 int i; 256 byte[] digest; 257 int numRounds; 258 int xorOffset; // offset in xorKey where next digest will be stored 259 int encrKeyLen; // the length of the encrpyted key 260 261 // do we support the algorithm? 262 AlgorithmId encrAlg = encrInfo.getAlgorithm(); 263 if (!(encrAlg.getOID().toString().equals(KEY_PROTECTOR_OID))) { 264 throw new UnrecoverableKeyException("Unsupported key protection " 265 + "algorithm"); 266 } 267 268 byte[] protectedKey = encrInfo.getEncryptedData(); 269 270 /* 271 * Get the salt associated with this key (the first SALT_LEN bytes of 272 * <code>protectedKey</code>) 273 */ 274 byte[] salt = new byte[SALT_LEN]; 275 System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN); 276 277 // Determine the number of digest rounds 278 encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN; 279 numRounds = encrKeyLen / DIGEST_LEN; 280 if ((encrKeyLen % DIGEST_LEN) != 0) numRounds++; 281 282 // Get the encrypted key portion and store it in "encrKey" 283 byte[] encrKey = new byte[encrKeyLen]; 284 System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen); 285 286 // Set up the byte array which will be XORed with "encrKey" 287 byte[] xorKey = new byte[encrKey.length]; 288 289 // Compute the digests, and store them in "xorKey" 290 for (i = 0, xorOffset = 0, digest = salt; 291 i < numRounds; 292 i++, xorOffset += DIGEST_LEN) { 293 md.update(passwdBytes); 294 md.update(digest); 295 digest = md.digest(); 296 md.reset(); 297 // Copy the digest into "xorKey" 298 if (i < numRounds - 1) { 299 System.arraycopy(digest, 0, xorKey, xorOffset, 300 digest.length); 301 } else { 302 System.arraycopy(digest, 0, xorKey, xorOffset, 303 xorKey.length - xorOffset); 304 } 305 } 306 307 // XOR "encrKey" with "xorKey", and store the result in "plainKey" 308 byte[] plainKey = new byte[encrKey.length]; 309 for (i = 0; i < plainKey.length; i++) { 310 plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]); 311 } 312 313 /* 314 * Check the integrity of the recovered key by concatenating it with 315 * the password, digesting the concatenation, and comparing the 316 * result of the digest operation with the digest provided at the end 317 * of <code>protectedKey</code>. If the two digest values are 318 * different, throw an exception. 319 */ 320 md.update(passwdBytes); 321 Arrays.fill(passwdBytes, (byte)0x00); 322 passwdBytes = null; 323 md.update(plainKey); 324 digest = md.digest(); 325 md.reset(); 326 for (i = 0; i < digest.length; i++) { 327 if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) { 328 throw new UnrecoverableKeyException("Cannot recover key"); 329 } 330 } 331 332 // The parseKey() method of PKCS8Key parses the key 333 // algorithm and instantiates the appropriate key factory, 334 // which in turn parses the key material. 335 try { 336 return PKCS8Key.parseKey(new DerValue(plainKey)); 337 } catch (IOException ioe) { 338 throw new UnrecoverableKeyException(ioe.getMessage()); 339 } 340 } 341} 342