Reputation: 64026
I have a PKCS#5 encrypted PKCS#8 RSA private key stored in a disk file (originally generated by SSLPlus, circa 1997), for example:
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICmDAaBgkqhkiG9w0BBQMwDQQIybM2XFqx4EwCAQUEggJ4MKg/NE+L6NJgbOf4
...
8QnGu4R7lFlweH/VAK8n0L75h3q2g62MKLJqmKLtAILNve4zymnO+LVZ4Js=
-----END ENCRYPTED PRIVATE KEY-----
For which I need to obtain a Java Key object which I can then add along with the matching cert to a KeyStore. The private key is encrypted with a 100 byte binary key.
The creation of a Certificate object was simple, but I can't seem to figure out how to go from the above Base64 encoded PKCS#5 key to the decrypted PKCS#8 RSA private key. At this point I am stymied because the SecretKeyFactory.generateSecret() call fails with:
InvalidKeySpecException: Password is not ASCII
Now, it's true the password is not ASCII, in the strictest sense of being 0x00 to 0x7F, but the PBEWithMD5AndDES algorithm should accept character values from 0x00 to 0xFF.
Can anyone show me how to get from the Base64 encoded value to a Key object I can add to a keystore?
Conclusion
The PBEKey issued with Java accepts a password with ASCII values in the range 0x20<=char<=0x7E only. This problem with my non-ASCII password was resolved by making my own BinaryPBEKey which allowed byte values from 0x00 to 0xFF (see below).
The subsequent problem I had was that my PKCS#8 data was not properly encoded (apparently a common mistake with early implementations of SSL), in that the PKCS#1 data needed to be wrapped in an ASN.1 octet string. I wrote a simple patching routine that will deal with my keys, which are known to be between 512 and 4096 bits in length (see below).
Private Key Decoder
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
/**
* 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
* ...
* </pre>
*
* Hex Dumps (1K key, space padded for clarity):
* Before : 30 820271 020100300D06092A864886F70D0101010500 30 82025B ... A228
* After : 30 820275 020100300D06092A864886F70D0101010500 04 82025F 30 82025B ... A228
* ^^^^^^ ^^^^^^
* Add 4 for later 0482xxxx Original total + 4 - 22 (equals the key length of 025B+4)
*/
private byte[] patchKeyData(File inpfil, byte[] asndta) throws IOException, GeneralSecurityException { // except it really doesn't throw an exception
ByteArrayOutputStream patdta=new ByteArrayOutputStream();
int orglen=decodeAsnLength(inpfil,asndta,1);
patdta.write(asndta,0,1); // original leader type
patdta.write(encodeAsnLength(inpfil,(orglen+4))); // new total length
patdta.write(asndta,4,(22-4)); // bit between total length an where octet-string wrapper needs to be inserted
patdta.write(0x04); // octet-string type
patdta.write(encodeAsnLength(inpfil,(orglen+4-22))); // octet-string length (key data type+key data length+key data)
patdta.write(asndta,22,asndta.length-22); // private key data
return patdta.toByteArray();
}
private int decodeAsnLength(File inpfil, byte[] asndta, int ofs) throws GeneralSecurityException {
if((asndta[ofs]&0xFF)==0x82) { return (((asndta[ofs+1]&0x000000FF)<< 8)|((asndta[ofs+2]&0x000000FF))); }
else { throw new GeneralSecurityException("The private key in file '"+inpfil+"' is not supported (ID="+Base16.toString(asndta,0,4)+")"); }
}
private byte[] encodeAsnLength(File inpfil, int len) throws GeneralSecurityException {
if(len>=0x0100 && len<=0xFFFF) { return new byte[]{ (byte)0x82,(byte)((len>>>8)&0x000000FF),(byte)len }; }
else { throw new GeneralSecurityException("The new length of "+len+" for patching the private key in file '"+inpfil+"' is out of range"); }
}
Upvotes: 2
Views: 8407
Reputation: 5159
Have you tried opening the key with some of the Bouncy Castle internal classes? Maybe by using them directly instead of just defining BC as a crypto provider you can parse that file...
Upvotes: 0
Reputation: 49629
I just dumped your decrypted data into an ASN.1 parser, and it looks like perfectly fine ASN.1 to me:
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
: 38 48 3F C3 1C DC 6B BC BE 26 A3 B2 F7 7C 60 A8
: 2C 0D 86 ED FC 2D D2 5C 99 B6 B6 71 A8 6D 2F 51
: 25 FA 9C 42 FE 10 C1 2F 39 EA E8 FF 1A 78 BA 6B
: 64 B8 39 34 3B F4 1C 45 06 C3 B9 98 DC 01 FF 41
: 56 36 4F DD 35 69 A4 27 BB 5F FD DD 5C 73 BA 9A
: 94 5A 4F 37 A9 48 3D 5B 89 EA EE BA 8D 02 6E D7
: 6E D4 6F BC 7D 7A A4 41 4C 4D CA 08 05 20 66 A3
: [ Another 1 bytes skipped ]
161 02 3: INTEGER 65537
166 02 128: INTEGER
: 21 6A E2 7B 2B DD D3 51 67 2A 52 62 09 07 3B B0
: F6 AC 1F C6 E9 D3 96 EA 44 72 8D 1E 31 17 BB 6A
: DA 28 C5 AB F4 DC 5E 90 B9 0A 50 A4 9E B1 4A D1
: DC 16 63 30 91 0F 72 7E 3A FA 8E F1 8D B0 27 FD
: C2 BA B5 F8 FC 7C 46 C0 FD AD A7 39 7C 36 71 7A
: 33 8B AD 0D 0C DA 50 B7 0E BF D8 64 7D 44 BD 64
: 6F E2 51 B7 5E 2D 7B BA 02 DB A6 2F 20 88 66 98
: 85 34 2E EF D4 29 61 23 79 87 27 27 55 15 8D 21
297 02 65: INTEGER
: 00 F9 62 BD 22 4A C8 56 7A C3 17 EB CE CC 5F 42
: E1 40 F5 A5 66 60 32 54 86 67 26 AD 7C 34 C2 FE
: FE 8A F7 7F BE 79 53 5F C9 73 D9 47 8B 0F 89 A1
: 09 F1 27 16 FC F1 4B C3 A9 27 59 29 0D DA 9C AE
: 53
364 02 65: INTEGER
: 00 CF D1 4A 31 50 9A B4 BA 90 42 25 49 54 7C 20
: 54 2E CF E8 F1 35 DA 92 C2 A3 94 9D B7 B1 85 3F
: 13 D0 CA BC 77 D9 8A F3 32 83 59 93 E1 F0 11 1B
: 4C E5 A2 30 50 FE 1F B6 8D A5 B1 44 DA 4D 4B 11
: 09
431 02 64: INTEGER
: 46 53 3A C4 9D D4 0A D7 09 87 08 5F 43 B0 A5 5A
: 82 08 03 81 70 25 21 42 D9 79 C5 B8 5D E4 93 25
: D2 A8 62 A4 A2 F0 08 F5 F5 2E 53 87 7A 75 34 2D
: 6A 8C BC 65 CD E1 B0 A6 55 CB 45 D1 7B 51 6D B3
497 02 65: INTEGER
: 00 81 CC 61 7F 9D AD 92 F5 F7 86 28 CD BD 43 ED
: D9 46 87 BB 21 75 16 78 95 B3 1F EE C6 3D CD 50
: 91 6A D6 45 92 C1 C0 24 97 C7 2C 5A CE 42 68 1C
: DA 11 8F 14 88 71 C0 92 FF B3 9E 9D B7 8F 91 34
: 29
564 02 65: INTEGER
: 00 88 7A 99 AC AA A9 D5 2B 6E E1 87 0A E8 D2 4C
: 04 8E A2 EA 00 3F 8D AF 9F 76 61 86 B0 1D 18 69
: C8 64 22 D4 6B A3 A4 BB 52 B1 AC 38 DB 6B 5C 28
: F0 78 73 3E 37 FD C8 54 72 C7 FD A9 EB C9 F2 45
: 96
: }
: }
Unfortunately it is not a correctly encoded PKCS#8 PrivateKeyInfo. The Sequence starting at index 22 is a PKCS#1 PKCS1RSAPrivateKey, which should have been wrapped inside an OctetString in order for the structure to be correctly encoded.
Try this instead: 30820277020100300D06092A864886F70D0101010500048202613082025D02010002818100CA72B8D1B88EB939C092C14C53B4F438483FC31CDC6BBCBE26A3B2F77C60A82C0D86EDFC2DD25C99B6B671A86D2F5125FA9C42FE10C12F39EAE8FF1A78BA6B64B839343BF41C4506C3B998DC01FF4156364FDD3569A427BB5FFDDD5C73BA9A945A4F37A9483D5B89EAEEBA8D026ED76ED46FBC7D7AA4414C4DCA08052066A3EB0203010001028180216AE27B2BDDD351672A526209073BB0F6AC1FC6E9D396EA44728D1E3117BB6ADA28C5ABF4DC5E90B90A50A49EB14AD1DC166330910F727E3AFA8EF18DB027FDC2BAB5F8FC7C46C0FDADA7397C36717A338BAD0D0CDA50B70EBFD8647D44BD646FE251B75E2D7BBA02DBA62F2088669885342EEFD42961237987272755158D21024100F962BD224AC8567AC317EBCECC5F42E140F5A566603254866726AD7C34C2FEFE8AF77FBE79535FC973D9478B0F89A109F12716FCF14BC3A92759290DDA9CAE53024100CFD14A31509AB4BA90422549547C20542ECFE8F135DA92C2A3949DB7B1853F13D0CABC77D98AF332835993E1F0111B4CE5A23050FE1FB68DA5B144DA4D4B1109024046533AC49DD40AD70987085F43B0A55A8208038170252142D979C5B85DE49325D2A862A4A2F008F5F52E53877A75342D6A8CBC65CDE1B0A655CB45D17B516DB302410081CC617F9DAD92F5F78628CDBD43EDD94687BB2175167895B31FEEC63DCD50916AD64592C1C02497C72C5ACE42681CDA118F148871C092FFB39E9DB78F913429024100887A99ACAAA9D52B6EE1870AE8D24C048EA2EA003F8DAF9F766186B01D1869C86422D46BA3A4BB52B1AC38DB6B5C28F078733E37FDC85472C7FDA9EBC9F24596
Parsed:
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
: 38 48 3F C3 1C DC 6B BC BE 26 A3 B2 F7 7C 60 A8
: 2C 0D 86 ED FC 2D D2 5C 99 B6 B6 71 A8 6D 2F 51
: 25 FA 9C 42 FE 10 C1 2F 39 EA E8 FF 1A 78 BA 6B
: 64 B8 39 34 3B F4 1C 45 06 C3 B9 98 DC 01 FF 41
: 56 36 4F DD 35 69 A4 27 BB 5F FD DD 5C 73 BA 9A
: 94 5A 4F 37 A9 48 3D 5B 89 EA EE BA 8D 02 6E D7
: 6E D4 6F BC 7D 7A A4 41 4C 4D CA 08 05 20 66 A3
: [ Another 1 bytes skipped ]
165 02 3: INTEGER 65537
170 02 128: INTEGER
: 21 6A E2 7B 2B DD D3 51 67 2A 52 62 09 07 3B B0
: F6 AC 1F C6 E9 D3 96 EA 44 72 8D 1E 31 17 BB 6A
: DA 28 C5 AB F4 DC 5E 90 B9 0A 50 A4 9E B1 4A D1
: DC 16 63 30 91 0F 72 7E 3A FA 8E F1 8D B0 27 FD
: C2 BA B5 F8 FC 7C 46 C0 FD AD A7 39 7C 36 71 7A
: 33 8B AD 0D 0C DA 50 B7 0E BF D8 64 7D 44 BD 64
: 6F E2 51 B7 5E 2D 7B BA 02 DB A6 2F 20 88 66 98
: 85 34 2E EF D4 29 61 23 79 87 27 27 55 15 8D 21
301 02 65: INTEGER
: 00 F9 62 BD 22 4A C8 56 7A C3 17 EB CE CC 5F 42
: E1 40 F5 A5 66 60 32 54 86 67 26 AD 7C 34 C2 FE
: FE 8A F7 7F BE 79 53 5F C9 73 D9 47 8B 0F 89 A1
: 09 F1 27 16 FC F1 4B C3 A9 27 59 29 0D DA 9C AE
: 53
368 02 65: INTEGER
: 00 CF D1 4A 31 50 9A B4 BA 90 42 25 49 54 7C 20
: 54 2E CF E8 F1 35 DA 92 C2 A3 94 9D B7 B1 85 3F
: 13 D0 CA BC 77 D9 8A F3 32 83 59 93 E1 F0 11 1B
: 4C E5 A2 30 50 FE 1F B6 8D A5 B1 44 DA 4D 4B 11
: 09
435 02 64: INTEGER
: 46 53 3A C4 9D D4 0A D7 09 87 08 5F 43 B0 A5 5A
: 82 08 03 81 70 25 21 42 D9 79 C5 B8 5D E4 93 25
: D2 A8 62 A4 A2 F0 08 F5 F5 2E 53 87 7A 75 34 2D
: 6A 8C BC 65 CD E1 B0 A6 55 CB 45 D1 7B 51 6D B3
501 02 65: INTEGER
: 00 81 CC 61 7F 9D AD 92 F5 F7 86 28 CD BD 43 ED
: D9 46 87 BB 21 75 16 78 95 B3 1F EE C6 3D CD 50
: 91 6A D6 45 92 C1 C0 24 97 C7 2C 5A CE 42 68 1C
: DA 11 8F 14 88 71 C0 92 FF B3 9E 9D B7 8F 91 34
: 29
568 02 65: INTEGER
: 00 88 7A 99 AC AA A9 D5 2B 6E E1 87 0A E8 D2 4C
: 04 8E A2 EA 00 3F 8D AF 9F 76 61 86 B0 1D 18 69
: C8 64 22 D4 6B A3 A4 BB 52 B1 AC 38 DB 6B 5C 28
: F0 78 73 3E 37 FD C8 54 72 C7 FD A9 EB C9 F2 45
: 96
: }
: }
: }
To fix your files, you can either use an ASN.1-library (but I am not aware of a good one for Java), or do the following:
Check that your data starts with
30(*1)020100300D06092A864886F70D010101050030(*2)
(*1)
and (*2)
will be length-encodings in one of the following forms
XX
, where XX is the length81XX
, where XX is the length82XXXX
, where XXXX is the length83XXXXXX
, where XXXXXX is the length
etc.If your keys are all of the same length, you can probably assume that the length-encodings always will be on the form 82XXXX
, but the actual lengths will probably vary.
Read the length in (*2)
, add the length in bytes of 30(*2)
to the number (this is probably 4) and encode the length as above (will most probably be the form 82XXXX
). Let us call this length-encoding (*3)
. Insert 04(*3)
right before 30(*2)
. Now add the length of 04(*3)
(probably also 4) to (*1)
and reencode this (can probably still fit in 82XXXX
) and replace (*1)
with this.
I hope that was understandable, otherwise I recommend reading A Layman's Guide to a Subset of ASN.1, BER, and DER.
Upvotes: 2