1

我有一个私钥。以“--- begin private key...”开头的文本文件

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

这可以在没有任何外部框架的情况下完成吗?

结果应该等同于 php openssl_sign 函数。

4

2 回答 2

4

您需要使用的 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.a到iOS 项目的Project Navigatorlibssl.a中的Frameworks组。在项目的Build Settings中,将以下内容添加到Header Search Paths设置中:

~/openssl-ios/include/include

接下来,创建一个名为该类的新Objective-C Category文件。在中,定义如下接口:openssl_signNSStringNSString+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'
于 2013-10-26T16:10:36.577 回答
1

无需外部资源或组件,您就可以更轻松地解决这个问题。

我发现了如何并想分享它,以便我可以帮助其他人。

  1. 您需要加载密钥文件 SecKeyRef 并保护 maxPlainLen
    NSString *resourcePath = [[NSBundle mainBundle] pathForResource:privateKeyResourceName ofType:@"p12"];
    NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];

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

    SecKeyRef privateKeyRef = NULL;

    //更改为您在此处使用的实际密码
    [选项 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);
        如果(安全错误!= noErr){
            privateKeyRef = NULL;
        }
    }
    CFR 释放(项目);

    私有密钥 = 私有密钥引用;
    maxPlainLen = SecKeyGetBlockSize(privateKey) - 12;
  1. 您可以使用类别方法将 NSString 转换为 SHA1
    - (NSData*)toSha1AsData {
        // PHP 使用 ASCII 编码,而不是 UTF
        const char *s = [self cStringUsingEncoding:NSASCIIStringEncoding];
        NSData *keyData = [NSData dataWithBytes:s 长度:strlen(s)];

        // 这是目的地
        uint8_t 摘要[CC_SHA1_DIGEST_LENGTH] = {0};
        // 这个函数对你的散列数据进行无键的 SHA1 散列
        CC_SHA1(keyData.bytes, keyData.length, 摘要);

        // 现在转换为 NSData 结构以使其再次可用
        NSData *out = [NSData dataWithBytes:摘要长度:CC_SHA1_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; }

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

于 2014-10-03T18:42:06.447 回答