jdk/src/share/classes/sun/security/provider/KeyProtector.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
equal deleted inserted replaced
0:fd16c54261b3 2:90ce3da70b43
       
     1 /*
       
     2  * Copyright 1997-2006 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 sun.security.provider;
       
    27 
       
    28 import java.io.IOException;
       
    29 import java.io.UnsupportedEncodingException;
       
    30 import java.security.Key;
       
    31 import java.security.KeyStoreException;
       
    32 import java.security.MessageDigest;
       
    33 import java.security.NoSuchAlgorithmException;
       
    34 import java.security.SecureRandom;
       
    35 import java.security.UnrecoverableKeyException;
       
    36 import java.util.*;
       
    37 
       
    38 import sun.security.pkcs.PKCS8Key;
       
    39 import sun.security.pkcs.EncryptedPrivateKeyInfo;
       
    40 import sun.security.x509.AlgorithmId;
       
    41 import sun.security.util.ObjectIdentifier;
       
    42 import 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 
       
   103 final 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 }