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;
}
Hello Berin,
I’m trying to implement RSA encryption between iPhone and Java based in your code…
My Java Application get a error : java.security.NoSuchAlgorithmException: RSA/ECB/PKCS1Padding KeyFactory not available do you have any suggestion for me?
Heya,
What version of java are you using – do you have your security provider set up ok? Sunjce should support on recent versions.
Cheers,
Berin
Hi,
I use currently:
java version “1.6.0_20”
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
part of my code is:
import java.security.*;
import javax.crypto.*;
import java.security.interfaces.*;
import java.security.spec.*;
….
public static final int KEYSIZE = 1024;
public static String RSA_DEFAULT = “RSA”;
public static String RSA_ECB_PKCS1_PADDING = “RSA/ECB/PKCS1Padding”;
public static String RSA_ECB_NO_PADDING = “RSA/ECB/NoPadding”;
public static void test(){ // This method works fine!!
try{
//keyPair
KeyPair keyPair = RSACryptoUnit.generateKeys();
//Key generation
RSAPublicKey pubKey = (RSAPublicKey)keyPair.getPublic();
RSAPublicKey newKey = RSACryptoUnit.generatyPublicKey(pubKey.getModulus(), pubKey.getPublicExponent());
//encrypt
String testStr = “plain text”;
String encryptedText = RSACryptoUnit.encrypt(testStr, newKey, RSACryptoUnit.RSA_ECB_PKCS1_PADDING);
//System.out.println(encryptedText);
//System.out.println(new String(pubKey.getEncoded()));
//System.out.println(“Format: “+pubKey.getFormat());
//System.out.println(“Algor: “+pubKey.getAlgorithm());
//decrypt
String decryptedText = RSACryptoUnit.decrypt(encryptedText, keyPair.getPrivate(), RSACryptoUnit.RSA_ECB_PKCS1_PADDING);
System.out.println(decryptedText);
System.out.println(keyPair.getPublic());
}catch (Exception e){
System.out.println(e.getMessage());
}
}
public static RSAPublicKey reconstruct_iPhonePublicKey(byte[] pk) throws Exception{
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
X509EncodedKeySpec pubEncKeySpec = new X509EncodedKeySpec(pk);
KeyFactory kf = KeyFactory.getInstance(“RSA/ECB/PKCS1Padding”, “BC”);
RSAPublicKey pubSpec = (RSAPublicKey) kf.generatePublic(pubEncKeySpec);
return pubSpec;
}
public static PublicKey reconstruct_iPhonePublicKey1(byte[] pk) throws Exception{
PublicKey publicKey = KeyFactory.getInstance(“RSA/ECB/PKCS1Padding”).generatePublic(new X509EncodedKeySpec(pk));
return publicKey;
}
reconstruct_iPhonePublicKey/reconstruct_iPhonePublicKey1 functions dont work 🙁
Grüß,
Javier
Hiya,
Looks like I mucked up the Java code when I copied it in – sorry :(. The keyspec is just RSA, it’s the cipher itself that requires the full algorithm definition. When I copied over I tried to remove some of the cruft from my program. I’ve done a direct copy/paste below.
BTW – I also had a problem then decrypting in the iPhone – the iPhone doesn’t seem to want to decrypt PKCS1 padded data directly. But you can decrypt with no padding and then strip the padding yourself.
Have a look at the following and see if it works a bit better.
try {
publicKey = KeyFactory.getInstance(“RSA”).generatePublic(new X509EncodedKeySpec(decodedPublicKey));
} catch (Exception ex) {
logger.log(Level.WARNING, “WSESSION004:getDeviceInfo – error importing public key for device:{0}”, deviceId);
return di;
}
try {
Cipher cipher = Cipher.getInstance(“RSA/ECB/PKCS1Padding”);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(nonce.getBytes());
String cipherDataB64 = Base64.encode(cipherData);
logger.fine(“WSESSOPN004:encData = ” + cipherDataB64);
di.setEncryptedData(cipherDataB64);
}
This looks great, I’ve been scouring the internet for 3 days straight trying to get some help on exactly this topic.
I have a few questions.
1. I if I understand correctly, your code retrieves an existing public key from the key chain, and then encodes it – could I instead create a new public key and use references returned by SecKeyGeneratePair()?
e.g.
status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr,
&publicKeyBits, &privateKey)
and then continue with the encoding using the publicKeyBits variable as you did?
2. Do you have an example that does the same thing in reverse? Let’s say I am sending a base64 encoded public key from my java server to the iPhone. I would like to convert that into a secKeyRef object.
Thanks!
Heya Kristian,
1) I don’t think so – but it’s difficult to be certain as the API is documented very poorly. Looking at SecKey.h it’s fairly specific about returning a reference only. So you’d need to then do a lookup out of the keychain to get the keybits.
2) I haven’t done it the other way yet. As soon as I do I’ll paste up the code. But have you tried importing just the straight Java exported key? It might be that the iPhone SKD is clever enough to see that it’s a full key and strip off the gumf. If not it should be fairly easy to cut it back down to size. The CryptoExercise code has some code for importing key data – in particular in SecKeyWrapper.m. Have a look at function
addPeerPublicKey
.Hi Berin,
thx for your samplecode.
Im trying to import the X.509 iphone key in my java application. When i generate the public key (keyFactory.generatePublic…) from the byte array i got an exception “Detect premature EOF”.
Im not sure, but my iphone byte array was only 161 [0..160] bytes length. Maybe im missing some bytes :)..
It would be glad if you have any suggestions.
Cheers!
Vulquangan
Hiya Vulquangan,
Not sure. I’ve only really tested the code with a 2048 bit key – so a smaller key might not quite pack correctly, particularly if I got something wrong in the length code.
Not sure what you are doing your development on – but are you able to either post either a hex dump of the key you are trying to import into Java or maybe in base64 if you aren’t on a system with od?
Cheers,
Berin
Hey Berin,
my iphone creates a 1024 bit key. Now ive tested it with a 2048 bitkey and it works fine. *yeay* 🙂
It would be really helpful if there was a way to use the smaller 1024 bit key. This will also reduce the key creation time on the iphone.
Here is my sample X.509 iphone (1024-Bit) key as base64 String:
MIGfMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgG3XsNZCUQhrwjlyT068ab12Fe2ivYKENwRcfrOjEjVWZ9OM64WMkMjGvNP8UfPBf2q8TQTHZLU+enzykkWJJCCyJqz0qta8G4eWgO1y4a17TG3d5T5x9TNsKz2VjE0mmxLGOawmUMcU8oqh3DsE+QxSo08p78gd8dqfIpGU792vAgMBAAE=
Cheers!
Vulquangan
Thanks for the quick reply.
Can I use the same method as above for the private key as well?
(to get it in a base64 encoded string which I can pass to a server running some java code?)
Hiya Kristian,
No – that won’t work. Assuming you can output the private key (I’ve not tried) the format it’s stored in will be quite different. You can probably use the same technique in general, but the actual code will be quite different. The private key is a bit more complicated internally.
Cheers,
Berin
Hiya Vulquangan,
I quickly dumped your key and I was correct – at the very least I mucked up one of the length calculations. I don’t have time to actually try this now, but the code:
// When we get to the bitstring - how will we encode it?
if ([publicKeyBits length] + 1 > 128)
bitstringEncLength = 3;
else
bitstringEncLength = 1;
needs to be changed so that if the length is >128 but < 256 the bitstringEncLength = 2. So something like (completely untested)
// When we get to the bitstring - how will we encode it?
if ([publicKeyBits length] + 1 > 128 and [publickeyBits length] + 1 < 256) bitstringEncLength = 2; else if ([publicKeyBits length] +1 >= 256) bitstringEncLength = 3; else bitstringEncLength = 1;
There is a much prettier way to do that but I'm falling asleep :). I'm also not 100% sure that will work - but I will play with it tomorrow as I need 1024 bit keys to work as well.
Cheers,
Berin
Yup – that was it. I’ve changed the lines I referenced to now be:
// 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;
and it works for 512/1024 and 2048 bit keys.
yeay!..Well done.
Thx a lot for your help. Cheers!
Vulquangan
I keep getting the exact same string every time I make a new key and generate the base64 string – there’s something I’m not doing right.
What code are you using to generate the keys?
I am just using the sample from Apple:
– (void)generateKeyPairPlease
{
OSStatus status = noErr;
NSMutableDictionary *privateKeyAttr = [[NSMutableDictionary alloc] init];
NSMutableDictionary *publicKeyAttr = [[NSMutableDictionary alloc] init];
NSMutableDictionary *keyPairAttr = [[NSMutableDictionary alloc] init];
// 2
NSData * publicTag = [NSData dataWithBytes:publicKeyIdentifier length:strlen((const char *)publicKeyIdentifier)];
NSData * privateTag = [NSData dataWithBytes:privateKeyIdentifier length:strlen((const char *)privateKeyIdentifier)];
// 3
SecKeyRef publicKey = NULL;
SecKeyRef privateKey = NULL; // 4
[keyPairAttr setObject:(id)kSecAttrKeyTypeRSA
forKey:(id)kSecAttrKeyType]; // 5
[keyPairAttr setObject:[NSNumber numberWithInt:1024]
forKey:(id)kSecAttrKeySizeInBits]; // 6
[privateKeyAttr setObject:[NSNumber numberWithBool:YES]
forKey:(id)kSecAttrIsPermanent]; // 7
[privateKeyAttr setObject:privateTag
forKey:(id)kSecAttrApplicationTag]; // 8
[publicKeyAttr setObject:[NSNumber numberWithBool:YES]
forKey:(id)kSecAttrIsPermanent]; // 9
[publicKeyAttr setObject:publicTag
forKey:(id)kSecAttrApplicationTag]; // 10
[keyPairAttr setObject:privateKeyAttr
forKey:(id)kSecPrivateKeyAttrs]; // 11
[keyPairAttr setObject:publicKeyAttr
forKey:(id)kSecPublicKeyAttrs]; // 12
status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr,
&publicKey, &privateKey); // 13
I have created a new class containing the method you have above. After I make the new keys using the method above, I create a new instance of my object and I call your method from it.
I always get returned the same string.
Do either of you perhaps have some full source code I could look at? A working project that creates a key and returns the base 64 string representation.
Thanks!
How big is the data you get back in bytes when you wrap everything up? Also – are you using the same key identifier between the generate and the class you have created that extracts the public key data?
Yes, I double checked the key identifier, in both cases it’s:
static const UInt8 publicKeyIdentifier[] = “kristian.sample.publickey”;
And the part of your method that looks up a key from the keychain based on that identifier does not produce an error so that part must work.
NSLog(@”Size: %d”, [encKey length]);
Prints out 162
And my base64 encoded version (I used something else than your base64 class since there was no source code here)
NSLog(@”PK Size size: %d”, [base64EncodedPublicKey length]);
Prints out 222
(also do you know how to serialize a public key in java so I can send it to the iphone?)
(My iPhone is supposed to be generating a 1024 bit key)
[keyPairAttr setObject:[NSNumber numberWithInt:1024]
forKey:(id)kSecAttrKeySizeInBits];
Fixed my previous problems.
How did you know the data structure returned by SecItemCopyMatching in order to extract the relavant information?
Heya Kristian,
I was thinking about your problem today and was going to ask if you’d deleted keys between times – but glad you worked it out!
Public keys are fairly simple constructs and the exponents are fairly standardised. So I simply dumped it in hex and worked figured it looked like two ASN.1 encoded numbers with the exponent being the second – which is the tail end of a standard RSA public key encoding.
Lucked out really :).
Hi Berin,
Yes, it was indeed forgetting to delete the keys. I’m glad you stumbled across this – it’s exactly what I need, and I’m surprised that there is not a standard way to do this. Thanks for all the help.
Now I just need to be able to do it in reverse, and also do it for private keys.
Note that your encKey is an owned reference that is not being released.
Hiya Maarten,
Darn – I missed that! Ty. Fixed.
Cheers,
Berin
Hi,
Atlast I got some boost from developer.apple.com and got public/private key pair.
I’m using “CryptoSample.zip” from apple sample code.
I’ve copied
(void)generateKeyPairPlease{
}
from apple’s developer website. But my question is that I need to feed filepath to libssh2_userauth_publickey_fromfile() function so I can use my public/private key for remote login with authenticated host. So, i get public/private key and temporarily write this key into “Documents” folder in application’s folder. Then I pass the path to these public/private keys to the above function, but it fails, it returns -1.
I’m sure that my public key and authorize_keys2 on remote server both have same content, then also i’m getting error like -1, so, ssh connection fails. My point of view is from developer not user’s. Am I using proper way to generate public/private key pair for iPhone device using the above function or I need to work for other function? Please let me know if you have any suggestion. I need to have success for this application.
I don’t know if the key pair i get is proper or not and can be used for remote server login. Please suggest me if i need any encoding or decoding functions. I have used Base64 encoding for keys.
Does anyone know where public/private key pair is stored on iPhone device and how can i get them?
Regards,
Paresh Thakor.
Hi Berin,
Excellent post!!
Hey can you tell me how can I import the RSA public key from Java server to iPhone?
Do I need to decode the public key in iphone side?
Regards,
Syam S
It should be the same process in reverse and then load as a key reference. I’ve done something like it – I’ll try to dig out the code and paste in a separate post
Berin,
Thanks for the reply.
But when I tried this I am unable to store the (X509EncodedSpec encoded) key to iPhone.
i.e. addPeerPublicKey() function returned a success message but no SecKeyRef is returned.So I am unable to encrypt data with the public key returned from server.
Do I need to add any decoding for X509EncodedSpec in iPhone?
Thanx,
Syam S
Can you post or send me the key? If you have it base64 encoded just post it.
Hi,
I am able to encrypt using public key from ur
http://blog.wingsofhermes.org/?p=75
Thanks man..great wok..
But my problems are not yet solved..When we tried to decrypt the encrypted data in server side I got some errors
1. Bad padding Exception
2. Block type mismatch exception
Can you direct me to what causes this error?
Regards,
Syam S
Sounds like a mismatch with padding. Try decrypting with no padding and see if the plain text is there. Or just use no padding on both sides to make sure the keys are right before turning padding back on.
Berin,
My problems fixed thanx…man
Regards,
Syam S
Nice examples. Do you have any idea how to get the modulus and exponent out of the publicKeyBits? Thanks, Joseph
Hi Joseph,
The publicKeyBits is the tail end of a standard DER encoded RSA public key. So it’s something like:
SEQUENCE {
INTEGER exponent
INTEGER modulus
}
(Excuse the lazy syntax).
In byte terms, that would be (assuming a relatively long key that would push modulus length > 0x80)
0x30
0x82
[two bytes representing length of sequence]
0x02
0x82
[two bytes representing length of modulus]
[bytes representing modulus]
0x02
[one byte representing length of exponent]
[bytes representing exponent]
Thanks for excellent blog and its awesome!!!!!!! Past 10 days I was struck with sending public key to server and I was failed to send it. After seen you post I can able to do it.
The above example will work for encryption and decryption but when want to do the Digital Signature with SHA256 padding my signature get failed. I have noticed something that public key is headed with SHA1 algorithm. Can you please help me to modify the public key with SHA256
Thanks & regards,
S. Murali Krishnan
Berin, this is very helpful, thanks! What type of license do you allow your code to be distributed under (you might look at this for a list and explanation of why we need to know… http://www.codinghorror.com/blog/2007/04/pick-a-license-any-license.html)? Also to whom should we attribute the code?
Apologies – been very sidetracked the last few months. Everything I do is under ASL 2.0.. Not too worried about attribution – but Berin Lautenbach if you have to :). I should mention this somewhere but I never think of it for code snippets.
Either way – glad it was of use!
Hi I new in RSA encryption/decryption. I am having encrypted text and .pem file generated by java code how can I decrypt text in iOS any help or suggestions. Thank you in advance.
Hi This is a great post but I have what is possibly a very stupid question but what is _PWITC_PUBLIC_KEY_TAG? (Needless to say I am new to xcode).
And I have another stupid question. What do I need to import to make use of Base64?
This is brilliant. It works awesome. The publicKeyBits generated by iOS were not X509 compatible with java code but with your code snippet the public key is transferrable to the java server for signature verification. This is just awesome. I thank you deeply for your knowledge and for your willingness to share it. If you’re ever in New York, I’ll buy you a drink 🙂
Same as Julia here. I’m trying to find out what _PWITC_PUBLIC_KEY_TAG is and where it comes from. Thanks
It’s an arbitrary string (in my case “com.pwitc.iphone.keypair”). kSecAttrApplicationTag is the “identifier” you use to identify the key in the keychain. So I used it when I generated the keypair in the first place and in this code I use it to extract the key out again so I can manipulate it.
Really I should fix the code snippet – I will do that now.
Is there any way of importing the private key?
Thank You.
Strangely enough I just ran into a need to import/export the private key today. Looks like a standard private key has an extra header set. Am having a look at it – will report back!
Yup – looks like it requires a PKCS#1 format private key. So if you are using a PKCS#8 format key (which is by default what Java will give you) you need to strip the PKCS#8 header. (PKCS#8 is a wrapper for the #1 format key, with a version and OID at the front of the private key.)
Mr Berin, sir, you have saved my bacon.
see here: http://stackoverflow.com/questions/30648980/ios-5-0-generated-x509-rsa-public-key-of-size-2048-is-270-bytes-instead-of-294