Search code examples
objective-ciosrsacommoncrypto

How can I use a PKCS8 RSA DER Private Key in iOS?


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?


Solution

  • 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);
    }