目标C:无法从PEM私钥获取SecKeyRef

时间:2013-10-31 02:47:49

标签: ios objective-c security encryption pem

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

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

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

顺便说一句,这里没有涉及证书,它只是简单的密钥。 这就是我正在做的事情:

  1. 阅读PEM私钥和Base64解码。
  2. 使用SecItemCopyMatching从解码后的字符串生成SecKeyRef。
  3. 使用SecKeyDecrypt解密。
  4. 我的问题是步骤#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提供的加密域知识和支持,但我根本不做任何iOS编程,这是一次性的事情。

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

    TIA。

3 个答案:

答案 0 :(得分:6)

不幸的是,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
  5. 复制私钥

    这样做的功能是:

    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
    }
    

    假设"数据"是NSData的实例,其中包含PKCS12数据和"密码"是表示密码的NSString实例。成功" privateKey"使用从PKCS12数据导入的私钥填充。

答案 1 :(得分: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;
    }
    
  6. 希望这会有所帮助!!!!!

答案 2 :(得分:0)

您已将私钥和证书存储在钥匙串中。否则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