views:

1550

answers:

2

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"); }
    }
+1  A: 

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

  • length<=0x7F: XX, where XX is the length
  • 0x80<=length<=0xFF: 81XX, where XX is the length
  • 0x0100<=length<=0xFFFF: 82XXXX, where XXXX is the length
  • 0x010000<=length<=0xFFFFFF: 83XXXXXX, 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.

Rasmus Faber
Given that I am stuck with this, even if incorrect, any thoughts on how to key a legitimate Key object from this data, since I can't use a PKCS8EncodedKeySpec?
Software Monkey
I just added some information about how to fix the data. You might be lucky enough that you can insert 04820261 and replace the start with 30820277, but all the encodings would have to be exactly the same size.
Rasmus Faber
@Rasmus: I think you meant to say 0x100<=length<=0xFFFF and 0x10000<=length<=0xFFFFFF; if so would you edit your answer - I would do the edit if I were sure.
Software Monkey
I actually made that change about an hour ago :-)
Rasmus Faber
Cryptix had some ASN.1 utilities, I used them once to parse a PKCS#8 password, a long time ago...
Chochos
A: 

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...

Chochos