2009-04-27 222 views
7

我有几个NSString对象表示一个RSA公钥 - 私钥对(不是由SecKeyCreatePair生成,而是由外部加密库生成)。我如何从这些NSString对象创建SecKeyRef对象(这是SecKeyDecrypt/Encrypt方法所必需的)?将RSA密钥导入iPhone钥匙串?

我需要先将它们导入钥匙串吗?如果是这样,怎么样?

谢谢!

+1

我们很早就想到了 - 如果传递正确的字典属性,SecItemAdd()将起作用。请参阅http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931 – Anant 2011-03-14 19:10:41

+0

所有的魔法都在keyData参数中,您从某处(downloadPrivateKeyBundle)获取并解密。这个NSData blob的格式是什么? – Uri 2012-08-20 09:21:32

回答

1

我从the MYcrypto library挖这个代码(BSD许可证)。它似乎是做你想做的。

SecKeyRef importKey(NSData *data, 
        SecExternalItemType type, 
        SecKeychainRef keychain, 
        SecKeyImportExportParameters *params) { 
    SecExternalFormat inputFormat = (type==kSecItemTypeSessionKey) ?kSecFormatRawKey :kSecFormatUnknown; 
    CFArrayRef items = NULL; 

    params->version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; 
    params->flags |= kSecKeyImportOnlyOne; 
    params->keyAttributes |= CSSM_KEYATTR_EXTRACTABLE; 
    if (keychain) { 
     params->keyAttributes |= CSSM_KEYATTR_PERMANENT; 
     if (type==kSecItemTypeSessionKey) 
      params->keyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT; 
     else if (type==kSecItemTypePublicKey) 
      params->keyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP; 
     else if (type==kSecItemTypePrivateKey) 
      params->keyUsage = CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN; 
    } 
    if (!check(SecKeychainItemImport((CFDataRef)data, NULL, &inputFormat, &type, 
            0, params, keychain, &items), 
       @"SecKeychainItemImport")) 
     return nil; 
    if (!items || CFArrayGetCount(items) != 1) 
     return nil; 
    SecKeyRef key = (SecKeyRef)CFRetain(CFArrayGetValueAtIndex(items,0)); 
    CFRelease(items); 
    return key; // caller must CFRelease 
} 
3

因此,在iOS中,钥匙串是沙盒,AFAIK。这意味着,除非您另行指定,否则无论您放入钥匙串,只能通过您的应用程序和您的应用程序访问。您必须在项目设置中启用钥匙串共享功能

既然这样,您肯定可以导入数据。由于它们是NSString对象,因此您首先必须将其转换为NSData对象才能正确导入它们。最有可能的,他们在编码为Base64,所以你必须要扭转:

NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64String options:0]; 

既然这样做了,你可以用这种方法既密钥保存到钥匙链,并获得SecKeyRef:

/** 
* key: the data you're importing 
* keySize: the length of the key (512, 1024, 2048) 
* isPrivate: is this a private key or public key? 
*/ 
- (SecKeyRef)saveKeyToKeychain:(NSData *)key keySize:(NSUInteger)keySize private:(BOOL)isPrivate { 
    OSStatus sanityCheck = noErr; 
    NSData *tag; 
    id keyClass; 

    if (isPrivate) { 
     tag = privateTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPrivate; 
    } 
    else { 
     tag = publicTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPublic; 
    } 

    NSDictionary *saveDict = @{ 
      (__bridge id) kSecClass : (__bridge id) kSecClassKey, 
      (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA, 
      (__bridge id) kSecAttrApplicationTag : tag, 
      (__bridge id) kSecAttrKeyClass : keyClass, 
      (__bridge id) kSecValueData : key, 
      (__bridge id) kSecAttrKeySizeInBits : [NSNumber numberWithUnsignedInteger:keySize], 
      (__bridge id) kSecAttrEffectiveKeySize : [NSNumber numberWithUnsignedInteger:keySize], 
      (__bridge id) kSecAttrCanDerive : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanEncrypt : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanDecrypt : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanVerify : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanSign : (__bridge id) kCFBooleanFalse, 
      (__bridge id) kSecAttrCanWrap : (__bridge id) kCFBooleanTrue, 
      (__bridge id) kSecAttrCanUnwrap : (__bridge id) kCFBooleanFalse 
    }; 

    SecKeyRef savedKeyRef = NULL; 
    sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, (CFTypeRef *)&savedKeyRef); 
    if (sanityCheck != errSecSuccess) { 
     LOGGING_FACILITY1(sanityCheck != noErr, @"Problem saving the key to keychain, OSStatus == %d.", sanityCheck); 
    } 

    return savedKeyRef; 
} 

以后,如果你想从钥匙串获取SecKeyRef,您可以使用此:

- (SecKeyRef)getKeyRef:(BOOL)isPrivate { 
    OSStatus sanityCheck = noErr; 
    NSData *tag; 
    id keyClass; 
    if (isPrivate) { 
     if (privateKeyRef != NULL) { 
      // already exists in memory, return 
      return privateKeyRef; 
     } 
     tag = privateTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPrivate; 
    } 
    else { 
     if (publicKeyRef != NULL) { 
      // already exists in memory, return 
      return publicKeyRef; 
     } 
     tag = publicTag; 
     keyClass = (__bridge id) kSecAttrKeyClassPublic; 
    } 

    NSDictionary *queryDict = @{ 
      (__bridge id) kSecClass : (__bridge id) kSecClassKey, 
      (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA, 
      (__bridge id) kSecAttrApplicationTag : tag, 
      (__bridge id) kSecAttrKeyClass : keyClass, 
      (__bridge id) kSecReturnRef : (__bridge id) kCFBooleanTrue 
    }; 

    SecKeyRef keyReference = NULL; 
    sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef) queryDict, (CFTypeRef *) &keyReference); 
    if (sanityCheck != errSecSuccess) { 
     NSLog(@"Error trying to retrieve key from server. isPrivate: %d. sanityCheck: %li", isPrivate, sanityCheck); 
    } 

    if (isPrivate) { 
     privateKeyRef = keyReference; 
    } 
    else { 
     publicKeyRef = keyReference; 
    } 
    return keyReference; 
} 
3

编辑:使用下面的方法,我们能够导入密钥高达4096尺寸的任何RSA键更大这似乎被钥匙扣拒绝了。我们取得了成功的地位,但我们没有得到关键的参考。

只是关于导入RSA私钥/公钥的快速说明。就我而言,我需要导入由OpenSSL生成的私钥。

This project做了我想要的大部分,只要把它放入钥匙串。正如你所看到的,它只是有一个关键数据,你可以将关键数据放到关键数据中,钥匙串可以从关键字中计算出数据块的大小等。钥匙串支持ASN.1编码密钥。

将密钥导出到文件时,很可能是PEM文件。一个PEM文件只是一个base64编码的DER结构。 DER结构是一个通用结构,但在OpenSSL的情况下,它通常是ASN.1编码的私钥或公钥。

ASN.1结构显示得很好here。请在阅读并理解如何阅读ASN.1结构之前,尝试并摆弄它,否则导入其他大小的密钥将失败。

我显然没有足够的“声望”发布超过2个链接。因此,对于下面的示例粘贴base64信息(除了--- BEGIN * KEY ---和--- END * KEY --- ---之外的所有内容:lapo.it/asn1js

如果你看起来像iOS项目我链接了,你会看到它们包含了示例密钥,将私钥粘贴到ASN.1解码器中,你会注意到你有一个SEQUENCE标记,后面跟着几个INTEGER值

现在粘贴在公钥中。你会注意到公钥与私钥有两个共同的信息:模数和指数,私钥是第二个和第三个INTEGER值,它的顶部也有一些信息。 2个额外的SEQUENCE,一个OBJECT ID,NULL和BIT STRING标签。

您还会在项目中注意到他调用了一个特殊函数来处理该公钥。它所做的是去除所有标题信息,直到它到达最内层的SEQUENCE标记。此时,他将其视为完全像私钥,并将其放入钥匙串中。

为什么要这样做,而不是其他?查看页眉和页脚文本。私钥说--- BEGIN RSA PRIVATE KEY ---,公钥说--- BEGIN PUBLIC KEY ---。您将在公钥中看到的对象ID是:1.2.840.113549.1.1.1。这是一个ID,它是一个静态标记,用于将所包含的密钥标识为RSA类型密钥。

由于私钥在前导码中有RSA,因此它假定它是一个RSA密钥,并且该头ASN.1信息不需要标识该密钥。公钥只是一个通用密钥,所以需要标头来标识它是什么类型的密钥。

钥匙串不会导入带有此ASN.1标头的RSA密钥。您需要将它一直剥离到最后一个SEQUENCE。此时你可以将它放入钥匙串中,钥匙串可以派生出块大小和其他关键属性。

因此,如果BEGIN RSA PRIVATE KEY在那里,则不需要进行剥离。如果是 - BEGIN PRIVATE KEY ---,则需要在将这些首标头放入钥匙串之前将其剥掉。

就我而言,我还需要公钥。一旦我们把私钥成功放入(我们可能错过了某些东西),我们无法想象我们能从钥匙串中获得它,所以我们实际上使用私钥创建了一个ASN.1公钥,并将其导入到keycahin中。在私钥(在ASN.1头部剥离之后),你将有一个SEQUENCE标记,随后是3个INTEGER标记(在这之后有更多的INTEGERS,但前3个是我们所关心的)。

第一个是VERSION标签。第二个是模数,第三个是公开指数。

查看公钥(在ASN.1标头剥离后)您会看到SEQUENCE后跟2个INTEGERS。你猜对了,这是私钥的模数和公开指数。

因此,所有你需要做的是:

  1. 抓住从私有密钥
  2. 模数和公开指数在缓冲区中创建一个序列标签及其长度设定为[模长度] + [指数长度]。 (将这些字节写入缓冲区时,您很可能需要反转字节的字节序,至少我做了。)
  3. 添加您的私钥
  4. 抓起模量数据添加你的私钥

抓起指数数据这就是所有你需要做的,创建你的私有密钥导入了公钥。似乎没有太多的信息用于导入您不在设备上生成的RSA密钥,并且我听说设备上生成的密钥不包含这些ASN.1标头,但我从未试过。我们的钥匙非常大,需要很长时间才能生成。我发现的唯一选择是使用OpenSSL,在那里你必须编译你自己的iOS版本。我宁愿在可能的情况下使用安全框架。

我还是比较新的iOS开发,我敢肯定有人知道一个简单的函数,做所有这些,我找不到,我看。这似乎工作正常,直到一个更简单的API可用于处理密钥。

最后一点:项目中包含的私钥具有BIT STRING标签,但是我从一个OpenSSL生成的私钥导入的私钥具有标签OCTET STRING。