【问题标题】:Adding RSA keys to the iPhone keychain将 RSA 密钥添加到 iPhone 钥匙串
【发布时间】:2009-05-14 16:22:03
【问题描述】:

我正在尝试将公共和私人 RSA 密钥添加到 iPhone 的钥匙串,所以我可以使用 CommonCrypto 库,但我不完全确定如何做到这一点。 MYCrypto 库目前似乎只适用于 Mac 而不是 iPhone。谁能帮助并解释如何将私钥/公钥添加到钥匙串并获得 SecKeyRef 给他们?

【问题讨论】:

  • 您如何存储这些密钥?在证书或 P12 包中?

标签: iphone cryptography rsa keychain


【解决方案1】:

所以在 iOS 中,钥匙串是沙盒的,AFAIK。这意味着您放入钥匙串的任何内容都只能由您的应用程序和您的应用程序访问,除非您另有说明。您必须在项目设置的 Capabilities 下启用 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;
}

关于 privateTag 和 publicTag

privateTagpublicTag 用于标记kSecAttrApplicationTag,它定义了使用此密钥的应用程序。您希望有一个单独的 privateTagpublicTag 来区分您的私钥和公钥。

这有点令人费解,因为我遵循了示例代码,但我这样定义了我的 privateTagpublicTag

SecKeyWrapper.h

#define kPublicKeyTag           "com.sample.app.publickey"
#define kPrivateKeyTag          "com.sample.app.privatekey"

SecKeyWrapper.m

// just under @implementation or @synthesize lines
static const uint8_t publicKeyIdentifier[] = kPublicKeyTag;
static const uint8_t privateKeyIdentifier[] = kPrivateKeyTag;

- (id)init {
    if (self = [super init]) {
        // Tag data to search for keys.
        privateTag = [[NSData alloc] initWithBytes:privateKeyIdentifier length:sizeof(privateKeyIdentifier)];
        publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
    }

    return self;
}

然后像我在上面提供的代码示例中那样使用privateTagpublicTag

【讨论】:

  • @thanks mikeho 提供代码。我第一次得到了 sanityCheck = 0 的 savedKeyRef = nil,之后每次 sanityCheck = -25299 和消息​​“保存密钥的问题......”。虽然 privateKeyRef 和 privateKeyRef 总是返回 nil 且 sanityCheck = 0。你能帮帮我吗?
  • @SaurabhShukla:我添加了一些关于 privateKeyRef 和 publicKeyRef 的信息。我怀疑的问题是 privateTag 和 publicTag 设置不正确,这意味着它实际上并没有将数据保存到正确的位置。查看我对答案的补充并尝试一下。
  • 我用另一种方式解决了它。我正在添加我的答案。 +1 你给我的方向。
【解决方案2】:

您是在 iPhone 上生成它们吗?如果是这样,this message 加上证书、密钥和信任服务编程指南中的 example code 应该会引导您朝着正确的方向前进。如果没有,我正在编写一些应该完成该任务的代码——它还没有完成。

【讨论】:

    【解决方案3】:

    这似乎与this SO question 重复。

    MyCrypto says that it works on the iPhone。您遇到了什么样的问题?

    【讨论】:

    • 不,MYCrypto 的限制明确表示它在运行 iPhone 时会遇到问题。我尝试导入公钥时收到 OSStatus 参数错误 (-50)。
    • 是的,Jens Alfke 正在努力解决这个问题,但从他在 CDSA 邮件列表上的通信中可以清楚地看出,他正在破解一个非常棘手的问题,而且还需要一段时间在他拥有一个随时可用的图书馆之前。
    【解决方案4】:

    适用于我将公钥添加到 iOS 钥匙串的代码如下:

    - (NSData *)stripPublicKeyHeader:(NSData *)d_key
    {
        // Skip ASN.1 public key header
        if (d_key == nil) return(nil);
    
        unsigned int len = [d_key length];
        if (!len) return(nil);
    
        unsigned char *c_key = (unsigned char *)[d_key bytes];
        unsigned int  idx    = 0;
    
        if (c_key[idx++] != 0x30) return(nil);
    
        if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
        else idx++;
    
        // PKCS #1 rsaEncryption szOID_RSA_RSA
        static unsigned char seqiod[] =
        { 0x30,   0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
            0x01, 0x05, 0x00 };
        if (memcmp(&c_key[idx], seqiod, 15)) return(nil);
    
        idx += 15;
    
        if (c_key[idx++] != 0x03) return(nil);
    
        if (c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
        else idx++;
    
        if (c_key[idx++] != '\0') return(nil);
    
        // Now make a new NSData from this buffer
        return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
    }
    
    - (BOOL)addPublicKey:(NSString *)key withTag:(NSString *)tag
    {    
        NSString *startPublicKey = @"-----BEGIN PUBLIC KEY-----";
        NSString *endPublicKey = @"-----END PUBLIC KEY-----";
    
        NSString *s_key = NULL;
    
        NSScanner *scanner = [NSScanner scannerWithString:key];
        [scanner scanUpToString:startPublicKey intoString:nil];
        [scanner scanString:startPublicKey intoString:nil];
        [scanner scanUpToString:endPublicKey intoString:&s_key];
    
        // This will be base64 encoded, decode it.
        NSData *d_key = [RSAKeyHandler base64DataFromString:s_key];
        d_key = [self stripPublicKeyHeader:d_key];
        if (d_key == nil) return(FALSE);
    
        NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];
    
        // Delete any old lingering key with the same tag
        NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
        [publicKey setObject:(__bridge id) kSecClassKey forKey:(__bridge id)kSecClass];
        [publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
        [publicKey setObject:d_tag forKey:(__bridge id)kSecAttrApplicationTag];
        SecItemDelete((__bridge CFDictionaryRef)publicKey);
    
        CFTypeRef persistKey = nil;
    
        // Add persistent version of the key to system keychain
        [publicKey setObject:d_key forKey:(__bridge id)kSecValueData];
        [publicKey setObject:(__bridge id) kSecAttrKeyClassPublic forKey:(__bridge id)
         kSecAttrKeyClass];
        [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)
         kSecReturnPersistentRef];
    
        OSStatus secStatus = SecItemAdd((__bridge CFDictionaryRef)publicKey, &persistKey);
        if (persistKey != nil) CFRelease(persistKey);
    
        if ((secStatus != noErr) && (secStatus != errSecDuplicateItem)) {
            NSLog(@"Release public key");
            return(FALSE);
        }
    
        // Now fetch the SecKeyRef version of the key
        SecKeyRef keyRef = nil;
    
        [publicKey removeObjectForKey:(__bridge id)kSecValueData];
        [publicKey removeObjectForKey:(__bridge id)kSecReturnPersistentRef];
        [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef
         ];
        [publicKey setObject:(__bridge id) kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];
        secStatus = SecItemCopyMatching((__bridge CFDictionaryRef)publicKey,
                                        (CFTypeRef *)&keyRef);
    
        //[publicKey release];
    
        if (keyRef == nil) return(FALSE);
    
        // Add to our pseudo keychain
        //[keyRefs addObject:[NSValue valueWithBytes:&keyRef objCType:@encode(
        //                                                                    SecKeyRef)]];
    
        return(TRUE);
    }
    

    【讨论】:

    • 啊,我明白了。为了给这个答案添加一点上下文,@SaurabhShukla 试图做的是向他的 iOS 设备添加一个外部生成的公钥。这需要去掉 iOS 不需要的标头。如果您还想将本地生成的公钥发送到服务器,则相反。
    猜你喜欢
    • 2010-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多