001: /*
002: * @(#)KeyProtector.java 1.23 06/10/10
003: *
004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: *
026: */
027:
028: package sun.security.provider;
029:
030: import java.io.IOException;
031: import java.io.UnsupportedEncodingException;
032: import java.security.Key;
033: import java.security.KeyStoreException;
034: import java.security.MessageDigest;
035: import java.security.NoSuchAlgorithmException;
036: import java.security.SecureRandom;
037: import java.security.UnrecoverableKeyException;
038: import java.util.*;
039:
040: import sun.security.pkcs.PKCS8Key;
041: import sun.security.pkcs.EncryptedPrivateKeyInfo;
042: import sun.security.x509.AlgorithmId;
043: import sun.security.util.ObjectIdentifier;
044: import sun.security.util.DerValue;
045:
046: /**
047: * This is an implementation of a Sun proprietary, exportable algorithm
048: * intended for use when protecting (or recovering the cleartext version of)
049: * sensitive keys.
050: * This algorithm is not intended as a general purpose cipher.
051: *
052: * This is how the algorithm works for key protection:
053: *
054: * p - user password
055: * s - random salt
056: * X - xor key
057: * P - to-be-protected key
058: * Y - protected key
059: * R - what gets stored in the keystore
060: *
061: * Step 1:
062: * Take the user's password, append a random salt (of fixed size) to it,
063: * and hash it: d1 = digest(p, s)
064: * Store d1 in X.
065: *
066: * Step 2:
067: * Take the user's password, append the digest result from the previous step,
068: * and hash it: dn = digest(p, dn-1).
069: * Store dn in X (append it to the previously stored digests).
070: * Repeat this step until the length of X matches the length of the private key
071: * P.
072: *
073: * Step 3:
074: * XOR X and P, and store the result in Y: Y = X XOR P.
075: *
076: * Step 4:
077: * Store s, Y, and digest(p, P) in the result buffer R:
078: * R = s + Y + digest(p, P), where "+" denotes concatenation.
079: * (NOTE: digest(p, P) is stored in the result buffer, so that when the key is
080: * recovered, we can check if the recovered key indeed matches the original
081: * key.) R is stored in the keystore.
082: *
083: * The protected key is recovered as follows:
084: *
085: * Step1 and Step2 are the same as above, except that the salt is not randomly
086: * generated, but taken from the result R of step 4 (the first length(s)
087: * bytes).
088: *
089: * Step 3 (XOR operation) yields the plaintext key.
090: *
091: * Then concatenate the password with the recovered key, and compare with the
092: * last length(digest(p, P)) bytes of R. If they match, the recovered key is
093: * indeed the same key as the original key.
094: *
095: * @author Jan Luehe
096: *
097: * @version 1.23, 10/10/06
098: *
099: * @see java.security.KeyStore
100: * @see JavaKeyStore
101: * @see KeyTool
102: *
103: * @since JDK1.2
104: */
105:
106: final class KeyProtector {
107:
108: private static final int SALT_LEN = 20; // the salt length
109: private static final String DIGEST_ALG = "SHA";
110: private static final int DIGEST_LEN = 20;
111:
112: // defined by JavaSoft
113: private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1";
114:
115: // The password used for protecting/recovering keys passed through this
116: // key protector. We store it as a byte array, so that we can digest it.
117: private byte[] passwdBytes;
118:
119: private MessageDigest md;
120:
121: /**
122: * Creates an instance of this class, and initializes it with the given
123: * password.
124: *
125: * <p>The password is expected to be in printable ASCII.
126: * Normal rules for good password selection apply: at least
127: * seven characters, mixed case, with punctuation encouraged.
128: * Phrases or words which are easily guessed, for example by
129: * being found in dictionaries, are bad.
130: */
131: public KeyProtector(char[] password)
132: throws NoSuchAlgorithmException {
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: }
146:
147: /**
148: * Ensures that the password bytes of this key protector are
149: * set to zero when there are no more references to it.
150: */
151: protected void finalize() {
152: if (passwdBytes != null) {
153: Arrays.fill(passwdBytes, (byte) 0x00);
154: passwdBytes = null;
155: }
156: }
157:
158: /*
159: * Protects the given plaintext key, using the password provided at
160: * construction time.
161: */
162: public byte[] protect(Key key) throws KeyStoreException {
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(
171: "plaintext key can't be null");
172: }
173: byte[] plainKey = key.getEncoded();
174:
175: // Determine the number of digest rounds
176: numRounds = plainKey.length / DIGEST_LEN;
177: if ((plainKey.length % DIGEST_LEN) != 0)
178: numRounds++;
179:
180: // Create a random salt
181: byte[] salt = new byte[SALT_LEN];
182: SecureRandom random = new SecureRandom();
183: random.nextBytes(salt);
184:
185: // Set up the byte array which will be XORed with "plainKey"
186: byte[] xorKey = new byte[plainKey.length];
187:
188: // Compute the digests, and store them in "xorKey"
189: for (i = 0, xorOffset = 0, digest = salt; i < numRounds; i++, xorOffset += DIGEST_LEN) {
190: md.update(passwdBytes);
191: md.update(digest);
192: digest = md.digest();
193: md.reset();
194: // Copy the digest into "xorKey"
195: if (i < numRounds - 1) {
196: System.arraycopy(digest, 0, xorKey, xorOffset,
197: digest.length);
198: } else {
199: System.arraycopy(digest, 0, xorKey, xorOffset,
200: xorKey.length - xorOffset);
201: }
202: }
203:
204: // XOR "plainKey" with "xorKey", and store the result in "tmpKey"
205: byte[] tmpKey = new byte[plainKey.length];
206: for (i = 0; i < tmpKey.length; i++) {
207: tmpKey[i] = (byte) (plainKey[i] ^ xorKey[i]);
208: }
209:
210: // Store salt and "tmpKey" in "encrKey"
211: byte[] encrKey = new byte[salt.length + tmpKey.length
212: + DIGEST_LEN];
213: System.arraycopy(salt, 0, encrKey, encrKeyOffset, salt.length);
214: encrKeyOffset += salt.length;
215: System.arraycopy(tmpKey, 0, encrKey, encrKeyOffset,
216: tmpKey.length);
217: encrKeyOffset += tmpKey.length;
218:
219: // Append digest(password, plainKey) as an integrity check to "encrKey"
220: md.update(passwdBytes);
221: Arrays.fill(passwdBytes, (byte) 0x00);
222: passwdBytes = null;
223: md.update(plainKey);
224: digest = md.digest();
225: md.reset();
226: System.arraycopy(digest, 0, encrKey, encrKeyOffset,
227: digest.length);
228:
229: // wrap the protected private key in a PKCS#8-style
230: // EncryptedPrivateKeyInfo, and returns its encoding
231: AlgorithmId encrAlg;
232: try {
233: encrAlg = new AlgorithmId(new ObjectIdentifier(
234: KEY_PROTECTOR_OID));
235: return new EncryptedPrivateKeyInfo(encrAlg, encrKey)
236: .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: int i;
249: byte[] digest;
250: int numRounds;
251: int xorOffset; // offset in xorKey where next digest will be stored
252: int encrKeyLen; // the length of the encrpyted key
253:
254: // do we support the algorithm?
255: AlgorithmId encrAlg = encrInfo.getAlgorithm();
256: if (!(encrAlg.getOID().toString().equals(KEY_PROTECTOR_OID))) {
257: throw new UnrecoverableKeyException(
258: "Unsupported key protection " + "algorithm");
259: }
260:
261: byte[] protectedKey = encrInfo.getEncryptedData();
262:
263: /*
264: * Get the salt associated with this key (the first SALT_LEN bytes of
265: * <code>protectedKey</code>)
266: */
267: byte[] salt = new byte[SALT_LEN];
268: System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);
269:
270: // Determine the number of digest rounds
271: encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;
272: numRounds = encrKeyLen / DIGEST_LEN;
273: if ((encrKeyLen % DIGEST_LEN) != 0)
274: numRounds++;
275:
276: // Get the encrypted key portion and store it in "encrKey"
277: byte[] encrKey = new byte[encrKeyLen];
278: System
279: .arraycopy(protectedKey, SALT_LEN, encrKey, 0,
280: encrKeyLen);
281:
282: // Set up the byte array which will be XORed with "encrKey"
283: byte[] xorKey = new byte[encrKey.length];
284:
285: // Compute the digests, and store them in "xorKey"
286: for (i = 0, xorOffset = 0, digest = salt; i < numRounds; 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(
323: "Cannot recover key");
324: }
325: }
326:
327: // The parseKey() method of PKCS8Key parses the key
328: // algorithm and instantiates the appropriate key factory,
329: // which in turn parses the key material.
330: try {
331: return PKCS8Key.parseKey(new DerValue(plainKey));
332: } catch (IOException ioe) {
333: throw new UnrecoverableKeyException(ioe.getMessage());
334: }
335: }
336: }
|