将 RSA 密钥导入 iPhone 钥匙串?

发布于 2024-07-18 11:58:55 字数 190 浏览 7 评论 0原文

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

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

谢谢!

I have a couple of NSString objects that represent an RSA public-private keypair (not generated by SecKeyCreatePair, but by an external crypto library). How can I create SecKeyRef objects (which are required for the SecKeyDecrypt/Encrypt methods) from these NSString objects?

Do I need to import them into the Keychain first? If so, how?

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

平定天下 2024-07-25 11:58:55

所以在 iOS 中,钥匙串是沙盒的,据我所知。 这意味着除非您另有指定,否则您放入钥匙串中的任何内容都只能由您的应用程序访问,并且只能由您的应用程序访问。 您必须在项目设置中的功能下启用钥匙串共享

现在一切都已解决,您当然可以导入数据了。 由于它们是 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;
}

So in iOS, the keychain is sandboxed, AFAIK. This means that whatever you put into the keychain is only accessible by your app and your app alone unless you specify otherwise. You have to enable Keychain Sharing under Capabilities in the project settings.

Now that that's out of the way, you can certainly import the data. Since they're NSString objects, you'd first have to convert that to NSData objects to import them correctly. Most likely, they're encoded in Base64, so you'lL have to reverse that:

NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];

Now that that's done, you can use this method to both save your key to the keychain and get the 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;
}

Later, if you want to retrieve the SecKeyRef from the keychain, you can use this:

- (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;
}
陪你搞怪i 2024-07-25 11:58:55

编辑:使用下面的方法,我们能够导入最大大小为 4096 的密钥。任何大于此大小的 RSA 密钥大小似乎都会被钥匙串拒绝。 我们得到了成功状态,但没有得到密钥的引用。

关于导入 RSA 私钥/公钥的简要说明。 就我而言,我需要导入 OpenSSL 生成的私钥。

这个项目实现了我想要的大部分功能就将它放入钥匙串中。 正如您所看到的,它只有一个密钥数据,您可以将密钥数据推入其中,并且钥匙串从密钥中计算出块大小等。 钥匙串支持 ASN.1 编码密钥。

当您将密钥导出到文件时,它很可能是 PEM 文件。 PEM 文件只是一个 base64 编码的 DER 结构。 DER 结构是一种通用结构,但对于 OpenSSL,它通常是 ASN.1 编码的私钥或公钥。

此处很好地显示了 ASN.1 结构。 在尝试摆弄它之前,请先阅读并理解如何读取 ASN.1 结构,否则导入其他大小的密钥将会失败。

我显然没有足够的“声誉”来发布两个以上的链接。 因此,对于以下示例,请粘贴 base64 信息(除了 --- BEGIN * KEY --- 和 ---END * KEY --- 之外的所有内容):lapo.it/asn1js。

如果您查看我链接的 iOS 项目,您会看到它们包含示例密钥。将私钥粘贴到 ASN.1 解码器中您会注意到有一个序列标记,后面跟着几个整数值。

现在您会注意到公钥。有两条与私钥相同的信息。在私钥中,这是第二个和第三个整数值。它还有两个额外的序列,一个对象 ID。 。

您还会注意到,在项目中,他调用了一个特殊函数来处理该公钥,它的作用是删除所有标头信息,直到到达最里面的 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 密钥。 您需要将其一直剥离到最后一个序列。 此时您可以将其放入钥匙串中,钥匙串能够导出块大小和其他关键属性。

因此,如果 BEGIN RSA PRIVATE KEY 存在,则无需进行剥离。 如果是 - BEGIN PRIVATE KEY ---,您将需要在将其放入钥匙串之前删除这些初始标头。

就我而言,我还需要公钥。 一旦我们成功放入私钥,我们就无法找到从钥匙串中获取它的方法(我们可能只是错过了一些东西),因此我们实际上从私钥创建了一个 ASN.1 公钥并将其导入到 keycahin 中。

在私钥中(在 ASN.1 标头剥离之后),您将有一个 SEQUENCE 标记,后跟 3 个 INTEGER 标记(此后还有更多 INTEGERS,但前 3 个是我们关心的)。

第一个是 VERSION 标签。 第二个是模数,第三个是公共指数。

查看公钥(在 ASN.1 标头剥离之后),您会看到 SEQUENCE 后面跟着 2 个 INTEGERS。 您猜对了,这是私钥的模数和公共指数。

因此,您需要做的就是:

  1. 从私钥中获取模数和公共指数
  2. 在缓冲区中创建一个 SEQUENCE 标记,并将其长度设置为 [模数长度] + [指数长度]。 (将这些字节写入缓冲区时,您很可能需要反转字节的字节序。至少我做到了。)
  3. 添加您从私钥中获取的模数数据
  4. 添加您从私钥中获取的指数数据

就这样您需要根据导入的私钥创建公钥。 似乎没有太多信息可用于导入不是在设备上生成的 RSA 密钥,而且我听说设备上生成的密钥不包含这些 ASN.1 标头,但我从未尝试过。 我们的密钥非常大,设备需要很长时间才能生成。 我发现的唯一选择是使用 OpenSSL,您必须为 iOS 编译自己的 SSL。 我宁愿尽可能使用安全框架。

我对 iOS 开发还很陌生,我确信有人知道一个简单的函数,可以完成我找不到的所有这些功能,并且我查看了。 在有更简单的 API 可用于处理密钥之前,这似乎工作正常。

最后一点:项目中包含的私钥具有 BIT STRING 标签,但我从 OpenSSL 生成的私钥导入的私钥具有 OCTET STRING 标签。

EDIT: Using the below method we were able to import keys up to size 4096. Any RSA Keysize larger than this seems to be rejected by keychain. We get back a success status, but we don't get a reference to the key.

Just a quick note regarding importing RSA private/public keys. In my case, I needed to import a private key generated by OpenSSL.

This project does most of what I wanted as far as putting it into keychain. As you can see it just has a keydata where you shove the key data into it and keychain figures out the block size, etc from the key. Keychain supports an ASN.1 encoded key.

When you export a key to a file, it is most likely a PEM file. A PEM file is just a base64 encoded DER structure. The DER structure is a generalized structure, but in the case of OpenSSL, it's usually an ASN.1 encoded private or public key.

The ASN.1 structure is displayed pretty well here. PLEASE read and understand how to read the ASN.1 structure before you try and fiddle with this, or importing a key of another size will fail.

I apparently don't have enough "reputation" to post more than 2 links. So for the following example paste the base64 information (everything except the --- BEGIN * KEY --- and ---END * KEY --- at: lapo.it/asn1js.

If you look ay the iOS project I linked, you'll see they include sample keys. Paste the private key into the ASN.1 decoder. You'll notice you have a SEQUENCE tag followed by several INTEGER values.

Now paste in the public key. You'll notice that the public key has two pieces of information in common with the private key. The modulus and the exponent. In the private key this is the second and third INTEGER values. It also has some information at the top. It has 2 extra SEQUENCEs, an OBJECT ID, NULL, and BIT STRING tags.

You'll also notice in the project that he calls a special function to process that public key. What it does is strip all the header information until it gets to the innermost SEQUENCE tag. At that point he treats it exactly like the private key and can put it into keychain.

Why do this for one and not the other? Look at the header and footer text. The private key says --- BEGIN RSA PRIVATE KEY ---, the public key says --- BEGIN PUBLIC KEY ---. The object ID you will see in the public key is: 1.2.840.113549.1.1.1. This is an ID that is a static tag identifying the contained key as an RSA type key.

Since the private key has RSA in the preamble, it's assumed it's an RSA key, and that header ASN.1 information isn't needed to identify the key. The public key is just a generic key, so a header is required to identify what type of key it is.

Keychain will NOT import an RSA key with this ASN.1 header. You need to strip it all the way to the last SEQUENCE. At that point you can put it into keychain, and keychain was able to derive the block size and other key attributes.

So if BEGIN RSA PRIVATE KEY is there, you do not need to do the stripping. If it is -- BEGIN PRIVATE KEY ---, you will need to strip those initial headers before putting it into keychain.

In my case, I also needed the public key. We couldn't figure our a way to get it from keychain once we put the private key in successfully (We may have just missed something), so we actually created an ASN.1 public key from the private key and imported that into keycahin.

In the private key (After ASN.1 header stripping) you will have a SEQUENCE tag followed by 3 INTEGER tags (there are more INTEGERS after this, but the first 3 are all we care about).

The first one is a VERSION tag. The second one is the Modulus and the third is the public exponent.

Looking at the public key (after ASN.1 header stripping) You see SEQUENCE followed by 2 INTEGERS. You guessed it, this is the modulus and public exponent from the private key.

So all you need to do is:

  1. Grab the modulus and public exponent from the private key
  2. Create a SEQUENCE tag in a buffer and set its length to [modulus length] + [exponent length]. (When writing these bytes to the buffer, you will most likely need to reverse the endian of the bytes. I did at least.)
  3. Add the modulus data you grabbed from the private key
  4. Add the exponent data you grabbed from the private key

That's all you need to do to create a public key from your private key you imported. There doesn't seem to be much information out there for importing RSA keys you don't generate on the device, and I've heard that keys generated on the device do NOT contain these ASN.1 headers, but I've never tried. Our keys are quite large and take a device too long to generate. The only option I had ever found was to use OpenSSL, where you have to compile your own for iOS. I'd rather use the security framework where possible.

I'm still rather new to iOS development, and I'm sure someone knows a simple function that does all of this that I couldn't find, and I LOOKED. This seems to work fine until an easier API is available to process keys.

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.

春风十里 2024-07-25 11:58:55

答案是使用正确的标志集调用 SecItemAdd 。 请参阅:http://hg.mozilla .org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931

The answer was to call SecItemAdd with the right set of flags. See: http://hg.mozilla.org/services/fx-home/file/tip/Sources/NetworkAndStorage/CryptoUtils.m#l931

ペ泪落弦音 2024-07-25 11:58:55

我从 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
}

I dug this code up (BSD license) from the MYcrypto library. It seems to do what you want.

    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
}
丢了幸福的猪 2024-07-25 11:58:55

我不确定 此 Apple 开发者论坛帖子 中的代码是否有效,但它似乎是对你问题的直接回答。

I'm not sure if the code in this Apple developer forum thread works, but it seems like a direct answer to your question.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文