我有几个 NSString 对象,它们代表一个 RSA 公私密钥对(不是由 SecKeyCreatePair 生成,而是由外部加密库生成)。如何从这些 NSString 对象创建 SecKeyRef 对象(SecKeyDecrypt/Encrypt 方法所需的对象)?
我需要先将它们导入钥匙串吗?如果是这样,怎么做?
谢谢!
我有几个 NSString 对象,它们代表一个 RSA 公私密钥对(不是由 SecKeyCreatePair 生成,而是由外部加密库生成)。如何从这些 NSString 对象创建 SecKeyRef 对象(SecKeyDecrypt/Encrypt 方法所需的对象)?
我需要先将它们导入钥匙串吗?如果是这样,怎么做?
谢谢!
所以在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 生成的私钥。
就将其放入钥匙串而言,该项目完成了我想要的大部分工作。如您所见,它只有一个密钥数据,您将密钥数据推入其中,密钥链从密钥中计算出块大小等。Keychain 支持 ASN.1 编码的密钥。
当您将密钥导出到文件时,它很可能是 PEM 文件。PEM 文件只是一个 base64 编码的 DER 结构。DER 结构是一种通用结构,但在 OpenSSL 的情况下,它通常是 ASN.1 编码的私钥或公钥。
ASN.1 结构在这里显示得很好。请在尝试修改之前阅读并理解如何阅读 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 信息来识别密钥。公钥只是一个通用密钥,因此需要一个标头来标识它是什么类型的密钥。
Keychain 不会使用此 ASN.1 标头导入 RSA 密钥。您需要将其一直剥离到最后一个 SEQUENCE。这时你可以把它放到keychain中,keychain就可以推导出块大小和其他关键属性。
因此,如果 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 可用于处理密钥之前,这似乎工作正常。
One final note: The private key included with the project had a tag of BIT STRING, but the one I import from an OpenSSL generated private key had the tag OCTET STRING.
答案是SecItemAdd
使用正确的标志来调用。请参阅:http ://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931
我从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
}
我不确定这个 Apple 开发者论坛线程中的代码是否有效,但这似乎是对您问题的直接回答。