我有几个代表RSA公私密钥对的NSString对象(不是由SecKeyCreatePair生成的,而是由外部加密库生成的)。如何从这些NSString对象创建SecKeyRef对象(SecKeyDecrypt / Encrypt方法所需)?
我是否需要先将它们导入钥匙串?如果是这样,怎么样?
谢谢!
答案 0 :(得分:4)
所以在iOS中,钥匙串是沙盒,AFAIK。这意味着除非您另行指定,否则只有您的应用和应用程序才能访问您放入钥匙串的任何内容。您必须在项目设置中的功能下启用 Keychain Sharing 。
现在已经不在了,您当然可以导入数据。由于它们是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;
}
答案 1 :(得分:3)
编辑:使用下面的方法我们能够导入大小为4096的密钥。任何大于此的RSA密钥大小似乎都被密钥链拒绝。我们取回了成功状态,但我们没有获得对密钥的引用。
关于导入RSA私钥/公钥的快速说明。就我而言,我需要导入OpenSSL生成的私钥。
This project做了我想要的大部分内容,只要把它放入钥匙串即可。正如你所看到的那样,它只有一个keydata,你可以将密钥数据推入其中,而keychain可以从密钥中找出块大小等。 Keychain支持ASN.1编码密钥。
将密钥导出到文件时,很可能是PEM文件。 PEM文件只是base64编码的DER结构。 DER结构是一种通用结构,但在OpenSSL的情况下,它通常是ASN.1编码的私钥或公钥。
ASN.1结构很好地显示here。请在阅读和理解之前阅读并理解如何阅读ASN.1结构,否则导入其他大小的密钥将失败。
我显然没有足够的声誉和#34;发布超过2个链接。因此,对于以下示例,粘贴base64信息(除了--- BEGIN * KEY ---和--- END * KEY --- at:lapo.it/asn1js。
如果您查看我链接的iOS项目,您会看到它们包含示例键。将私钥粘贴到ASN.1解码器中。您会注意到您有一个SEQUENCE标记,后跟几个INTEGER值。
现在粘贴公钥。您会注意到公钥与私钥有两条共同的信息。模数和指数。在私钥中,这是第二个和第三个INTEGER值。它顶部还有一些信息。它有2个额外的SEQUENCE,一个OBJECT ID,NULL和BIT STRING标签。
您还会在项目中注意到他调用了一个特殊的函数来处理该公钥。它的作用是删除所有标题信息,直到它到达最里面的SEQUENCE标记。此时,他将其视为私钥,并将其置于钥匙串中。
为什么这个为一个而不是另一个?查看页眉和页脚文本。私钥说--- BEGIN RSA PRIVATE KEY ---,公钥说 - 开始公钥---。您将在公钥中看到的对象ID是:1.2.840.113549.1.1.1。这是一个ID,它是一个静态标记,用于将包含的密钥标识为RSA类型密钥。
由于私钥在前导码中具有RSA,因此它假定它是RSA密钥,并且不需要标头ASN.1信息来识别密钥。公钥只是一个通用密钥,因此需要一个标头来标识它是什么类型的密钥。
Keychain不会导入带有此ASN.1标头的RSA密钥。你需要将它一直剥离到最后一个SEQUENCE。此时,您可以将其放入钥匙串中,钥匙串可以导出块大小和其他关键属性。
因此,如果存在BEGIN RSA PRIVATE KEY,则无需进行剥离。如果它是--BEGIN PRIVATE KEY ---,你需要在将它们放入钥匙串之前去掉那些初始标题。
就我而言,我还需要公钥。一旦我们成功地放入私钥(我们可能刚刚错过了某些东西),我们无法想出从钥匙串获取它的方法,所以我们实际上从私钥创建了一个ASN.1公钥并将其导入到keycahin。
在私钥中(在ASN.1标题剥离之后),您将拥有一个SEQUENCE标记,后跟3个INTEGER标记(此后有更多的INTEGERS,但前三个是我们关心的。)
第一个是VERSION标签。第二个是模数,第三个是公共指数。
查看公钥(在ASN.1标题剥离之后)您会看到SEQUENCE后跟2个INTEGERS。你猜对了,这是私钥的模数和公共指数。
所以你需要做的就是:
您需要做的就是从您导入的私钥创建公钥。似乎没有太多信息用于导入您在设备上生成的RSA密钥,并且我听说设备上生成的密钥不包含这些ASN.1标头,但我从未尝试过。我们的密钥非常大,设备耗时太长而无法生成。我找到的唯一选择是使用OpenSSL,你必须为iOS编译自己的。我宁愿尽可能使用安全框架。
我仍然是iOS开发的新手,我确信有人知道一个简单的功能,可以完成所有这些我无法找到的功能,而且我很期待。这似乎工作正常,直到更容易的API可用于处理密钥。
最后一点说明:项目中包含的私钥有一个BIT STRING标签,但我从OpenSSL生成的私钥导入的私钥有OCTET STRING标签。
答案 2 :(得分: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 :(得分:1)
答案是用正确的标志集来调用SecItemAdd
。请参阅:http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931
答案 4 :(得分:0)
我不确定this Apple developer forum thread中的代码是否有效,但它似乎是对您问题的直接回答。