Path: blob/master/src/java.base/share/classes/sun/security/provider/KeyProtector.java
41159 views
/*1* Copyright (c) 1997, 2021, 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.security.Key;29import java.security.KeyStoreException;30import java.security.MessageDigest;31import java.security.NoSuchAlgorithmException;32import java.security.SecureRandom;33import java.security.UnrecoverableKeyException;34import java.util.*;3536import sun.security.pkcs.PKCS8Key;37import sun.security.pkcs.EncryptedPrivateKeyInfo;38import sun.security.x509.AlgorithmId;39import sun.security.util.ObjectIdentifier;40import sun.security.util.KnownOIDs;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// The password used for protecting/recovering keys passed through this109// key protector. We store it as a byte array, so that we can digest it.110private byte[] passwdBytes;111112private MessageDigest md;113114115/**116* Creates an instance of this class, and initializes it with the given117* password.118*/119public KeyProtector(byte[] passwordBytes)120throws NoSuchAlgorithmException121{122if (passwordBytes == null) {123throw new IllegalArgumentException("password can't be null");124}125md = MessageDigest.getInstance(DIGEST_ALG);126this.passwdBytes = passwordBytes;127}128129/*130* Protects the given plaintext key, using the password provided at131* construction time.132*/133public byte[] protect(Key key) throws KeyStoreException134{135int i;136int numRounds;137byte[] digest;138int xorOffset; // offset in xorKey where next digest will be stored139int encrKeyOffset = 0;140141if (key == null) {142throw new IllegalArgumentException("plaintext key can't be null");143}144145if (!"PKCS#8".equalsIgnoreCase(key.getFormat())) {146throw new KeyStoreException(147"Cannot get key bytes, not PKCS#8 encoded");148}149150byte[] plainKey = key.getEncoded();151if (plainKey == null) {152throw new KeyStoreException(153"Cannot get key bytes, encoding not supported");154}155156// Determine the number of digest rounds157numRounds = plainKey.length / DIGEST_LEN;158if ((plainKey.length % DIGEST_LEN) != 0)159numRounds++;160161// Create a random salt162byte[] salt = new byte[SALT_LEN];163SecureRandom random = new SecureRandom();164random.nextBytes(salt);165166// Set up the byte array which will be XORed with "plainKey"167byte[] xorKey = new byte[plainKey.length];168169// Compute the digests, and store them in "xorKey"170for (i = 0, xorOffset = 0, digest = salt;171i < numRounds;172i++, xorOffset += DIGEST_LEN) {173md.update(passwdBytes);174md.update(digest);175digest = md.digest();176md.reset();177// Copy the digest into "xorKey"178if (i < numRounds - 1) {179System.arraycopy(digest, 0, xorKey, xorOffset,180digest.length);181} else {182System.arraycopy(digest, 0, xorKey, xorOffset,183xorKey.length - xorOffset);184}185}186187// XOR "plainKey" with "xorKey", and store the result in "tmpKey"188byte[] tmpKey = new byte[plainKey.length];189for (i = 0; i < tmpKey.length; i++) {190tmpKey[i] = (byte)(plainKey[i] ^ xorKey[i]);191}192193// Store salt and "tmpKey" in "encrKey"194byte[] encrKey = new byte[salt.length + tmpKey.length + DIGEST_LEN];195System.arraycopy(salt, 0, encrKey, encrKeyOffset, salt.length);196encrKeyOffset += salt.length;197System.arraycopy(tmpKey, 0, encrKey, encrKeyOffset, tmpKey.length);198encrKeyOffset += tmpKey.length;199200// Append digest(password, plainKey) as an integrity check to "encrKey"201md.update(passwdBytes);202Arrays.fill(passwdBytes, (byte)0x00);203passwdBytes = null;204md.update(plainKey);205digest = md.digest();206md.reset();207System.arraycopy(digest, 0, encrKey, encrKeyOffset, digest.length);208Arrays.fill(plainKey, (byte)0);209210// wrap the protected private key in a PKCS#8-style211// EncryptedPrivateKeyInfo, and returns its encoding212AlgorithmId encrAlg;213try {214encrAlg = new AlgorithmId(ObjectIdentifier.of215(KnownOIDs.JAVASOFT_JDKKeyProtector));216return new EncryptedPrivateKeyInfo(encrAlg,encrKey).getEncoded();217} catch (IOException ioe) {218throw new KeyStoreException(ioe.getMessage());219}220}221222/*223* Recovers the plaintext version of the given key (in protected format),224* using the password provided at construction time.225*/226public Key recover(EncryptedPrivateKeyInfo encrInfo)227throws UnrecoverableKeyException228{229int i;230byte[] digest;231int numRounds;232int xorOffset; // offset in xorKey where next digest will be stored233int encrKeyLen; // the length of the encrpyted key234235// do we support the algorithm?236AlgorithmId encrAlg = encrInfo.getAlgorithm();237if (!(encrAlg.getOID().toString().equals238(KnownOIDs.JAVASOFT_JDKKeyProtector.value()))) {239throw new UnrecoverableKeyException("Unsupported key protection "240+ "algorithm");241}242243byte[] protectedKey = encrInfo.getEncryptedData();244245/*246* Get the salt associated with this key (the first SALT_LEN bytes of247* <code>protectedKey</code>)248*/249byte[] salt = new byte[SALT_LEN];250System.arraycopy(protectedKey, 0, salt, 0, SALT_LEN);251252// Determine the number of digest rounds253encrKeyLen = protectedKey.length - SALT_LEN - DIGEST_LEN;254numRounds = encrKeyLen / DIGEST_LEN;255if ((encrKeyLen % DIGEST_LEN) != 0) numRounds++;256257// Get the encrypted key portion and store it in "encrKey"258byte[] encrKey = new byte[encrKeyLen];259System.arraycopy(protectedKey, SALT_LEN, encrKey, 0, encrKeyLen);260261// Set up the byte array which will be XORed with "encrKey"262byte[] xorKey = new byte[encrKey.length];263264// Compute the digests, and store them in "xorKey"265for (i = 0, xorOffset = 0, digest = salt;266i < numRounds;267i++, xorOffset += DIGEST_LEN) {268md.update(passwdBytes);269md.update(digest);270digest = md.digest();271md.reset();272// Copy the digest into "xorKey"273if (i < numRounds - 1) {274System.arraycopy(digest, 0, xorKey, xorOffset,275digest.length);276} else {277System.arraycopy(digest, 0, xorKey, xorOffset,278xorKey.length - xorOffset);279}280}281282// XOR "encrKey" with "xorKey", and store the result in "plainKey"283byte[] plainKey = new byte[encrKey.length];284for (i = 0; i < plainKey.length; i++) {285plainKey[i] = (byte)(encrKey[i] ^ xorKey[i]);286}287288/*289* Check the integrity of the recovered key by concatenating it with290* the password, digesting the concatenation, and comparing the291* result of the digest operation with the digest provided at the end292* of <code>protectedKey</code>. If the two digest values are293* different, throw an exception.294*/295md.update(passwdBytes);296Arrays.fill(passwdBytes, (byte)0x00);297passwdBytes = null;298md.update(plainKey);299digest = md.digest();300md.reset();301for (i = 0; i < digest.length; i++) {302if (digest[i] != protectedKey[SALT_LEN + encrKeyLen + i]) {303throw new UnrecoverableKeyException("Cannot recover key");304}305}306307// The parseKey() method of PKCS8Key parses the key308// algorithm and instantiates the appropriate key factory,309// which in turn parses the key material.310try {311return PKCS8Key.parseKey(plainKey);312} catch (IOException ioe) {313throw new UnrecoverableKeyException(ioe.getMessage());314} finally {315Arrays.fill(plainKey, (byte)0);316}317}318}319320321