将RSA密钥导入iPhone钥匙串?
我有几个NSString对象表示一个RSA公钥 - 私钥对(不是由SecKeyCreatePair生成,而是由外部加密库生成)。我如何从这些NSString对象创建SecKeyRef对象(这是SecKeyDecrypt/Encrypt方法所必需的)?将RSA密钥导入iPhone钥匙串?
我需要先将它们导入钥匙串吗?如果是这样,怎么样?
谢谢!
答案是打电话SecItemAdd
与正确的标志集。请参阅:http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931
我从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
}
因此,在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;
}
编辑:使用下面的方法,我们能够导入密钥高达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。你猜对了,这是私钥的模数和公开指数。
因此,所有你需要做的是:
- 抓住从私有密钥
- 模数和公开指数在缓冲区中创建一个序列标签及其长度设定为[模长度] + [指数长度]。 (将这些字节写入缓冲区时,您很可能需要反转字节的字节序,至少我做了。)
- 添加您的私钥
- 抓起模量数据添加你的私钥
抓起指数数据这就是所有你需要做的,创建你的私有密钥导入了公钥。似乎没有太多的信息用于导入您不在设备上生成的RSA密钥,并且我听说设备上生成的密钥不包含这些ASN.1标头,但我从未试过。我们的钥匙非常大,需要很长时间才能生成。我发现的唯一选择是使用OpenSSL,在那里你必须编译你自己的iOS版本。我宁愿在可能的情况下使用安全框架。
我还是比较新的iOS开发,我敢肯定有人知道一个简单的函数,做所有这些,我找不到,我看。这似乎工作正常,直到一个更简单的API可用于处理密钥。
最后一点:项目中包含的私钥具有BIT STRING标签,但是我从一个OpenSSL生成的私钥导入的私钥具有标签OCTET STRING。
我们很早就想到了 - 如果传递正确的字典属性,SecItemAdd()将起作用。请参阅http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931 – Anant 2011-03-14 19:10:41
所有的魔法都在keyData参数中,您从某处(downloadPrivateKeyBundle)获取并解密。这个NSData blob的格式是什么? – Uri 2012-08-20 09:21:32