I'm new to Objective C & iOS programming.
I'm using a simple public / private keys (PEM format) generated using openssl to encrypt and decrypt data that needs to be exchanged between a Server and a Client. I got this successfully working in Java Server & Client.
The trouble started when I was encrypting data using public key in Java and decrypting using private key in Objective C / iOS. I've looked around a few examples and put some code together but I'm getting an error -25300 when I call the SecItemCopyMatching all the time as part of creating a SecKeyRef from the private key.
BTW, there are no certificates involved here and it's just plain keys. Here is what I'm doing:
My problem is step #2 which returns a status of -25300 (errSecItemNotFound –25300
The item cannot be found. Available in iOS 2.0 and later.)
Here is my code for generating the SecKeyRef:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *challenge = @"2KFqc46DNSWrizzv69lJN25o62xEYQw/QLcMiT2V1XLER9uJbOu+xH2qgTuNWa1HZ9SW3Lq+HovtkhFmjmf08QkVQohHmxCJXVyCgVhPBleScAgQ8AoP3tmV0RqGb2mJrb19ybeYP7uZ2piVtF4cRwU1gO3VTooCUK3cX4wS7Tc=";
NSLog(@"challenge, %@", challenge);
NSData *incomingData = [self base64DataFromString:challenge];
uint8_t *challengeBuffer = (uint8_t*)[incomingData bytes];
NSLog(@"challengeBuffer: %s", challengeBuffer);
[self decryptWithPrivateKey:challengeBuffer];
free(challengeBuffer);
return YES;
}
// Generate a SecKeyRef from the private key in the private.pem file.
- (SecKeyRef)getPrivateKeyRef {
NSString *startPrivateKey = @"-----BEGIN RSA PRIVATE KEY-----";
NSString *endPrivateKey = @"-----END RSA PRIVATE KEY-----";
NSString* path = [[NSBundle mainBundle] pathForResource:@"private"
ofType:@"pem"];
NSString* content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:NULL];
NSLog(@"Private Key: %@", content);
NSString *privateKey;
NSScanner *scanner = [NSScanner scannerWithString:content];
[scanner scanUpToString:startPrivateKey intoString:nil];
[scanner scanString:startPrivateKey intoString:nil];
[scanner scanUpToString:endPrivateKey intoString:&privateKey];
NSData *privateTag = [self dataWithBase64EncodedString:privateKey];
NSLog(@"Decoded String: %@", privateTag);
OSStatus status = noErr;
SecKeyRef privateKeyReference = NULL;
NSMutableDictionary * queryPrivateKey = [[NSMutableDictionary alloc] init];
// Set the private key query dictionary.
[queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[queryPrivateKey setObject:privateTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
//[queryPrivateKey setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnRef];
// Get the key.
status = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKeyReference);
NSLog(@"status: %ld", status);
if(status != noErr)
{
privateKeyReference = NULL;
}
return privateKeyReference;
}
// Decrypt data
- (void)decryptWithPrivateKey:(uint8_t *)cipherBuffer {
OSStatus status = noErr;
SecKeyRef privateKeyRef = [self getPrivateKeyRef];
size_t plainBufferSize = SecKeyGetBlockSize(privateKeyRef);
uint8_t *plainBuffer = malloc(plainBufferSize);
size_t cipherBufferSize = strlen((char *)cipherBuffer);
NSLog(@"decryptWithPrivateKey: length of input: %lu", cipherBufferSize);
// Error handling
status = SecKeyDecrypt(privateKeyRef,
PADDING,
cipherBuffer,
cipherBufferSize,
&plainBuffer[0],
&plainBufferSize
);
NSLog(@"decryption result code: %ld (size: %lu)", status, plainBufferSize);
NSLog(@"FINAL decrypted text: %s", plainBuffer);
}
I've been breaking my head for a couple of days now and I felt like I need to get some help here. Any one any pointers? I could spend more time gaining the Crypto domain knowledge and support that iOS provides but I don't do any iOS programming at all and this is a one time thing.
I just need some direction and I can struggle to make it work.
TIA.
Unfortunately the Security framework on iOS requires that private keys be in the PKCS12 format, with a passphrase. Public keys can be in X509 armored DER or PKCS12, but private keys are required to be PKCS12. The private key you are trying to use is a PEM-formatted RSA key.
If you have access to the key it can be converted using the openssl command line tools:
openssl pkcs12 -export -nocerts -inkey privatekey.pem -out privatekey.p12
This will create a PKCS12 file with the private key, and requires a passphrase. If you do not have control of the private key (for example, if it is coming from an external source such as a server), you are out of luck.
But let's assume you were able to do the steps above to convert that troubesome PEM RSA private key to PKCS12. Extracting the private key from the PKCS12 data is not too difficult:
NSData
. You can do this using dataWithContentsOfURL:
if this is a resource on the filesystem.SecPKCS12Import
to import the PKCS12 data with the passphrase.SecIdentityRef
from the imported items.SecIdentityRef
A function for doing so would be:
OSStatus SecKeyPrivatePKCS12Import(CFDataRef keyData, CFStringRef passphrase, SecKeyRef *privateKey){
OSStatus status = errSecSuccess;
CFDictionaryRef secImportOptions = NULL;
CFArrayRef secImportItems = NULL;
if ((keyData != NULL) && (CFStringGetLength(passphrase) > 0) ){
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { passphrase };
secImportOptions = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
status = SecPKCS12Import((CFDataRef) keyData, (CFDictionaryRef)secImportOptions, &secImportItems);
if (CFArrayGetCount(secImportItems) > 0){
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(secImportItems, 0);
SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
SecIdentityCopyPrivateKey(identityApp, privateKey);
}
}
return status;
}
Calling it from Objective-C would look like:
OSStatus status = errSecSuccess;
status = SecKeyPrivatePKCS12Import((_bridge CFDataRef)data, (_bridge CFStringRef)passphrase, &privateKey);
if (privateKey == NULL){
// Check the status value for why it failed
}
Assuming that "data" is an instance of NSData
that contains the PKCS12 data, and "passphrase" is an NSString
instance representing the passphrase. On success "privateKey" is populated with the private key imported from the PKCS12 data.
I have faced the same issue when I was working with java server and iPhone application and my work around was as below.
Send those data to iOS application no matter how you want.
3.1 You can put base 64 in text file and send that to iOS. [Safest way and working fine in my case.]
3.2 You can use JSON string to send that string. [This might corrupt your data.]
Use following method to get the SecKeyRef of your private key.
- (SecKeyRef)getPrivateKeyFromData:(NSData *)p12Data withPassword:(NSString *)password {
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKey = NULL;
[options setObject:password forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = NULL;// = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)p12Data,
(__bridge CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKey);
if (securityError != noErr) {
privateKey = NULL;
}
}
//NSLog(@"-------------------- Private Key Error %d",(int)securityError);
CFRelease(items);
options = nil;
p12Data = nil;
password = nil;
return privateKey;
}
Hope this helps!!!!!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With