【问题标题】:Using SecKeyRawSign on the iPhone在 iPhone 上使用 SecKeyRawSign
【发布时间】:2010-05-19 14:41:33
【问题描述】:

我正在尝试使用 SecKeyRawSign 对一些数据进行签名,但我不断收到 -4 errSecUnimplemented。这似乎很奇怪,因为文档指出它在 iPhone OS2.0 及更高版本中可用。

有人用过这个功能吗?如果有,有什么技巧吗?

~内特

【问题讨论】:

    标签: iphone objective-c signing keychain


    【解决方案1】:

    如果您遇到此问题,很可能是因为您生成的私钥实际上并未保存到钥匙串中。我在停止并重新启动应用程序并且签署消息不起作用时发现了这一点。

    所以这是我的方法来完成这项工作。

    这个生成密钥对

    - (void)generateKeyPair:(NSUInteger)keySize {
        OSStatus sanityCheck = noErr;
        publicKeyRef = NULL;
        privateKeyRef = NULL;
    
        LOGGING_FACILITY1( keySize == 512 || keySize == 1024 || keySize == 2048, @"%d is an invalid and unsupported key size.", keySize );
    
        // First delete current keys.
        [self deleteAsymmetricKeys];
    
        // Container dictionaries.
    
        // See SecKey.h for other values
        NSDictionary *privateKeyDict = @{
                        (__bridge id) kSecAttrIsPermanent : [NSNumber numberWithBool:YES],
                        (__bridge id) kSecAttrApplicationTag : privateTag
        };
    
        // See SecKey.h for other values
        NSDictionary *publicKeyDict = @{
                        (__bridge id) kSecAttrIsPermanent : [NSNumber numberWithBool:YES],
                        (__bridge id) kSecAttrApplicationTag : publicTag
        };
    
        NSDictionary *keyPairDict = @{
                        (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
                        (__bridge id) kSecAttrKeySizeInBits : [NSNumber numberWithUnsignedInteger:keySize],
                        (__bridge id) kSecPrivateKeyAttrs : privateKeyDict,
                        (__bridge id) kSecPublicKeyAttrs : publicKeyDict
        };
    
        // SecKeyGeneratePair returns the SecKeyRefs
        sanityCheck = SecKeyGeneratePair((__bridge CFDictionaryRef) keyPairDict, &publicKeyRef, &privateKeyRef);
        LOGGING_FACILITY( sanityCheck == noErr && publicKeyRef != NULL && privateKeyRef != NULL, @"Something really bad went wrong with generating the key pair." );
    
        // retrieve the actual bits for the keys, not just the references
        NSData *publicKeyBits = [self getKeyBitsFromKey:publicKeyRef];
        NSData *privateKeyBits = [self getKeyBitsFromKey:privateKeyRef];
    
        // save the keys to the keychain
        [self saveKeyToKeychain:publicKeyBits keySize:keySize private:NO];
        [self saveKeyToKeychain:privateKeyBits keySize:keySize private:YES];
    }
    

    ** 编辑 **

    iOS 9 引入了一项名为 Secure Enclave 的新功能。如果您想生成将存储在那里且仅存储在那里的密钥,您将需要使用256-bit EC 密钥,因为这是飞地支持的唯一类型。 keyPairDict 会变成这样:

    NSDictionary *keyPairDict = @{
                    (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave,
                    (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeEC,
                    // we can use keySize here if we want
                    // but since 256 is the only available size
                    // we can just hardcode it for now
                    (__bridge id) kSecAttrKeySizeInBits : @256],
                    (__bridge id) kSecPrivateKeyAttrs : privateKeyDict,
                    (__bridge id) kSecPublicKeyAttrs : publicKeyDict
    };
    

    我知道参数是正确的,但我自己还没有测试过 Secure Enclave,所以如果由于某种原因这不起作用,请告诉我。

    另外,作为参考:256-bit EC 密钥等同于 3072-bit RSA 密钥。

    用于检索以下键的查询也会有所不同:

    NSDictionary *queryKey = @{
                    (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                    (__bridge id) kSecAttrApplicationTag : tempTag,
                    (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeEC
    };
    

    因为 Secure Enclave 是安全的,所以您很可能无法检索私钥位。很可能,您只能生成参考。但是无论如何您都不需要处理私钥数据。

    ** 结束编辑 **

    此方法从钥匙串中检索实际位,而不仅仅是参考

    - (NSData *)getKeyBitsFromKey:(SecKeyRef)givenKey {
        static const uint8_t publicKeyIdentifier[] = "com.sample.temp";
        NSData *tempTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:sizeof(publicKeyIdentifier)];
    
        NSDictionary *queryKey = @{
                        (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                        (__bridge id) kSecAttrApplicationTag : tempTag,
                        (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA
        };
    
        // Temporarily add key to the Keychain, return as data:
        NSMutableDictionary *attributes = [[NSMutableDictionary alloc] initWithDictionary:queryKey copyItems:YES];
        [attributes setObject:(__bridge id) givenKey forKey:(__bridge id) kSecValueRef];
        [attributes setObject:@YES forKey:(__bridge id) kSecReturnData];
    
        // result codes: https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/Reference/reference.html#//apple_ref/doc/uid/TP30000157-CH4g-339030
        OSStatus sanityCheck = noErr;
        NSData *keyBits = nil;
    
        CFTypeRef result;
        sanityCheck = SecItemAdd((__bridge CFDictionaryRef) attributes, &result);
        if (sanityCheck == errSecSuccess) {
                keyBits = CFBridgingRelease(result);
    
                // Remove from Keychain again:
                (void) SecItemDelete((__bridge CFDictionaryRef) queryKey);
                return keyBits;
        }
        else if (sanityCheck == errSecDuplicateItem) {
                // Remove from Keychain again:
                (void) SecItemDelete((__bridge CFDictionaryRef) queryKey);
                return [self getKeyBitsFromKey:givenKey];
        }
    
        return nil;
    }
    

    此方法将位保存到钥匙串

    - (void)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 savedKey = NULL;
        sanityCheck = SecItemAdd((__bridge CFDictionaryRef) saveDict, (CFTypeRef *)&savedKey);
        if (sanityCheck != errSecSuccess) {
                LOGGING_FACILITY1(sanityCheck != noErr, @"Problem saving the key to keychain, OSStatus == %d.", sanityCheck);
        }
    }
    

    然后你像这样签名:

    - (NSData *)getSignatureBytes:(NSData *)plainText {
        OSStatus sanityCheck = noErr;
        NSData *signedHash = nil;
    
        uint8_t *signedHashBytes = NULL;
        size_t signedHashBytesSize = 0;
    
        SecKeyRef privateKey = NULL;
    
        privateKey = [self getKeyRef:YES];
        signedHashBytesSize = SecKeyGetBlockSize(privateKey);
    
        // Malloc a buffer to hold signature.
        signedHashBytes = malloc(signedHashBytesSize * sizeof(uint8_t));
        memset((void *) signedHashBytes, 0x0, signedHashBytesSize);
    
        // Sign the SHA1 hash.
        sanityCheck = SecKeyRawSign(privateKey,
                kTypeOfSigPadding,
                (const uint8_t *) [[self getHashBytes:plainText] bytes],
                kChosenDigestLength,
                signedHashBytes,
                &signedHashBytesSize
        );
    
        LOGGING_FACILITY1( sanityCheck == noErr, @"Problem signing the SHA1 hash, OSStatus == %d.", sanityCheck );
    
        // Build up signed SHA1 blob.
        signedHash = [NSData dataWithBytes:(const void *) signedHashBytes length:(NSUInteger) signedHashBytesSize];
    
        if (signedHashBytes) {
            free(signedHashBytes);
        }
    
        return signedHash;
    }
    

    【讨论】:

    • iOS 9 允许将私钥存储在安全飞地中,如果需要进行哪些更改才能利用它? (显然私钥无法导出,但最好验证一下)
    • @LamonteCristo 因为评论框比较有限,我在上面添加了一个编辑来回答你的问题。
    • @LamonteCristo 我已经有一段时间没有接触过这段代码了,所以有机会我会重新审视它。
    【解决方案2】:

    -4 errSecUnimplemented 错误是由对用于签署数据的私钥的错误引用引起的。这种情况令人困惑的错误。 errSecParam 会更好。

    ~Nate

    【讨论】:

    • 这正是我遇到的问题。事实证明,CryptoExercise 中的 SecKeyWrapper 类没有将私钥保存到钥匙串中!
    猜你喜欢
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 2010-11-09
    • 2013-01-19
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    相关资源
    最近更新 更多