Reputation: 3300
I need to implement encryption / decryption using a X.509 RSA public/private key pair.
So far, I have something which I think will work for encryption, but I have no way of decrypting to check. Everything I try has issues reading the private key.
.der
and a .pem
)openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem -days 3650
+ (NSData *) RSAEncryptData:(NSData *)content {
SecKeyRef publicKey;
SecCertificateRef certificate;
SecPolicyRef policy;
SecTrustRef trust;
size_t maxPlainLen;
NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:@"public_key" ofType:@"der"];
NSData *base64KeyData = [NSData dataWithContentsOfFile:publicKeyPath];
certificate = SecCertificateCreateWithData(kCFAllocatorDefault, ( __bridge CFDataRef) base64KeyData);
if (certificate == nil) {
NSLog(@"Can not read certificate from data");
return nil;
}
policy = SecPolicyCreateBasicX509();
OSStatus returnCode = SecTrustCreateWithCertificates(certificate, policy, &trust);
if (returnCode != 0) {
NSLog(@"SecTrustCreateWithCertificates fail. Error Code: %d", (int)returnCode);
return nil;
}
SecTrustResultType trustResultType;
returnCode = SecTrustEvaluate(trust, &trustResultType);
if (returnCode != 0) {
return nil;
}
publicKey = SecTrustCopyPublicKey(trust);
if (publicKey == nil) {
NSLog(@"SecTrustCopyPublicKey fail");
return nil;
}
maxPlainLen = SecKeyGetBlockSize(publicKey) - 12;
size_t plainLen = [content length];
if (plainLen > maxPlainLen) {
NSLog(@"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
return nil;
}
void *plain = malloc(plainLen);
[content getBytes:plain
length:plainLen];
size_t cipherLen = 128; // currently RSA key length is set to 128 bytes
void *cipher = malloc(cipherLen);
OSStatus encryptReturnCode = SecKeyEncrypt(publicKey, kSecPaddingPKCS1, plain,
plainLen, cipher, &cipherLen);
NSData *result = nil;
if (encryptReturnCode != 0) {
NSLog(@"SecKeyEncrypt fail. Error Code: %d", (int)returnCode);
}
else {
result = [NSData dataWithBytes:cipher
length:cipherLen];
}
free(plain);
free(cipher);
return result;
}
I have tried using OpenSSL's PEM_read_X509
also PEM_read_RSAPrivateKey
, but both fail to read the cert. I have not even gotten past that. If I could do this without having a dependency on the OpenSSL library, that would be even better.
+(void)readTest{
FILE *fp;
X509 *x;
NSString *path =[[NSBundle mainBundle] pathForResource:@"private_key" ofType:@"pem"];
fp=fopen([path UTF8String],"r");
x=NULL;
PEM_read_X509(fp,&x,NULL,NULL); // I have also tried PEM_read_RSAPrivateKey
if (x == NULL) {
NSLog(@"Cant Read File"); // This ALWAYS fires
}
fclose(fp);
X509_free(x);
}
If someone could help me out with encryption/decryption using X.509 RSA pairs, I'd appreciate it. Thanks.
Upvotes: 7
Views: 5689
Reputation: 6347
It seems your private key is encrypted (openssl asked you for a password on the command line), yet you do not decrypt it when you try to open it. Besides, private_key.pem is an RSA key, not a certificate, so you should use PEM_read_RSAPrivateKey.
The following decoding code should work:
int pass_cb(char *buf, int size, int rwflag, void* password) {
snprintf(buf, size, "%s", (char*) password);
return strlen(buf);
}
+(void)readTest{
FILE *fp;
RSA *x;
NSString *path =[[NSBundle mainBundle] pathForResource:@"private_key" ofType:@"pem"];
fp=fopen([path UTF8String],"r");
x = PEM_read_RSAPrivateKey(fp,&x,pass_cb,"key password");
if (x == NULL) {
NSLog(@"Cant Read File"); // This ALWAYS fires
}
fclose(fp);
X509_free(x);
}
Alternatively, you could generate a non-encrypted key. Pass -nodes
to openssl when creating the keys and the certificate.
Please note that you might need to make sure OpenSSL is properly initialized with:
SSL_library_init();
OpenSSL_add_all_algorithms();
Besides, OpenSSL generates error messages that could help you through development. You load the error strings with:
SSL_load_error_strings();
And you could call:
ERR_print_errors_fp(stderr);
OpenSSL is not the only solution as Security framework on iOS contains everything you need. I guess you turned to OpenSSL because you did not know how to convert your private key file to valid parameters for SecKeyDecrypt.
The trick is to produce a PKCS#12 file and to call SecPKCS12Import
.
You can produce this file with OpenSSL:
openssl x509 -inform der -outform pem -in public_key.der -out public_key.pem
openssl pkcs12 -export -in public_key.pem -inkey private_key.pem -out private_key.p12
This will ask you for an export password. This password should be passed to SecPKCS12Import ("key password"
below).
NSString *privateKeyPath = [[NSBundle mainBundle] pathForResource:@"private_key" ofType:@"p12"];
NSData *pkcs12key = [NSData dataWithContentsOfFile:privateKeyPath];
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys: @"key password", kSecImportExportPassphrase, nil];
CFArrayRef importedItems = NULL;
OSStatus returnCode = SecPKCS12Import(
(__bridge CFDataRef) pkcs12key,
(__bridge CFDictionaryRef) options,
&importedItems
);
importedItems
is an array containing all imported PKCS12 items, and basically, the "identity" (private key + certificate).
NSDictionary* item = (NSDictionary*) CFArrayGetValueAtIndex(importedItems, 0);
SecIdentityRef identity = (__bridge SecIdentityRef) [item objectForKey:(__bridge NSString *) kSecImportItemIdentity];
SecKeyRef privateKeyRef;
SecIdentityCopyPrivateKey(identity, &privateKeyRef);
Then you can use privateKeyRef to perform the decryption with SecKeyDecrypt
. To match your encryption routine:
size_t cipherLen = [content length];
void *cipher = malloc(cipherLen);
[content getBytes:cipher length:cipherLen];
size_t plainLen = SecKeyGetBlockSize(privateKeyRef) - 12;
void *plain = malloc(plainLen);
OSStatus decryptReturnCode = SecKeyDecrypt(privateKeyRef, kSecPaddingPKCS1, cipher, cipherLen, plain, &plainLen);
Upvotes: 9