我有一对NSString对象,表示一个RSA公私钥对(不是由SecKeyCreatePair生成的,而是由外部加密库生成的)。如何从这些NSString对象创建SecKeyRef对象(SecKeyDecrypt / Encrypt方法所需的对象)?
我需要先将它们导入到Keychain中吗?如果需要,应该如何操作?
谢谢!
我有一对NSString对象,表示一个RSA公私钥对(不是由SecKeyCreatePair生成的,而是由外部加密库生成的)。如何从这些NSString对象创建SecKeyRef对象(SecKeyDecrypt / Encrypt方法所需的对象)?
我需要先将它们导入到Keychain中吗?如果需要,应该如何操作?
谢谢!
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生成的私钥。
这个项目基本上实现了我想要的所有功能,可以将其放入钥匙串中。正如您所看到的,它只有一个keydata,您将密钥数据放入其中,然后钥匙串从密钥中找出块大小等信息。钥匙串支持ASN.1编码的密钥。
当您将密钥导出到文件时,它很可能是PEM文件。PEM文件只是一个base64编码的DER结构。该DER结构是一个广义结构,但在OpenSSL的情况下,它通常是一个ASN.1编码的私钥或公钥。
ASN.1结构在这里展示得非常好。请在尝试处理此内容或导入其他大小的密钥之前,请阅读并理解如何读取ASN.1结构。
我显然没有足够的“声望”来发布超过2个链接。因此,对于以下示例,请在lapo.it/asn1js中粘贴base64信息(除了--- BEGIN * KEY ---和---END * KEY ---之外的所有内容)。
如果您查看我提供的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信息来标识密钥。公钥只是一个通用密钥,因此需要头部来确定密钥类型。SecItemAdd
。参见:http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931
我从MYcrypto库中找到了这段代码(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
}
#if TARGET_IPHONE_SIMULATOR
和 #else
编译指令,你就可以得到在模拟器和设备上都能工作的东西。(你已经有了 Mac 版本,只需寻找 iPhone 的类比即可。我正在进行类似的任务,否则我会自己回答的。) - bbrown