在 iPhone 上使用 SecKeyRawSign

发布于 2024-09-02 06:42:42 字数 152 浏览 3 评论 0原文

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

有人用过这个功能吗?如果是的话,有没有什么技巧呢?

〜内特

I'm trying to sign some data using SecKeyRawSign but I keep getting a -4 errSecUnimplemented. That seems strange since the documentation states that it is available in iPhone OS2.0 and later.

Has anyone been able to use this function? If so, are there any tricks involved?

~Nate

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

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

发布评论

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

评论(2

旧人九事 2024-09-09 06:42:42

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

以下是我实现这项工作的方法。

这个生成密钥对

- (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 位 EC 密钥,因为这是 enclave 支持的唯一类型。 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 位 EC 密钥相当于 3072 位 RSA 密钥。

用于检索下面密钥的查询也将有所不同:

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

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

** END EDIT **

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

- (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;
}

If you're having this problem, most likely it is because the private key you generated isn't actually being saved into the keychain. I figured this out when stopping and restarting the application and signing the message wasn't working.

So here are my methods to make this work.

This one generates the key pair

- (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];
}

** EDIT **

iOS 9 introduced a new feature called the Secure Enclave. If you want to generate a key that will be stored there and only there, you will be required to use a 256-bit EC key, as that is the only type supported by the enclave. The keyPairDict will look like this instead:

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
};

I know the parameters are correct, but I haven't myself tested the Secure Enclave yet, so let me know if this doesn't work for some reason.

Also, for reference: a 256-bit EC key is equivalent to a 3072-bit RSA key.

The query used to retrieve the key below would also be different:

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

Because the Secure Enclave is, well, secure, you most likely won't be able to retrieve the private key bits. Most likely, you'll only be able to generate a reference. But you shouldn't need to handle the private key data anyway.

** END EDIT **

This method retrieves the actual bits from the keychain and not just the reference

- (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;
}

This method saves the bits to the keychain

- (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);
    }
}

And then you sign like so:

- (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;
}
隐诗 2024-09-09 06:42:42

-4 errSecUnimplemented 错误是由对用于签署数据的私钥的错误引用引起的。在这种情况下会出现令人困惑的错误。如果有 errSecParam 就更好了。

~NAte

The -4 errSecUnimplemented error was being caused by a bad reference to the private key used to sign the data. Confusing error for that situation. A errSecParam would have been nicer.

~NAte

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