I’ve spent the last two days working through this – and couldn’t find any easy code at all on the net. So to save others the time here is what I found.

First off, when you export a key from the iPhone keychain, it’s exported in a cut down format – just the public key and exponent without any of the other ASN.1 stuff you’d expect in a fully encoded public key. The java crypto functions generally expect a fully encoded key (OID and all). So I quickly whipped up some Objective C code to take the exported key and expand it out to a fully interoperable key in the X.509 format.

I’m darn sure there is an easier way to do this, I just couldn’t for the life of me find it. Please let me know what I’ve missed or any bugs/errors/plain sillies in the attached following code

First off a simple function to encode ASN.1 length fields:

// Helper function for ASN.1 encoding

size_t encodeLength(unsigned char * buf, size_t length) {

    // encode length in ASN.1 DER format
    if (length < 128) {
        buf[0] = length;
        return 1;
    }

    size_t i = (length / 256) + 1;
    buf[0] = i + 0x80;
    for (size_t j = 0 ; j < i; ++j) {         buf[i - j] = length & 0xFF;         length = length >> 8;
    }

    return i + 1;
}

OK – with that in place, some code to actually do the encoding. The following is a full copy of a function from something I’m working on. The Base64 class is a very simple encoding/decoding class I built.

- (NSString *) getRSAPublicKeyAsBase64 {

    static const unsigned char _encodedRSAEncryptionOID[15] = {

        /* Sequence of length 0xd made up of OID followed by NULL */
        0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
        0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00

    };

    NSData * publicTag =
        [NSData dataWithBytes:_PWITC_PUBLIC_KEY_TAG
                       length:strlen((const char *) _PWITC_PUBLIC_KEY_TAG)];

    // Now lets extract the public key - build query to get bits
    NSMutableDictionary * queryPublicKey =
        [[NSMutableDictionary alloc] init];

    [queryPublicKey setObject:(id)kSecClassKey
                       forKey:(id)kSecClass];
    [queryPublicKey setObject:publicTag
                       forKey:(id)kSecAttrApplicationTag];
    [queryPublicKey setObject:(id)kSecAttrKeyTypeRSA
                       forKey:(id)kSecAttrKeyType];
    [queryPublicKey setObject:[NSNumber numberWithBool:YES]
                       forKey:(id)kSecReturnData];

    NSData * publicKeyBits;
    OSStatus err =
        SecItemCopyMatching((CFDictionaryRef)queryPublicKey,
                            (CFTypeRef *)&publicKeyBits);

    if (err != noErr) {
        return nil;
    }

    // OK - that gives us the "BITSTRING component of a full DER
    // encoded RSA public key - we now need to build the rest

    unsigned char builder[15];
    NSMutableData * encKey = [[NSMutableData alloc] init];
    int bitstringEncLength;

    // When we get to the bitstring - how will we encode it?
    if  ([publicKeyBits length ] + 1  < 128 )
        bitstringEncLength = 1 ;
    else
        bitstringEncLength = (([publicKeyBits length ] +1 ) / 256 ) + 2 ; 

    // Overall we have a sequence of a certain length
    builder[0] = 0x30;    // ASN.1 encoding representing a SEQUENCE
    // Build up overall size made up of -
    // size of OID + size of bitstring encoding + size of actual key
    size_t i = sizeof(_encodedRSAEncryptionOID) + 2 + bitstringEncLength +
               [publicKeyBits length];
    size_t j = encodeLength(&builder[1], i);
    [encKey appendBytes:builder length:j +1];

    // First part of the sequence is the OID
    [encKey appendBytes:_encodedRSAEncryptionOID
                 length:sizeof(_encodedRSAEncryptionOID)];

    // Now add the bitstring
    builder[0] = 0x03;
    j = encodeLength(&builder[1], [publicKeyBits length] + 1);
    builder[j+1] = 0x00;
    [encKey appendBytes:builder length:j + 2];

    // Now the actual key
    [encKey appendData:publicKeyBits];

    // Now translate the result to a Base64 string
    Base64 * base64 = [[Base64 alloc] init];
    NSString * ret = [base64 base64encode:[encKey bytes]
                                   length:[encKey length]];
    [base64 release];
    [encKey release];
    return ret;
}

As I said – I’m very sure there is an easier way to do this. If someone can tell me what it is I’d be most grateful!

And if you need some code to import into a Java key – the following is a cut from the java app I am building. Clearly you will want to get a bit prettier on the exception handling :).

        byte[] decodedPublicKey;
        try {
            // Now lets encrypt the nonce
            decodedPublicKey = Base64.decode(d.getPublicKey());
        } catch (Base64DecodingException ex) {
            logger.log(Level.WARNING,
               "getDeviceInfo - error decoding public key for device:{0}",
               deviceId);
            return di;
        }
        PublicKey publicKey;
        try {
            publicKey = KeyFactory.getInstance("RSA").
                generatePublic(new X509EncodedKeySpec(decodedPublicKey));
        } catch (Exception ex) {
            logger.log(Level.WARNING,
               "getDeviceInfo - error importing public key for device:{0}",
                deviceId);
            return di;
        }