将RSA密钥导入iPhone钥匙串?

时间:2009-04-27 02:34:56

标签: iphone objective-c security cryptography rsa

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

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

谢谢!

5 个答案:

答案 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。你猜对了,这是私钥的模数和公共指数。

所以你需要做的就是:

  1. 从私钥中获取模数和公共指数
  2. 在缓冲区中创建SEQUENCE标记,并将其长度设置为[模数长度] + [指数长度]。 (当将这些字节写入缓冲区时,您很可能需要反转字节的字节序。至少我做了。)
  3. 添加您从私钥中获取的模数数据
  4. 添加您从私钥中获取的指数数据
  5. 您需要做的就是从您导入的私钥创建公钥。似乎没有太多信息用于导入您在设备上生成的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中的代码是否有效,但它似乎是对您问题的直接回答。