3

At run time, my iOS application receives a file with a public-private RSA key-pair, generated by someone else's Java:

KeyPairGenerator keygenerator;
keygenerator = KeyPairGenerator.getInstance("RSA");
keygenerator.initialize(4096);

KeyPair keypair = keygenerator.generateKeyPair();
PrivateKey privateKey = keypair.getPrivate().getEncoded();
PublicKey publicKey = keypair.getPublic().getEncoded();

I have successfully read and used the public key, using this method, which strips some preamble from the key.

I now want to use the private key. The same method doesn't work, I assumed the preamble is different somehow. The blog suggested that it was importing PKCS#1 PEM keys, but then says they're binary, so I think they just mean Base64-encoded DER keys. I also found that maybe the keys I have are PKCS#8 encoded instead.

Certainly I can use

openssl pkcs8 -nocrypt -inform der < pk8.der > pvt.pem

on a sample private key and openssl doesn't complain.

Would it make sense that the public key was PKCS#1 and the private PKCS#8?

But I really would like to use CommonCrypto and the Security Framework rather than linking against OpenSSL if I possibly can. On Mac OS there are functions in libsecurity to read PKCS#8, but this hasn't made it to iOS yet. I did, honestly, try reading the source but I can't work out where they actually strip the key.

[TL;DR] How can I strip the version and algorithm PKCS#8 fields from the DER private key, and just get the plain key, using either CommonCrypto or some C/C++/ObjC?

Community
  • 1
  • 1
Cathy
  • 515
  • 7
  • 23

2 Answers2

1

I couldn't solve the problem without OpenSSL. So here is a solution that does use OpenSSL.

Assuming you have a NSData called privateKey with the key, and another called signableData which you want to sign.

#import <openssl/x509.h>
#import <openssl/pem.h>

NSURL *cacheDir = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
NSString *infile = [[cacheDir URLByAppendingPathComponent:@"privkey.der"] path];

NSError *error;
[privateKey writeToFile:infile options:NSDataWritingFileProtectionComplete error:&error];
if (error) {
    NSLog(@"%@", error);
} else {
    BIO *in = BIO_new_file([infile cStringUsingEncoding:NSUTF8StringEncoding], "rb");
    PKCS8_PRIV_KEY_INFO *p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(in, NULL);
    NSLog(@"%i", p8inf->broken);
    EVP_PKEY *pkey = EVP_PKCS82PKEY(p8inf);
    PKCS8_PRIV_KEY_INFO_free(p8inf);
    BIO_free(in);

    uint8_t * cipherBuffer = NULL;

    // Calculate the buffer sizes.
    unsigned int cipherBufferSize = RSA_size(pkey->pkey.rsa);
    unsigned int signatureLength;

    // Allocate some buffer space. I don't trust calloc.
    cipherBuffer = malloc(cipherBufferSize);
    memset((void *)cipherBuffer, 0x0, cipherBufferSize);

    unsigned char *openSSLHash = SHA1(signableData.bytes, signableData.length, NULL);
    int success = RSA_sign(NID_sha1, openSSLHash, 20, cipherBuffer, &signatureLength, pkey->pkey.rsa);
    if (success) NSLog(@"WIN");


    NSData *signed = [NSData dataWithBytes:(const void*)cipherBuffer length:signatureLength];    

    EVP_PKEY_free(pkey);
}
Cathy
  • 515
  • 7
  • 23
  • It's not working for me. I have replaced 1st two lines with NSString *path = [[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"pem"]; NSString *infile = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; and I am always getting error it's not going in else part. If I removed if and else then it's giving me error on "BIO *in". – Nitesh Meshram Jun 26 '13 at 13:06
  • @NiteshMeshram Are you trying to write the key to the bundle? You can't write files there. The `infile` should be the *path* (as a NSString) to the key file, not the contents of the file. In your version, replace `infile` on the line starting with `BIO *in` with `path`, and remove all my code above it (except the imports), keeping your `NSString *path = [[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"pem"];`. – Cathy Jun 26 '13 at 22:47
  • No I am having .pem file and I just want to read that. any suggestions I have tried both and read/write but it's not working. Basically I want to read only... – Nitesh Meshram Jun 27 '13 at 05:40
  • @NiteshMeshram If your key is .pem format, it won't work. You need to base-64 encode it to make it .der format. OpenSSL (command line) can do that `openssl x509 -in cert.crt -outform der -out cert.der`. Then just follow what I said above: replace `infile` in the line staring with `BIO *in...` with an `NSString` that contains the path to (not the contents!) of your key file. I.E. `NSString *path = [[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"der"];` – Cathy Jun 27 '13 at 05:53
  • Ok I have modified that .pem and used .der but still I an getting in " BIO *in" this variable. Code - NSString *path = [[NSBundle mainBundle] pathForResource:@"newpvt" ofType:@"der"]; NSString *infile = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; BIO *in = BIO_new_file([infile cStringUsingEncoding:NSUTF8StringEncoding], "rb"); PKCS8_PRIV_KEY_INFO *p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(in, NULL); – Nitesh Meshram Jun 27 '13 at 06:19
  • @NiteshMeshram You're reading the file, and then trying to open a file *called* the random bytes of your key. In my answer I *write* the file, because I have it as NSData. You just need do `BIO *in = BIO_new_file([[[NSBundle mainBundle] pathForResource:@"keyNew" ofType:@"der"] cStringUsingEncoding:NSUTF8StringEncoding], "rb");` and get rid of the `NSString *infile = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];` which is opening and reading the file. – Cathy Jun 27 '13 at 23:02
1

You can see how a der key looks in ASN1 structure in this webpage: https://lapo.it/asn1js/

Here is a code from the SwCrypt library, which strips the PKCS8 header from a private key. This is Swift, but you can rewrite it any other language easily.

    static private func stripHeaderIfAny(keyData: NSData) throws -> NSData {
        var bytes = keyData.arrayOfBytes()

        var offset = 0
        guard bytes[offset] == 0x30 else {
            throw SwError.ASN1Parse
        }
        offset += 1

        if bytes[offset] > 0x80 {
            offset += Int(bytes[offset]) - 0x80
        }
        offset += 1

        guard bytes[offset] == 0x02 else {
            throw SwError.ASN1Parse
        }
        offset += 3

        //without PKCS8 header
        if bytes[offset] == 0x02 {
            return keyData
        }

        let OID: [UInt8] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
                            0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]
        let slice: [UInt8] = Array(bytes[offset..<(offset + OID.count)])

        guard slice == OID else {
            throw SwError.ASN1Parse
        }

        offset += OID.count
        guard bytes[offset] == 0x04 else {
            throw SwError.ASN1Parse
        }

        offset += 1
        if bytes[offset] > 0x80 {
            offset += Int(bytes[offset]) - 0x80
        }
        offset += 1

        guard bytes[offset] == 0x30 else {
            throw SwError.ASN1Parse
        }

        return keyData.subdataWithRange(NSRange(location: offset, length: keyData.length - offset))
    }
soyer
  • 316
  • 2
  • 10