Question

i have a private key. Text File that begins like "--- begin private key..."

i want to use that key to encrypt an NSString. since its the private key, better call it sign an NSString.

can this be done without any external frameworks?

the result should be equivalent to the php openssl_sign function.

Was it helpful?

Solution 2

You can solve this much easier with no external sources or components.

I found out how and wanted to share it so i may help others.

  1. You need to load the key file a SecKeyRef and safe the maxPlainLen as well
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:privateKeyResourceName ofType:@"p12"];
    NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];

    NSMutableDictionary * options = [[NSMutableDictionary alloc] init];

    SecKeyRef privateKeyRef = NULL;

    //change to the actual password you used here
    [options setObject:@"_YOURPASSWORDHERE__" forKey:(__bridge id)kSecImportExportPassphrase];

    CFArrayRef items = 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, &privateKeyRef);
        if (securityError != noErr) {
            privateKeyRef = NULL;
        }
    }
    CFRelease(items);

    privateKey = privateKeyRef;
    maxPlainLen = SecKeyGetBlockSize(privateKey) - 12;
  1. You can convert NSString with a category method to SHA1
    - (NSData*)toSha1AsData {
        // PHP uses ASCII encoding, not UTF
        const char *s = [self cStringUsingEncoding:NSASCIIStringEncoding];
        NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];

        // This is the destination
        uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
        // This one function does an unkeyed SHA1 hash of your hash data
        CC_SHA1(keyData.bytes, keyData.length, digest);

        // Now convert to NSData structure to make it usable again
        NSData *out = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]

        return out;
    }
  1. Now you can sign your SHA1 with this method

(NSData *)signSha1Data:(NSData *)data { size_t plainLen = [data length]; if (plainLen > maxPlainLen) { NSLog(@"content(%ld) is too long, must < %ld", plainLen, maxPlainLen); return nil; } void *plain = malloc(plainLen); [data getBytes:plain length:plainLen]; size_t cipherLen = 128; // currently RSA key length is set to 128 bytes void *cipher = malloc(cipherLen); OSStatus returnCode = SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1, plain, plainLen, cipher, &cipherLen); NSData *result = nil; if (returnCode != 0) { NSLog(@"SecKeyEncrypt fail. Error Code: %ld", returnCode); } else { result = [NSData dataWithBytes:cipher length:cipherLen]; } free(plain); free(cipher); return result; }

It works very well and without any external libs. There is no need to compile some wierd openssl stuff.

OTHER TIPS

The iOS SDK framework you will need to use is called CommonCrypto. Here's a very good article that describes the right way to go about it.

Edit: I missed the part about compatibility with the PHP function openssl_sign. The solution below resolves that.

The way to do this so that it's compatible with the PHP function openssl_sign is to use the OpenSSL library. The openssl_sign function uses OpenSSL's EVP API internally to encrypt the input string using the private key and compute the SHA-1 hash digest of that encrypted string. It's common then to convert this hash digest into a Base64-encoded string.


Unfortunately, the iOS SDK does not include OpenSSL, but it's easy to build it. The following instructions for building OpenSSL for iOS are taken from this blog post and are reproduced here to provide a complete solution to the question.

In Terminal, follow those steps to build the OpenSSL library for iOS:

# Make a directory in which to run the build
mkdir ~/openssl-ios
cd ~/openssl-ios

# Download the openssl source (verify the file before using it in production!)
curl -O http://www.openssl.org/source/openssl-1.0.1e.tar.gz

# Download the openssl iOS build script
curl -O https://raw.github.com/Raphaelios/raphaelios-scripts/master/openssl/build-openssl.sh

# Make the build script executable
chmod +x build-openssl.sh

# Run the script (takes about 3min on an Intel Core i5)
./build-openssl.sh

This will take a few minutes but once it's complete you can verify that the build library is a universal library that you can use on iOS devices and in the iOS Simulator using the following command:

lipo -info ~/openssl-ios/lib/*.a

Now that the OpenSSL library has been built, let's got on with writing the code to sign a string.


First, we need to setup the Xcode project to link against the OpenSSL library. Drag & drop both libcrypto.a and libssl.a to the Frameworks group in the Project Navigator of your iOS project. In your project's Build Settings, add the following to the Header Search Paths setting:

~/openssl-ios/include/include

Next, create a new Objective-C Category file called openssl_sign on the NSString class. In NSString+openssl_sign.h, define the following interface:

@interface NSString (openssl_sign)

- (NSString *)signStringWithPrivateKey:(NSData *)privateKey;

@end

In NSString+openssl_sign.m, add the following header imports:

#import <openssl/evp.h>
#import <openssl/pem.h>

And add the following implementation of signStringWithPrivateKey::

@implementation NSString (openssl_sign)

- (NSString *)signStringWithPrivateKey:(NSData *)privateKeyData
{
    BIO *publicBIO = NULL;
    EVP_PKEY *privateKey = NULL;

    if ((publicBIO = BIO_new_mem_buf((unsigned char *)[privateKeyData bytes], [privateKeyData length])) == NO) {
        NSLog(@"BIO_new_mem_buf() failed!");
        return nil;
    }

    if (PEM_read_bio_PrivateKey(publicBIO, &privateKey, NULL, NULL) == NO) {
        NSLog(@"PEM_read_bio_PrivateKey() failed!");
        return nil;
    }

    const char * cString = [self cStringUsingEncoding:NSUTF8StringEncoding];
    unsigned int stringLength = [self length];

    unsigned char * signatureBuffer[EVP_MAX_MD_SIZE];
    int signatureLength;

    EVP_MD_CTX msgDigestContext;
    const EVP_MD * msgDigest = EVP_sha1();

    EVP_MD_CTX_init(&msgDigestContext);
    EVP_SignInit(&msgDigestContext, msgDigest);
    EVP_SignUpdate(&msgDigestContext, cString, stringLength);

    if (EVP_SignFinal(&msgDigestContext, (unsigned char *)signatureBuffer, (unsigned int *)&signatureLength, privateKey) == NO) {
        NSLog(@"Failed to sign string.");
        return nil;
    }

    EVP_MD_CTX_cleanup(&msgDigestContext);
    EVP_PKEY_free(privateKey);

    NSData *signatureData = [NSData dataWithBytes:signatureBuffer length:signatureLength];
    NSString *signature = [signatureData base64EncodedStringWithOptions:0];

    return signature;
}

@end

In the class that will be signing the string, you can now import NSString+openssl_sign.h and sign the string like so:

NSData *privateKey = ...; // Read the .pem file into a NSData variable
NSString *helloSignature = [@"hello" signStringWithPrivateKey:privateKey];

You can verify that the signatures are the same using the following command in Terminal:

echo -n "hello" | openssl dgst -sha1 -sign priv.pem | openssl enc -base64 | tr -d '\n'
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top