使用Objective C在iOS中使用公钥加密或签名字符串



我有一个私钥。以"---开始私钥…"开头的文本文件

我想用那个密钥加密一个NSString。既然它是私钥,最好称之为NSString签名。

这能在没有任何外部框架的情况下实现吗?

结果应该相当于phpopensslsign函数。

您需要使用的iOS SDK框架称为CommonCrypto。这是一篇非常好的文章,描述了正确的方法。

编辑:我错过了与PHP函数openssl_sign兼容的部分。下面的解决方案解决了这个问题

要使其与PHP函数openssl_sign兼容,方法是使用OpenSSL库。openssl_sign函数在内部使用OpenSSL的EVP API来使用私钥加密输入字符串,并计算该加密字符串的SHA-1哈希摘要。通常将这个散列摘要转换为Base64编码的字符串。


不幸的是,iOS SDK不包括OpenSSL,但构建它很容易。以下构建iOS版OpenSSL的说明摘自本博客文章,并在此处转载,以提供该问题的完整解决方案。

在终端中,按照以下步骤构建适用于iOS的OpenSSL库:

# 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

这将需要几分钟的时间,但一旦完成,您可以使用以下命令验证构建库是否是可以在iOS设备和iOS模拟器中使用的通用库:

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

现在已经构建了OpenSSL库,让我们继续编写对字符串进行签名的代码。


首先,我们需要设置Xcode项目以链接到OpenSSL库。拖动&将libcrypto.alibssl.a都放到iOS项目的项目导航器中的Frameworks组中。在项目的构建设置中,将以下内容添加到标题搜索路径设置中:

~/openssl-ios/include/include

接下来,在NSString类上创建一个名为openssl_sign的新Objective-C Category文件。在NSString+openssl_sign.h中,定义以下接口:

@interface NSString (openssl_sign)
- (NSString *)signStringWithPrivateKey:(NSData *)privateKey;
@end

NSString+openssl_sign.m中,添加以下标头导入:

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

并添加以下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

在将对字符串进行签名的类中,现在可以导入NSString+openssl_sign.h并对字符串进行如下签名:

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

您可以在终端中使用以下命令验证签名是否相同:

echo -n "hello" | openssl dgst -sha1 -sign priv.pem | openssl enc -base64 | tr -d 'n'

您可以在没有外部源或组件的情况下更容易地解决此问题。

我发现了如何分享它,这样我就可以帮助别人。

  1. 您需要加载密钥文件SecKeyRef,并保护maxPlainLen
NSString*resourcePath=[[NSBundle mainBundle]pathForResource:privateKeyResourceType:@"p12"];NSData*p12Data=[NSData数据WithContentsOfFile:resourcePath];NSMutableDictionary*选项=[[NSMutaableDictionary alloc]init];SecKeyRef privateKeyRef=NULL;//更改为您在此处使用的实际密码[options setObject:@"_YOURPASSWORDHERE_"forKey:(__bridge id(kSecImportExportPassphrase];CFArrayRef项=CFArrayCreate(NULL,0,0,NULL(;OSStatus securityError=SecPKCS12Import(__bridge CFDataRef(p12数据,(__bridge CFDictionaryRef(选项,&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;}}CF发布(项(;privateKey=privateKeyRef;maxPlainLen=SecKeyGetBlockSize(私钥(-12;
  1. 您可以使用category方法将NSString转换为SHA1
-(NSData*(到Sha1AsData{//PHP使用ASCII编码,而不是UTFconst char*s=[self-cStringUsingEncoding:NSASIIStringEncoding];NSData*keyData=[NSData dataWithBytes:s length:strlen(s(];//这是目的地uint8_t摘要[CC_SHA1_IGEST_LENGTH]={0};//这一个函数对您的哈希数据进行未加密的SHA1哈希CC_SHA1(keyData.bytes,keyData.length,摘要(;//现在转换为NSData结构,使其再次可用NSData*out=[NSData dataWithBytes:摘要长度:CC_SHA_digest_length]退出;}
  1. 现在您可以使用此方法对SHA1进行签名

(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;
}

它工作得很好,没有任何外部libs。没有必要编译一些wierd-openssl的东西。

最新更新