Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/provider/KeyProtector.java
38830 views
/*1* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.provider;2627import java.io.IOException;28import java.io.UnsupportedEncodingException;29import java.security.Key;30import java.security.KeyStoreException;31import java.security.MessageDigest;32import java.security.NoSuchAlgorithmException;33import java.security.SecureRandom;34import java.security.UnrecoverableKeyException;35import java.util.*;3637import sun.security.pkcs.PKCS8Key;38import sun.security.pkcs.EncryptedPrivateKeyInfo;39import sun.security.x509.AlgorithmId;40import sun.security.util.ObjectIdentifier;41import sun.security.util.DerValue;4243/**44* This is an implementation of a Sun proprietary, exportable algorithm45* intended for use when protecting (or recovering the cleartext version of)46* sensitive keys.47* This algorithm is not intended as a general purpose cipher.48*49* This is how the algorithm works for key protection:50*51* p - user password52* s - random salt53* X - xor key54* P - to-be-protected key55* Y - protected key56* R - what gets stored in the keystore57*58* Step 1:59* Take the user's password, append a random salt (of fixed size) to it,60* and hash it: d1 = digest(p, s)61* Store d1 in X.62*63* Step 2:64* Take the user's password, append the digest result from the previous step,65* and hash it: dn = digest(p, dn-1).66* Store dn in X (append it to the previously stored digests).67* Repeat this step until the length of X matches the length of the private key68* P.69*70* Step 3:71* XOR X and P, and store the result in Y: Y = X XOR P.72*73* Step 4:74* Store s, Y, and digest(p, P) in the result buffer R:75* R = s + Y + digest(p, P), where "+" denotes concatenation.76* (NOTE: digest(p, P) is stored in the result buffer, so that when the key is77* recovered, we can check if the recovered key indeed matches the original78* key.) R is stored in the keystore.79*80* The protected key is recovered as follows:81*82* Step1 and Step2 are the same as above, except that the salt is not randomly83* generated, but taken from the result R of step 4 (the first length(s)84* bytes).85*86* Step 3 (XOR operation) yields the plaintext key.87*88* Then concatenate the password with the recovered key, and compare with the89* last length(digest(p, P)) bytes of R. If they match, the recovered key is90* indeed the same key as the original key.91*92* @author Jan Luehe93*94*95* @see java.security.KeyStore96* @see JavaKeyStore97* @see KeyTool98*99* @since 1.2100*/101102final class KeyProtector {103104private static final int SALT_LEN = 20; // the salt length105private static final String DIGEST_ALG = "SHA";106private static final int DIGEST_LEN = 20;107108// defined by JavaSoft109private static final String KEY_PROTECTOR_OID = "1.3.6.1.4.1.42.2.17.1.1";110111// The password used for protecting/recovering keys passed through this112// key protector. We store it as a byte array, so that we can digest it.113private byte[] passwdBytes;114115private MessageDigest md;116117118/**119* Creates an instance of this class, and initializes it with the given120* password.121*/122public KeyProtector(byte[] passwordBytes)123throws NoSuchAlgorithmException124{125if (passwordBytes == null) {126throw new IllegalArgumentException("password can't be null");127}128md = MessageDigest.getInstance(DIGEST_ALG);129this.passwdBytes = passwordBytes;130}131132/**133* Ensures that the password bytes of this key protector are134* set to zero when there are no more references to it.135*/136protected void finalize() {137if (passwdBytes != null) {138Arrays.fill(passwdBytes, (byte)0x00);139passwdBytes = null;140}141}142143/*144* Protects the given plaintext key, using the password provided at145* construction time.146*/147public byte[] protect(Key key) throws KeyStoreException148{149int i;150int numRounds;151byte[] digest;152int xorOffset; // offset in xorKey where next digest will be stored153int encrKeyOffset = 0;154155if (key == null) {156throw new IllegalArgumentException("plaintext key can't be null");157}158159if (!"PKCS#8".equalsIgnoreCase(key.getFormat())) {160throw new KeyStoreException(161"Cannot get key bytes, not PKCS#8 encoded");162}163164byte[] plainKey = key.getEncoded();165if (plainKey == null) {166throw new KeyStoreException(167"Cannot get key bytes, encoding not supported");168}169170// Determine the number of digest rounds171numRounds = plainKey.length / DIGEST_LEN;172if ((plainKey.length % DIGEST_LEN) != 0)173numRounds++;174175// Create a random salt176byte[] salt = new byte[SALT_LEN];177SecureRandom random = new SecureRandom();178random.nextBytes(salt);179180// Set up the byte array which will be XORed with "plainKey"181byte[] xorKey = new byte[plainKey.length];182183// Compute the digests, and store them in "xorKey"184for (i = 0, xorOffset = 0, digest = salt;185i < numRounds;186i++, xorOffset += DIGEST_LEN) {187md.update(passwdBytes);188md.update(digest);189digest = md.digest();190md.reset();191// Copy the digest into "xorKey"192if (i < numRounds - 1) {193System.arraycopy(digest, 0, xorKey, xorOffset,194digest.length);195} else {196System.arraycopy(digest, 0, xorKey, xorOffset,197xorKey.length - xorOffset);198}199}200201// XOR "plainKey" with "xorKey", and store the result in "tmpKey"202byte[] tmpKey = new byte[plainKey.length];203for (i = 0; i < tmpKey.length; i++) {204tmpKey[i] = (byte)(plainKey[i] ^ xorKey[i]);205}206207// Store salt and "tmpKey" in "encrKey"208byte[] encrKey = new byte[salt.length + tmpKey.length + DIGEST_LEN];209System.arraycopy(salt, 0, encrKey, encrKeyOffset, salt.length);210encrKeyOffset += salt.length;211System.arraycopy(tmpKey, 0, encrKey, encrKeyOffset, tmpKey.length);212encrKeyOffset += tmpKey.length;213214// Append digest(password, plainKey) as an integrity check to "encrKey"215md.update(passwdBytes);216Arrays.fill(passwdBytes, (byte)0x00);217passwdBytes = null;218md.update(plainKey);219digest = md.digest();220md.reset();221System.arraycopy(digest, 0, encrKey, encrKeyOffset, digest.length);222223// wrap the protected private key in a PKCS#8-style224// EncryptedPrivateKeyInfo, and returns its encoding225AlgorithmId encrAlg;226try {227encrAlg = new AlgorithmId(new ObjectIdentifier(KEY_PROTECTOR_OID));228return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();229} catch (IOException ioe) {230throw new KeyStoreException(ioe.getMessage());231}232}233234/*235* Recovers the plaintext version of the given key (in protected format),236* using the password provided at construction time.237*/238public Key recover(EncryptedPrivateKeyInfo encrInfo)239throws UnrecoverableKeyException240{241int i;242byte[] digest;243int numRounds;244int xorOffset; // offset in xorKey where next digest will be stored245int encrKeyLen; // the length of the encrpyted key246247// do we support the algorithm?248AlgorithmId encrAlg = encrInfo.getAlgorithm();249if (!(encrAlg.getOID().toString().equals(KEY_PROTECTOR_OID))) {250throw new UnrecoverableKeyException("Unsupported key protection "251+ "algorithm");252}253254byte[] protectedKey = encrInfo.getEncryptedData();255256/*257* Get the salt associated with this key (the first SALT_LEN bytes of258* <code>protectedKey</code>)259*/260byte[] salt = new byte[SALT_LEN];261System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);262263// Determine the number of digest rounds264encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;265numRounds = encrKeyLen / DIGEST_LEN;266if ((encrKeyLen % DIGEST_LEN) != 0) numRounds++;267268// Get the encrypted key portion and store it in "encrKey"269byte[] encrKey = new byte[encrKeyLen];270System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);271272// Set up the byte array which will be XORed with "encrKey"273byte[] xorKey = new byte[encrKey.length];274275// Compute the digests, and store them in "xorKey"276for (i = 0, xorOffset = 0, digest = salt;277i < numRounds;278i++, xorOffset += DIGEST_LEN) {279md.update(passwdBytes);280md.update(digest);281digest = md.digest();282md.reset();283// Copy the digest into "xorKey"284if (i < numRounds - 1) {285System.arraycopy(digest, 0, xorKey, xorOffset,286digest.length);287} else {288System.arraycopy(digest, 0, xorKey, xorOffset,289xorKey.length - xorOffset);290}291}292293// XOR "encrKey" with "xorKey", and store the result in "plainKey"294byte[] plainKey = new byte[encrKey.length];295for (i = 0; i < plainKey.length; i++) {296plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);297}298299/*300* Check the integrity of the recovered key by concatenating it with301* the password, digesting the concatenation, and comparing the302* result of the digest operation with the digest provided at the end303* of <code>protectedKey</code>. If the two digest values are304* different, throw an exception.305*/306md.update(passwdBytes);307Arrays.fill(passwdBytes, (byte)0x00);308passwdBytes = null;309md.update(plainKey);310digest = md.digest();311md.reset();312for (i = 0; i < digest.length; i++) {313if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {314throw new UnrecoverableKeyException("Cannot recover key");315}316}317318// The parseKey() method of PKCS8Key parses the key319// algorithm and instantiates the appropriate key factory,320// which in turn parses the key material.321try {322return PKCS8Key.parseKey(new DerValue(plainKey));323} catch (IOException ioe) {324throw new UnrecoverableKeyException(ioe.getMessage());325}326}327}328329330