У меня есть зашифрованный PKCS # 5 закрытый ключ PKCS # 8 RSA, который хранится в файле на диске (первоначально создан SSLPlus, около 1997 г.), например:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICmDAaBgkqhkiG9w0BBQMwDQQIybM2XFqx4EwCAQUEggJ4MKg/NE+L6NJgbOf4
...
8QnGu4R7lFlweH/VAK8n0L75h3q2g62MKLJqmKLtAILNve4zymnO+LVZ4Js=
-----END ENCRYPTED PRIVATE KEY-----
Для которого мне нужно получить объект Java Key, который я затем могу добавить вместе с соответствующим сертификатом в KeyStore. Закрытый ключ шифруется двоичным ключом длиной 100 байт.
Создание объекта Certificate было простым, но я не могу понять, как перейти от указанного выше закодированного в Base64 ключа PKCS # 5 к расшифрованному секретному ключу PKCS # 8 RSA. На данный момент я зашел в тупик, потому что вызов SecretKeyFactory.generateSecret () не удается с:
InvalidKeySpecException: Password is not ASCII
Теперь верно, что пароль не является ASCII, в самом строгом смысле, от 0x00 до 0x7F, но алгоритм PBEWithMD5AndDES должен принимать символьные значения от 0x00 до 0xFF.
Может кто-нибудь показать мне, как получить из закодированного в Base64 значения объект Key, который я могу добавить в хранилище ключей?
Заключение
PBEKey, выпущенный с Java, принимает пароль со значениями ASCII только в диапазоне 0x20 <= char <= 0x7E. Эта проблема с моим паролем, отличным от ASCII, была решена путем создания моего собственного BinaryPBEKey, который допускал значения байтов от 0x00 до 0xFF (см. Ниже). </p>
Следующая проблема, с которой я столкнулся, заключалась в том, что мои данные PKCS # 8 не были должным образом закодированы (по-видимому, распространенная ошибка в ранних реализациях SSL), поскольку данные PKCS # 1 необходимо было обернуть в строку октета ASN.1. Я написал простую процедуру исправления, которая будет работать с моими ключами, длина которых, как известно, составляет от 512 до 4096 бит (см. Ниже).
Декодер с секретным ключом
private PrivateKey readPrivateKey(File inpfil) throws IOException, GeneralSecurityException {
String[] pbeb64s; // PBE ASN.1 data base-64 encoded
byte[] pbedta; // PBE ASN.1 data in bytes
EncryptedPrivateKeyInfo pbeinf; // PBE key info
PBEParameterSpec pbeprm; // PBE parameters
Cipher pbecph; // PBE decryption cipher
byte[] pk8dta; // PKCS#8 ASN.1 data in bytes
KeyFactory pk8fac=KeyFactory.getInstance("RSA"); // PKCS#8 key factory for decoding private key from ASN.1 data.
pbeb64s=readDataBlocks(inpfil,"ENCRYPTED PRIVATE KEY");
if(pbeb64s.length!=1) { throw new GeneralSecurityException("The keystore '"+inpfil+"' contains multiple private keys"); }
pbedta=base64.decode(pbeb64s[0]);
log.diagln(" - Read private key data");
pbeinf=new EncryptedPrivateKeyInfo(pbedta);
pbeprm=(PBEParameterSpec)pbeinf.getAlgParameters().getParameterSpec(PBEParameterSpec.class);
pbecph=Cipher.getInstance(pbeinf.getAlgName());
pbecph.init(Cipher.DECRYPT_MODE,pbeDecryptKey,pbeprm);
pk8dta=pbecph.doFinal(pbeinf.getEncryptedData());
log.diagln(" - Private Key: Algorithm= "+pbeinf.getAlgName()+", Iterations: "+pbeprm.getIterationCount()+", Salt: "+Base16.toString(pbeprm.getSalt()));
pk8dta=patchKeyData(inpfil,pk8dta);
return pk8fac.generatePrivate(new PKCS8EncodedKeySpec(pk8dta));
}
BinaryPBEKey
import java.io.*;
import java.security.*;
import java.security.spec.*;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
class BinaryPBEKey
extends Object
implements SecretKey
{
private final byte[] key;
/**
* Creates a PBE key from a given binary key.
*
* @param key The key.
*/
BinaryPBEKey(byte[] key) throws InvalidKeySpecException {
if(key==null) { this.key=new byte[0]; }
else { this.key=(byte[])key.clone(); }
Arrays.fill(key,(byte)0);
}
public byte[] getEncoded() {
return (byte[])key.clone();
}
public String getAlgorithm() {
return "PBEWithMD5AndDES";
}
public String getFormat() {
return "RAW";
}
/**
* Calculates a hash code value for the object.
* Objects that are equal will also have the same hashcode.
*/
public int hashCode() {
int ret=0;
for(int xa=1; xa<this.key.length; xa++) { ret+=(this.key[xa]*xa); }
return (ret^=getAlgorithm().toLowerCase().hashCode());
}
public boolean equals(Object obj) {
if(obj==this ) { return true; }
if(obj.getClass()!=getClass()) { return false; }
BinaryPBEKey oth=(BinaryPBEKey)obj;
if(!(oth.getAlgorithm().equalsIgnoreCase(getAlgorithm()))) {
return false;
}
byte[] othkey=oth.getEncoded();
boolean ret =Arrays.equals(key,othkey);
Arrays.fill(othkey,(byte)0);
return ret;
}
public void destroy() {
Arrays.fill(this.key,(byte)0);
}
/**
* Ensure that the password bytes of this key are zeroed out when there are no more references to it.
*/
protected void finalize() throws Throwable {
try { destroy(); } finally { super.finalize(); }
}
PKCS # 8 Patching
<code>/**
* Patch the private key ASN.1 data to conform to PKCS#8.
* <p>
* The SSLPlus private key is not properly encoded PKCS#8 - the PKCS#1 RSAPrivateKey should have been wrapped
* inside an OctetString, thus:
* <pre>
* SSLPlus Encoding:
* 0 30 627: SEQUENCE {
* 4 02 1: INTEGER 0
* 7 30 13: SEQUENCE {
* 9 06 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
* 20 05 0: NULL
* : }
* 22 30 605: SEQUENCE {
* 26 02 1: INTEGER 0
* 29 02 129: INTEGER
* : 00 CA 72 B8 D1 B8 8E B9 39 C0 92 C1 4C 53 B4 F4
* ...
*
* PKCS#8 Encoding
* 0 30 631: SEQUENCE {
* 4 02 1: INTEGER 0
* 7 30 13: SEQUENCE {
* 9 06 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
* 20 05 0: NULL
* : }
* ==> 22 04 609: OCTET STRING, encapsulates {
* 26 30 605: SEQUENCE {
* 30 02 1: INTEGER 0
* 33 02 129: INTEGER
* : 00 CA 72 B8 D1 B8 8E B9 39 C0 92 C1 4C 53 B4 F4
* ...
*
*
* Шестнадцатеричные дампы (клавиша 1K, пробел для ясности):
* До: 30 820271 020100300D06092A864886F70D0101010500 30 82025B ... A228
* После: 30 820275 020100300D06092A864886F70D0101010500 04 82025F 30 82025B ... A228
* ^^^^^^ ^^^^^^
* Добавьте 4 для более поздней версии 0482xxxx Исходная сумма + 4 - 22 (равна длине ключа 025B + 4)
* /
private byte [] patchKeyData (файл inpfil, byte [] asndta) выдает IOException, GeneralSecurityException {// за исключением того, что действительно не выдает исключение
ByteArrayOutputStream patdta = new ByteArrayOutputStream ();
int orglen = decodeAsnLength (inpfil, asndta, 1);
patdta.write (asndta, 0,1); // оригинальный тип лидера
patdta.write (encodeAsnLength (inpfil, (orglen + 4))); // новая общая длина
patdta.write (asndta, 4, (22-4)); // бит между общей длиной, куда нужно вставить обертку строки октета
patdta.write (0x04); // тип строки октета
patdta.write (encodeAsnLength (inpfil, (orglen + 4-22))); // длина строки октета (тип данных ключа + длина данных ключа + данные ключа)
patdta.write (asndta, 22, asndta.length-22); // закрытый ключ данных
return patdta.toByteArray ();
}
private int decodeAsnLength (файл inpfil, byte [] asndta, int ofs) выдает GeneralSecurityException {
if ((asndta [ofs] & 0xFF) == 0x82) {return (((asndta [ofs + 1] & 0x000000FF) << 8) | ((asndta [ofs + 2] & 0x000000FF))); }
else {throw new GeneralSecurityException («Закрытый ключ в файле« + inpfil + »не поддерживается (ID =" + Base16.toString (asndta, 0,4) + ")"); }
}
закрытый байт [] encodeAsnLength (файл inpfil, int len) создает исключение GeneralSecurityException {
if (len> = 0x0100 && len <= 0xFFFF) {вернуть новый байт [] {(byte) 0x82, (byte) ((len >>> 8) & 0x000000FF), (byte) len}; }else {throw new GeneralSecurityException («Новая длина« + len + »для исправления закрытого ключа в файле« + inpfil + »выходит за пределы диапазона»); }
}