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