【问题标题】:Objective C: Unable to fetch SecKeyRef from PEM private key目标 C:无法从 PEM 私钥获取 SecKeyRef
【发布时间】:2013-10-31 02:47:49
【问题描述】:

我是 Objective C 和 iOS 编程的新手。

我正在使用使用 openssl 生成的简单公钥/私钥(PEM 格式)来加密和解密需要在服务器和客户端之间交换的数据。我成功地在 Java 服务器和客户端中工作。

当我在 Java 中使用公钥加密数据并在 Objective C / iOS 中使用私钥解密时,问题就开始了。我查看了一些示例并将一些代码放在一起,但是当我一直调用 SecItemCopyMatching 作为从私钥创建 SecKeyRef 的一部分时,我得到了一个错误 -25300。

顺便说一句,这里不涉及证书,它只是普通密钥。 这是我正在做的事情:

  1. 读取 PEM 私钥和 Base64 解码。
  2. 使用 SecItemCopyMatching 从解码的字符串生成 SecKeyRef。
  3. 使用 SecKeyDecrypt 解密。

我的问题是第 2 步,它返回的状态为 -25300 (errSecItemNotFound –25300
找不到该项目。适用于 iOS 2.0 及更高版本。)

这是我生成 SecKeyRef 的代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSString *challenge = @"2KFqc46DNSWrizzv69lJN25o62xEYQw/QLcMiT2V1XLER9uJbOu+xH2qgTuNWa1HZ9SW3Lq+HovtkhFmjmf08QkVQohHmxCJXVyCgVhPBleScAgQ8AoP3tmV0RqGb2mJrb19ybeYP7uZ2piVtF4cRwU1gO3VTooCUK3cX4wS7Tc=";
NSLog(@"challenge, %@", challenge);

NSData *incomingData = [self base64DataFromString:challenge];
uint8_t *challengeBuffer = (uint8_t*)[incomingData bytes];
NSLog(@"challengeBuffer: %s", challengeBuffer);

[self decryptWithPrivateKey:challengeBuffer];

free(challengeBuffer);

return YES;
}

// Generate a SecKeyRef from the private key in the private.pem file.
- (SecKeyRef)getPrivateKeyRef {
NSString *startPrivateKey = @"-----BEGIN RSA PRIVATE KEY-----";
NSString *endPrivateKey = @"-----END RSA PRIVATE KEY-----";
NSString* path = [[NSBundle mainBundle] pathForResource:@"private"
                                                 ofType:@"pem"];
NSString* content = [NSString stringWithContentsOfFile:path
                                              encoding:NSUTF8StringEncoding
                                                 error:NULL];
NSLog(@"Private Key: %@", content);

NSString *privateKey;
NSScanner *scanner = [NSScanner scannerWithString:content];
[scanner scanUpToString:startPrivateKey intoString:nil];
[scanner scanString:startPrivateKey intoString:nil];
[scanner scanUpToString:endPrivateKey intoString:&privateKey];

NSData *privateTag = [self dataWithBase64EncodedString:privateKey];
NSLog(@"Decoded String: %@", privateTag);

OSStatus status = noErr;
SecKeyRef privateKeyReference = NULL;

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

// Set the private key query dictionary.
[queryPrivateKey setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
[queryPrivateKey setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
[queryPrivateKey setObject:privateTag forKey:(__bridge id)kSecAttrApplicationTag];
[queryPrivateKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];
//[queryPrivateKey setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnRef];


// Get the key.
status = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKeyReference);
NSLog(@"status: %ld", status);

if(status != noErr)
{
    privateKeyReference = NULL;
}

return privateKeyReference;
}

// Decrypt data
- (void)decryptWithPrivateKey:(uint8_t *)cipherBuffer {
OSStatus status = noErr;

SecKeyRef privateKeyRef = [self getPrivateKeyRef];

size_t plainBufferSize = SecKeyGetBlockSize(privateKeyRef);
uint8_t *plainBuffer = malloc(plainBufferSize);

size_t cipherBufferSize = strlen((char *)cipherBuffer);
NSLog(@"decryptWithPrivateKey: length of input: %lu", cipherBufferSize);

//  Error handling
status = SecKeyDecrypt(privateKeyRef,
                       PADDING,
                       cipherBuffer,
                       cipherBufferSize,
                       &plainBuffer[0],
                       &plainBufferSize
                       );
NSLog(@"decryption result code: %ld (size: %lu)", status, plainBufferSize);
NSLog(@"FINAL decrypted text: %s", plainBuffer);
}

这几天我一直在头疼,我觉得我需要在这里寻求帮助。任何人任何指针?我可以花更多时间获得 iOS 提供的 Crypto 领域知识和支持,但我根本不做任何 iOS 编程,这是一次性的事情。

我只需要一些方向,我很难让它发挥作用。

TIA。

【问题讨论】:

  • 你有没有得到这个工作?同样的问题。
  • 我仍然面临同样的问题。你搞定了吗?
  • 您只能使用SecItemCopyMatching 检索您之前添加到钥匙串中的项目。由于您没有添加任何内容,因此没有可检索的内容。

标签: ios objective-c security encryption pem


【解决方案1】:

不幸的是,iOS 上的安全框架要求私钥采用 PKCS12 格式,并带有密码。公钥可以是 X509 装甲 DER 或 PKCS12,但私钥必须是 PKCS12。您尝试使用的私钥是 PEM 格式的 RSA 密钥。

如果您有权访问密钥,则可以使用 openssl command line tools 进行转换:

openssl pkcs12 -export -nocerts -inkey privatekey.pem -out privatekey.p12

这将使用私钥创建一个 PKCS12 文件,并且需要密码。如果您无法控制私钥(例如,如果它来自外部来源,例如服务器),那么您就不走运了。

但是让我们假设您能够执行上述步骤将麻烦的 PEM RSA 私钥转换为 PKCS12。 从 PKCS12 数据中提取私钥并不难:

  1. 将 PKCS12 加载为 NSData。如果这是文件系统上的资源,您可以使用 dataWithContentsOfURL: 执行此操作。
  2. 使用SecPKCS12Import 导入带有密码的PKCS12 数据。
  3. 从导入的项目中提取SecIdentityRef
  4. SecIdentityRef复制私钥

这样做的功能是:

OSStatus    SecKeyPrivatePKCS12Import(CFDataRef keyData, CFStringRef passphrase, SecKeyRef *privateKey){
    OSStatus        status              = errSecSuccess;
    CFDictionaryRef secImportOptions    = NULL;
    CFArrayRef      secImportItems      = NULL;

    if ((keyData != NULL) && (CFStringGetLength(passphrase) > 0) ){
        const void *keys[] = { kSecImportExportPassphrase };
        const void *values[] = { passphrase };

        secImportOptions = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);

        status = SecPKCS12Import((CFDataRef) keyData, (CFDictionaryRef)secImportOptions, &secImportItems);
        if (CFArrayGetCount(secImportItems) > 0){
            CFDictionaryRef identityDict = CFArrayGetValueAtIndex(secImportItems, 0);
            SecIdentityRef identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
            SecIdentityCopyPrivateKey(identityApp, privateKey);
        }
    }

    return status;
}

从 Objective-C 调用它看起来像:

OSStatus status = errSecSuccess;

status = SecKeyPrivatePKCS12Import((_bridge CFDataRef)data, (_bridge CFStringRef)passphrase, &privateKey);
if (privateKey == NULL){
   // Check the status value for why it failed
}

假设“data”是包含 PKCS12 数据的 NSData 实例,而“passphrase”是代表密码短语的 NSString 实例。成功时,“privateKey”将填充从 PKCS12 数据导入的私钥。

【讨论】:

    【解决方案2】:

    我在使用 java 服务器和 iPhone 应用程序时遇到了同样的问题,我的解决方法如下。

    1. 在 java 服务器上生成 p12。 [记得记下密码。]
    2. 将 p12 文件的原始字节转换为 base 64 字符串。
    3. 将这些数据发送到 iOS 应用程序,无论你想要什么。

      3.1 您可以将 base 64 放入文本文件并将其发送到 iOS。 [在我的情况下,最安全的方式和工作正常。]

      3.2 您可以使用 JSON 字符串来发送该字符串。 [这可能会损坏您的数据。]

    4. 在 iPhone 应用程序上获取数据后,将 base 64 字符串转换为 NSData。 NSData+Base64
    5. 使用以下方法获取您的私钥的 SecKeyRef。

      - (SecKeyRef)getPrivateKeyFromData:(NSData *)p12Data withPassword:(NSString *)password {
          NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
          SecKeyRef privateKey = NULL;
          [options setObject:password forKey:(__bridge id)kSecImportExportPassphrase];
          CFArrayRef items = NULL;// = 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, &privateKey);
          if (securityError != noErr) {
                  privateKey = NULL;
              }
          }
          //NSLog(@"-------------------- Private Key Error %d",(int)securityError);
          CFRelease(items);
          options = nil;
          p12Data = nil;
          password = nil;
          return privateKey;
      }
      

    希望这会有所帮助!!!!!!!

    【讨论】:

    • 感谢@Pratik 的回答,但我得到了字符串格式的密钥。字符串用base64编码,然后具有这种格式:@"——BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC0HPYiPItBtjJNky...-----END RSA PRIVATE KEY——";我没有 p12 及其密码。
    • +1 @Pratik。我用示例 .p12 文件尝试了您的代码,并且工作正常。
    • @SaurabhShukla 我很乐意帮助别人。抱歉,我无法回复之前的问题。
    【解决方案3】:

    您已将私钥和证书存储在钥匙串中。否则SecItemCopyMatching 不会做任何事情。您只需导入一次。

    /* importing client identity (private key) */
    NSData* certificateData = ... ; // decoded pkcs21 certificate from base64 pem
    NSString* passcode = @"passphrased used to encrypt the private key";
    CFDictionaryRef optionsDictionary = (__bridge CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys: passcode,  kSecImportExportPassphrase, nil];
    CFArrayRef certificates;
    OSStatus error = SecPKCS12Import((__bridge CFDataRef) certificateData, optionsDictionary, &certificates);
    CFDictionaryRef myIDs = CFArrayGetValueAtIndex(certificates, 0); 
    
    SecIdentityRef identity = (SecIdentityRef) CFDictionaryGetValue(myIDs, kSecImportItemIdentity);
    
    NSDictionary* clientCertificateQuery = @{(__bridge id)kSecValueRef        : identity,
                                             (__bridge id)kSecAttrLabel       : @"some label you can use to find the item again with SecItemCopyMatching"};
    OSStatus err = SecItemAdd((__bridge CFDictionaryRef) clientCertificateQuery, NULL);
    

    然后您可以稍后使用SecItemCopyMatching 获取身份,并使用SecIdentityCopyPrivateKey 获取私钥。

    NSDictionary* clientCertificateQuery = @{(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll,
                                             (__bridge id)kSecClass      : (__bridge id)kSecClassIdentity,
                                             (__bridge id)kSecReturnRef   : (__bridge id)kCFBooleanTrue};
    SecIdentityRef identity = NULL;
    OSStatus errorCode = SecItemCopyMatching((__bridge CFDictionaryRef) clientCertificateQuery, &identity);
    SecKeyRef privateKeyRef;
    OSStatus err = SecIdentityCopyPrivateKey (identity, &privateKeyRef);
    

    始终检查 OSStatus 错误,因为您肯定会遇到errSecDuplicateItem

    请务必阅读 Apple 的 Certificate, Key, and Trust Services Reference

    【讨论】:

    • 嗨@orkoden。感谢您的回答,但我得到了字符串格式的密钥。字符串用base64编码,然后具有这种格式:@"——BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQC0HPYiPItBtjJNky...-----END RSA PRIVATE KEY——";我将如何从这些数据中生成 pkcs21 数据?
    猜你喜欢
    • 2014-08-19
    • 1970-01-01
    • 2015-07-09
    • 2013-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-05
    相关资源
    最近更新 更多