In my static Library I have a licence file. Which I want to make sure has been generated by myself (and has not been altered). So the idea was to use an RSA Signature from what I've read.
I've looked on the internet and this is what I came up with:
First: Generating the private keys and self signed certificates with the information I found here.
// Generate private key
openssl genrsa -out private_key.pem 2048 -sha256
// Generate certificate request
openssl req -new -key private_key.pem -out certificate_request.pem -sha256
// Generate public certificate
openssl x509 -req -days 2000 -in certificate_request.pem -signkey private_key.pem -out certificate.pem -sha256
// Convert it to cer format so iOS kan work with it
openssl x509 -outform der -in certificate.pem -out certificate.cer -sha256
After that, I create a licence file (with a date and app identifier as contents) and generate a signature for that file like so based on the information found here:
// Store the sha256 of the licence in a file
openssl dgst -sha256 licence.txt > hash
// And generate a signature file for that hash with the private key generated earlier
openssl rsautl -sign -inkey private_key.pem -keyform PEM -in hash > signature.sig
Which I think all works fine. I don't get any errors and get the keys and certificates and other files as expected.
Next I copy certificate.cer, signature.sig and license.txt to my application.
Now I want to check if the signature has been signed by me and is valid for the license.txt. I found it fairly hard to find any good examples but this is what I have currently:
The Seucyrity.Framework I found out uses a SecKeyRef to reference an RSA key / certificate and SecKeyRawVerify to verify a signature.
I have the following method to load the public key from a file.
- (SecKeyRef)publicKeyFromFile:(NSString *) path
{
NSData *myCertData = [[NSFileManager defaultManager] contentsAtPath:path];
CFDataRef myCertDataRef = (__bridge CFDataRef) myCertData;
SecCertificateRef cert = SecCertificateCreateWithData (kCFAllocatorDefault, myCertDataRef);
CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
SecTrustCreateWithCertificates(certs, policy, &trust);
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
SecKeyRef pub_key_leaf = SecTrustCopyPublicKey(trust);
if (trustResult == kSecTrustResultRecoverableTrustFailure)
{
NSLog(#"I think this is the problem");
}
return pub_key_leaf;
}
Which is based on this SO post.
For the signature validation I found the following function
BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
const void* signedHashBytes = [signature bytes];
size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH;
uint8_t* hashBytes = malloc(hashBytesSize);
if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], hashBytes)) {
return nil;
}
OSStatus status = SecKeyRawVerify(publicKey,
kSecPaddingPKCS1SHA256,
hashBytes,
hashBytesSize,
signedHashBytes,
signedHashBytesSize);
return status == errSecSuccess;
}
Which is taken from here
In my project I call the code like so:
// Get the licence data
NSString *licencePath = [[NSBundle mainBundle] pathForResource:#"licence" ofType:#"txt"];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:licencePath];
// Get the signature data
NSString *signaturePath = [[NSBundle mainBundle] pathForResource:#"signature" ofType:#"sig"];
NSData *signature = [[NSFileManager defaultManager] contentsAtPath:signaturePath];
// Get the public key
NSString *publicKeyPath = [[NSBundle mainBundle] pathForResource:#"certificate" ofType:#"cer"];
SecKeyRef publicKey = [self publicKeyFromFile:publicKeyPath];
// Check if the signature is valid with this public key for this data
BOOL result = PKCSVerifyBytesSHA256withRSA(data, signature, publicKey);
if (result)
{
NSLog(#"Alright All good!");
}
else
{
NSLog(#"Something went wrong!");
}
Currently it always says: "Something went wrong!" though I am not sure what. I found out that trust result in the method that fetches the public key equals kSecTrustResultRecoverableTrustFailure which I think is the problem. In the Apple documentation I found that could be the result of a certificate that has been expired. Though that does not seem to be the case here. But maybe there is something wrong with the way I generate my certificate?
My question boils down to, what am I doing wrong, and how could I fix that? I find the documentation on this quite sparse and hard to read.
I have uploaded an iOS project with generated certificates and the code referenced here. Maybe that could come in handy.
The problem is lying on the way you create the signature file; following the same step I was able to produce the binary equivalent signature.sig file.
By looking inside the hash file we can see openssl add some prefix (and hex encode the hash):
$ cat hash
SHA256(licence.txt)= 652b23d424dd7106b66f14c49bac5013c74724c055bc2711521a1ddf23441724
So signature.sig is based on that and not on license.txt
By using your sample and creating the signing file with:
openssl dgst -sha256 -sign certificates/private_key.pem licence.txt > signature.sig
The hashing & signing step gets correct, and the sample outputs: Alright All good!
The final state of my file, just in case
- (SecKeyRef)publicKeyFromFile:(NSString *) path
{
NSData * certificateData = [[NSFileManager defaultManager] contentsAtPath:path];
SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData);
SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
SecTrustRef trust;
SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust);
SecTrustResultType resultType;
SecTrustEvaluate(trust, &resultType);
SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
return publicKey;
}
BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], digest))
return NO;
OSStatus status = SecKeyRawVerify(publicKey,
kSecPaddingPKCS1SHA256,
digest,
CC_SHA256_DIGEST_LENGTH,
[signature bytes],
[signature length]);
return status == errSecSuccess;
}
PS: the malloc was a leak
Edit:
To make your current signature.sig file work as-is, you have to produce the same step as openssl (add prefix, hex-hash, and a newline \n), then pass this data to SecKeyRawVerify with kSecPaddingPKCS1 and not kSecPaddingPKCS1SHA256:
BOOL PKCSVerifyBytesSHA256withRSA(NSData* plainData, NSData* signature, SecKeyRef publicKey)
{
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
if (!CC_SHA256([plainData bytes], (CC_LONG)[plainData length], digest))
return NO;
NSMutableString *hashFile = [NSMutableString stringWithFormat:#"SHA256(licence.txt)= "];
for (NSUInteger index = 0; index < sizeof(digest); ++index)
[hashFile appendFormat:#"%02x", digest[index]];
[hashFile appendString:#"\n"];
NSData *hashFileData = [hashFile dataUsingEncoding:NSNonLossyASCIIStringEncoding];
OSStatus status = SecKeyRawVerify(publicKey,
kSecPaddingPKCS1,
[hashFileData bytes],
[hashFileData length],
[signature bytes],
[signature length]);
return status == errSecSuccess;
}
Related
I am using Apple's Security Framework. I am able to sign and then successfully verify all on OS X, but when I try to use SecKeyRawVerify on iOS it fails with -9809 error.
I've played with various PKCS padding options and many other attributes but I'm just not able to get this to verify correctly.
Note that the code below probably has leaks all over the place, just trying to get this to function properly first.
OS X Signing code:
NSData* signData(NSData* plainData, SecKeyRef privateKey) {
CFErrorRef error;
/* Create the transform objects */
SecTransformRef signer = SecSignTransformCreate(privateKey, &error);
if (error) { CFShow(error); exit(-1); }
/* Explicitly set padding type (necessary?) */
SecTransformSetAttribute(signer,
kSecPaddingKey,
kSecPaddingPKCS1Key,
&error);
if (error) { CFShow(error); exit(-1); }
/* Specify digest type */
SecTransformSetAttribute(signer,
kSecDigestTypeAttribute,
kSecDigestSHA1,
&error);
if (error) { CFShow(error); exit(-1); }
/* OS X calculates SHA1 hash/signature for us */
SecTransformSetAttribute(signer,
kSecTransformInputAttributeName,
plainData,
&error);
if (error) { CFShow(error); exit(-1); }
CFTypeRef signature = SecTransformExecute(signer, &error);
if (error || !signature) { CFShow(error); exit(-1); }
CFRelease(signer);
return signature;
}
and iOS verification code:
+ (NSData *)verifyData:(NSData *)data
usingKey:(SecKeyRef)publicKey {
size_t signatureByteLength = SecKeyGetBlockSize(publicKey);
if (signatureByteLength > data.length)
{
NSLog(#"Signature length is greater that data length.");
return nil;
}
NSData *signature = [data subdataWithRange:NSMakeRange(0, signatureByteLength)];
NSData *plainData = [data subdataWithRange:NSMakeRange(signatureByteLength, data.length - signatureByteLength)];
NSLog(#"signatureLength='%lu', signatureBytes='%#', plainDataLength=%lu", (unsigned long)signature.length, signature, (unsigned long)plainData.length);
size_t hashByteLength = CC_SHA1_DIGEST_LENGTH;
uint8_t* hashBytes = (uint8_t *)malloc(hashByteLength);
if (CC_SHA1([plainData bytes], (CC_LONG)[plainData length], hashBytes))
{
NSData *b = [NSData dataWithBytes:hashBytes length:hashByteLength];
NSLog(#"hashBytesLength='%lu', hashBytes=%#", (unsigned long)b.length, b);
OSStatus status = SecKeyRawVerify(publicKey,
kSecPaddingPKCS1SHA1, // I have also tried kSecPaddingPKCS1, doesn't work
hashBytes,
hashByteLength,
(uint8_t *)[signature bytes],
signatureByteLength);
switch (status)
{
case errSecSuccess:
NSLog(#"SecKeyRawVerify success.");
return plainData;
case errSSLCrypto:
NSLog(#"SecKeyRawVerify failed: underlying cryptographic error");
break;
case errSecParam:
NSLog(#"SecKeyRawVerify failed: one or more parameters passed to a function where not valid.");
break;
default:
NSLog(#"SecKeyRawVerify failed: error code '%d'", (int)status);
break;
}
}
return nil;
}
I created the private and public keys via command line using the following OpenSSL commands:
1. // Generate private and public key pair
openssl genrsa -out rsaPrivate.pem 1024
1a. // Generate public key
openssl rsa -in rsaPrivate.pem -pubout -outform PEM -out rsaPublic.pem
2. //Create a certificate signing request with the private key
openssl req -new -key rsaPrivate.pem -out rsaCertReq.csr
3. //Create a self-signed certificate with the private key and signing request
openssl x509 -req -days 3650 -in rsaCertReq.csr -signkey rsaPrivate.pem -out rsaCert.crt
4. //Convert the certificate to DER format: the certificate contains the public key
openssl x509 -outform der -in rsaCert.crt -out rsaCert.der
Any help is greatly appreciated.
I figured out the issue. The code I posted is correct, but the padding needs to be set as kSecPaddingPKCS1SHA1 per the SecKey.h header file:
If you are verifying a proper PKCS1-style signature, with DER encoding
of the digest type - and the signedData is a SHA1 digest - use
kSecPaddingPKCS1SHA1.
Also, you might want to make sure your public key in .der format is the correct one :)
if (CC_SHA1([plainData bytes], (CC_LONG)[plainData length], hashBytes))
{
NSData *b = [NSData dataWithBytes:hashBytes length:hashByteLength];
NSLog(#"hashBytesLength='%lu', hashBytes=%#", (unsigned long)b.length, b);
...
}
It appears you are not encoding and applying the padding correctly. The encoding and padding gets applied before the hashing. See RFC 3447, Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1 (or the PKCS #1 specification).
I am trying to use an RSA public key to encrypt data within an iOS app to send to a customer's service over the web. The key was generated using openssl:
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.der -outform der
I then try to use the key as follows:
NSString *aPublicKeyPath = [[NSBundle mainBundle] pathForResource:#"public" ofType:#"der"];
NSFileManager* aFileMgr = [NSFileManager defaultManager];
NSData* myCertData = [aFileMgr contentsAtPath: aPublicKeyPath];
CFDataRef myCertDataRef = (__bridge CFDataRef)myCertData;
SecCertificateRef cert = SecCertificateCreateWithData (kCFAllocatorDefault, myCertDataRef);
The contents of the cert load correctly into myCertData, but SecCertificateCreateWithData returns nil without throwing an exception. There is no result code to check, so I don't know why it isn't working.
FYI - The key appears valid. If I check the key, I get this output:
$ openssl rsa -text -in public.der -inform DER -pubin
Modulus (2048 bit):
00:a7:21:90:62:96:15:38:f8:43:5b:33:9f:00:a0:
12:51:63:0e:5c:72:58:05:6a:99:93:d8:6e:f4:d8:
4a:91:cb:dc:74:de:3c:38:63:06:41:5f:63:1e:e9:
70:81:90:e9:f0:e4:78:0a:00:5c:d3:ee:6a:5a:c1:
d7:9a:62:af:f3:45:91:5a:86:bc:aa:8f:86:f5:4f:
3d:01:4b:64:11:31:6f:c1:ab:07:e5:b8:5b:46:8e:
b5:c0:a1:2c:7b:65:1b:09:a9:12:b4:99:f8:77:46:
fa:62:52:04:32:98:09:1c:69:89:52:8c:30:77:af:
51:f2:30:0d:99:33:d3:2b:39:6c:6b:0e:19:ad:67:
10:69:0f:4e:3c:7a:a4:41:bd:87:4e:1d:65:b7:62:
7e:0d:b5:f7:ab:3d:b8:40:f1:c9:8c:c3:1e:9e:7c:
02:67:3a:21:ca:3a:3c:cd:d8:98:fe:4c:ab:30:d4:
f8:2d:3e:78:c3:f0:39:54:15:8a:b7:16:77:ca:3b:
ab:42:5e:70:3e:48:fd:a3:6c:55:5e:66:4b:33:d6:
c0:9c:db:2d:d2:26:78:b8:ca:69:6c:77:6d:38:ab:
77:a0:68:ad:e5:2d:11:50:57:b6:16:dc:b3:e2:82:
bb:07:88:08:a4:87:4c:c6:37:9e:33:ea:5a:f9:b4:
0c:6f
Exponent: 65537 (0x10001)
writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApyGQYpYVOPhDWzOfAKAS
UWMOXHJYBWqZk9hu9NhKkcvcdN48OGMGQV9jHulwgZDp8OR4CgBc0+5qWsHXmmKv
80WRWoa8qo+G9U89AUtkETFvwasH5bhbRo61wKEse2UbCakStJn4d0b6YlIEMpgJ
HGmJUowwd69R8jANmTPTKzlsaw4ZrWcQaQ9OPHqkQb2HTh1lt2J+DbX3qz24QPHJ
jMMennwCZzohyjo8zdiY/kyrMNT4LT54w/A5VBWKtxZ3yjurQl5wPkj9o2xVXmZL
M9bAnNst0iZ4uMppbHdtOKt3oGit5S0RUFe2Ftyz4oK7B4gIpIdMxjeeM+pa+bQM
bwIDAQAB
-----END PUBLIC KEY-----
Any suggestions?
UPDATE:
iOS does not appear to support directly reading a public key as generated above without some serious hacking, see this blog post where public header is manually stripped from key to get it to load. I was able to make this work, but I feel it is not a good approach for me to put into my app.
Finally got a solution I am happy with. As stated in the documentation, the certificate must be in DER format, so I regenerated it using this command and it worked:
$ openssl req -x509 -out public_key.crt -outform DER -new -newkey rsa:4096 -keyout private_key.pem
Then I use the key as follows:
NSString *aPublicKeyPath = [[NSBundle mainBundle] pathForResource:#"public" ofType:#"der"];
NSFileManager* aFileMgr = [NSFileManager defaultManager];
NSData* aCertData = [aFileMgr contentsAtPath: aPublicKeyPath];
SecCertificateRef aCertRef = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)aCertData);
if (aCertRef != NULL) {
SecKeyRef aPublicKeyRef = NULL;
SecTrustRef aTrustRef = NULL;
SecPolicyRef aPolicyRef = SecPolicyCreateBasicX509();
if (aPolicyRef) {
if (SecTrustCreateWithCertificates((CFTypeRef)aCertRef, aPolicyRef, &aTrustRef) == noErr) {
SecTrustResultType result;
if (SecTrustEvaluate(aTrustRef, &result) == noErr) {
aPublicKeyRef = SecTrustCopyPublicKey(aTrustRef);
}
}
}
if (aPolicyRef) CFRelease(aPolicyRef);
if (aTrustRef) CFRelease(aTrustRef);
if (aCertRef) CFRelease(aCertRef);
NSString* aClearTextString = #"This is the test string";
NSString* aEncryptedString = [[self class] encryptRSA:aClearTextString
key:aPublicKeyRef];
}
The encryption routine is:
+(NSString *)encryptRSA:(NSString *)plainTextString key:(SecKeyRef)publicKey
{
size_t cipherBufferSize = SecKeyGetBlockSize(publicKey);
uint8_t *cipherBuffer = malloc(cipherBufferSize);
uint8_t *nonce = (uint8_t *)[plainTextString UTF8String];
SecKeyEncrypt(publicKey,
kSecPaddingOAEP,
nonce,
strlen( (char*)nonce ),
&cipherBuffer[0],
&cipherBufferSize);
NSData *encryptedData = [NSData dataWithBytes:cipherBuffer length:cipherBufferSize];
return [encryptedData base64EncodedStringWithOptions:0];
}
See documentation at https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/#//apple_ref/c/func/SecCertificateCreateWithData
You are trying to load public key. This API expects public key certificate, not public key.
It says
Returns NULL if the data passed in the data parameter is not a valid
DER-encoded X.509 certificate.
So, make public key of it or use other API to load public key.
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.
Generating the key pairs (returns a .der and a .pem)
openssl req -x509 -out public_key.der -outform der -new -newkey rsa:1024 -keyout private_key.pem -days 3650
Encryption (Not sure if this works, but looks like it does)
+ (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;
}
Decryption
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.
Where you are stuck
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);
RSA encryption and decryption on iOS
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);
I have created a public key like this:
NSString *pkFilePath = [[NSBundle mainBundle] pathForResource:#"public_key" ofType:#"der"];
NSData *myCertData = [NSData dataWithContentsOfFile:pkFilePath];
SecCertificateRef cert = SecCertificateCreateWithData (kCFAllocatorDefault, (CFDataRef)myCertData);
CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL);
SecTrustRef trust;
SecPolicyRef myPolicy = SecPolicyCreateBasicX509();
SecTrustCreateWithCertificates(certs, myPolicy, &trust);
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
SecKeyRef pub_key_leaf = SecTrustCopyPublicKey(trust);
NSLog(#"%#",pub_key_leaf);
But how to create private key from file?
Here is some related code to create a private key but I am not getting how to create private key from this using my privatekey file.
https://developer.apple.com/library/mac/#documentation/security/conceptual/CertKeyTrustProgGuide/iPhone_Tasks/iPhone_Tasks.html#//apple_ref/doc/uid/TP40001358-CH208-SW10
AFAIK there is no easy way to create a SecPrivateKeyRef from NSData. If at all possible - combine your private & public file into a p12:
openssl pkcs12 -export -out combined.p12 -in public.der -inform DER -inkey private...
Password: 12345678
and then do
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
#"12345678", kSecImportExportPassphrase,
nil];
OSStatus status = SecPKCS12Import((__bridge CFDataRef)pkcs12derAsData,
(__bridge CFDictionaryRef)options, &items);
In the cases where I had to deal with DER/PEM - I've found it easiest to link with openssl. And take it from there.
Dw.
I had the same problem, it turns out you need to use a p12 file to import the private key.
See my answer here https://stackoverflow.com/a/17295321/480467
I want to recover a public key from a file. Here is the Java code that works:
PublicKey readPubKeyFromFile(AssetFileDescriptor cle) throws IOException {
// read RSA public key
byte[] encodedKey = new byte[(int) cle.getDeclaredLength()];
cle.createInputStream().read(encodedKey);
// create public key
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedKey);
PublicKey pk = null;
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
pk = kf.generatePublic(publicKeySpec);
} catch(Exception e) {
Logger.getInstance().logError("KeyUtils", e.toString());
}
return pk;
}
And here is the iOS code that doesn't work:
-(SecKeyRef)readPublicKeyFromFile:(NSString*)filename andExtension:(NSString*)extension {
NSString* filePath = [[NSBundle mainBundle] pathForResource:filename ofType:extension];
NSData* encodedKey = [NSData dataWithContentsOfFile:filePath];
CFDataRef myCertData = (CFDataRef)encodedKey;
SecCertificateRef cert = SecCertificateCreateWithData (kCFAllocatorSystemDefault, myCertData);
CFArrayRef certs = CFArrayCreate(kCFAllocatorDefault, (const void **) &cert, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
OSStatus check = SecTrustCreateWithCertificates(certs, policy, &trust);
if (check != noErr)
{
NSLog(#"Problem extracting public key from file: %#", filename);
return nil;
}
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
SecKeyRef pub_key_leaf = SecTrustCopyPublicKey(trust);
return pub_key_leaf;
}
Any idea of what is wrong in my iOS code?
I've tested your code and there is nothing wrong with it. The problem seems to be related with the format of the certificates that you are trying to get the public key.
The function SecCertificateCreateWithData() assumes that the certificate that you are providing is in DER format. Most certificates that you find are encoded in base64 like the famous .pem format. I've tested your code with a correctly formatted DER certificate (the certificate form developer.apple.com converted to DER with openssl) and the public key is correctly extracted.
To convert a .pem certificate to DER simply use openssl in terminal:
openssl x509 -in developer.apple.com.pem -outform der -out cert.der
After that the output certificate file should work with no problems with your code.
But you can convert the certificate on the application itself, you only need to grab de x509 base64 encoded certificate (assuming that you are using .pem encoded certificates) and convert it to binary.
There is an example how you can do it:
This code will assume that the certificate is encoded in the following standard:
-----BEGIN CERTIFICATE-----
< your base64 encoded certificate goes here >
-----END CERTIFICATE-----
The code to convert this certificate to binary DER is:
-(NSData *)getBinaryCertificateFromPemEncodedFile:(NSString *)filename andExtension:(NSString *)extension
{
NSString* filePath = [[NSBundle mainBundle] pathForResource:filename ofType:extension];
NSString *pemCert = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
//The header and footer conforms to .pem specificatio
NSString *header = #"-----BEGIN CERTIFICATE-----";
NSString *footer = #"-----END CERTIFICATE-----";
NSString *base64Cert;
NSScanner *scanner = [NSScanner scannerWithString:pemCert];
//First we ignore the header part
[scanner scanString:header intoString:nil];
//Then we copy the base64 string excluding the footer
[scanner scanUpToString:footer intoString:&base64Cert];
//The reason I'm using NSDataBase64DecodingIgnoreUnknownCharacters is to exclude possible line breaks in the encoding
NSData *binaryCertificate = [[NSData alloc] initWithBase64EncodedString:base64Cert options:NSDataBase64DecodingIgnoreUnknownCharacters];
return binaryCertificate;
}
Then a small adaptation in your perfectly functional code does the trick:
-(SecKeyRef)readPublicKeyFromCertificate:(NSData *)binaryCertificate {
NSData *encodedKey = binaryCertificate;
CFDataRef myCertData = (CFDataRef)CFBridgingRetain(encodedKey);
SecCertificateRef cert = SecCertificateCreateWithData(kCFAllocatorSystemDefault, myCertData);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef trust;
//If you only have one certificate you don't need to put it inside an array
OSStatus check = SecTrustCreateWithCertificates(cert, policy, &trust);
if (check != noErr)
{
NSLog(#"Problem extracting public key from certificate");
return nil;
}
SecTrustResultType trustResult;
SecTrustEvaluate(trust, &trustResult);
SecKeyRef pub_key_leaf = SecTrustCopyPublicKey(trust);
return pub_key_leaf;
}
Then just call it:
NSData *data = [self getBinaryCertificateFromPemEncodedFile:#"developer" andExtension:#"pem"];
SecKeyRef key = [self readPublicKeyFromCertificate:data];
NSLog(#"%#", key);
And if your certificate is "valid" you should see:
2014-09-15 21:52:13.275 cert[15813:60b] <SecKeyRef algorithm id: 1,
key type: RSAPublicKey, version: 2, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537},
modulus: BE19E30F47F2D31F27D576CF007B3E615F986D14AFD0D52B825E01E90BA3E1CBB6F3A472E6AECDC28BC13D0B6E58FC497ACF61D80F274E4799602DA4F819E54ADDE2FBFA89FC4EB2172501DDED8DE0FBDDBC5550CC018C73E1FD8152C905DE850862B8D57596025DE1908D8337E95637AF0F52C4A11DA178FF737DCE09471BC0A49DAD7DB39F1BA1B693D3A12F9CA50EF388B50292C73076BF1EEE412A5CFA940E99D4CF07F17FAC87F0D0E2FC8FA3ACDDEEFCCE8AFEC407B94536FCB1E4ACF34773728D189F85EAE4347E0BF868D25C7CE89F8A29B4E6865C68F4F915DFA540549EE9333007145D367FE2852622AAD776F3E5D505A02E5155CC8646A01C1031,
addr: 0x9a48200>
For testing I've used the certificate from developer.apple.com, you can check the public key in the log and compare it.