Importing an iPhone RSA public key into a Java app
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.
#define _MY_PUBLIC_KEY_TAG "my_key_identifier"
- (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:_MY_PUBLIC_KEY_TAG
length:strlen((const char *) _MY_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;
}
Glad it helped :).
Mr Brian,
Sir, I have a question.
Say,
1. I create key pair (think generated “private key” is privateKey)
2. Get public key and apply your code (which really works. Thanks a lot).
3. Send above public key to the server
4. Server will send me a key which encrypted using my given public key
question is can I decrypt it using the “privateKey”I generated.? Or will there be any conflict?
Thank you..
Apologies – I totally missed this.
Not completely sure of question. If you encrypt something with a public key, you decrypt with the private. If you encrypt with the private, then you use the public key to decrypt (used for signatures). That’s true for RSA at least.
Is that what you meant?
Hello,
thank you for your post but there’s something that I don’t get. Where did you get that d.getPublicKey() in the Java code? Because you’re passing a String to Android…
Thank you !
Sorry – it’s a bad cut of code from me. “d” is an object in the code I’m using. The “d.getPublicKey()” just returns the base64 encoded public key. It’s a string. The result of the decode is a byte array that can be used in the public key import function.
Struggling to get this simple func to work
size_t encodeLength(unsigned char * buf, size_t length)
Added to the header file:
– (size_t)encodeLength: (unsigned char *)buf bufLength:(size_t *)length;
Implementation:
// Helper function for ASN.1 encoding, http://blog.wingsofhermes.org/?p=42
– (size_t)encodeLength: (unsigned char *)buf bufLength:(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 > 8;
}
return i + 1;
}
Has several errors – yes i am new to objective-c
Saved me in 2020, thanks! Share your PayPal and I will send you some coffee money.
Glad it helped :).
Take care,
Berin