在iOS

时间:2016-11-10 22:02:29

标签: ios cryptography elliptic-curve commoncrypto

我试图在iOS上使用Elliptic Curve算法对数据进行签名并验证签名。创建密钥效果很好,但尝试对数据进行签名会返回错误-1 - 这非常通用。

按键创建如下:

publicKeyRef = NULL;
privateKeyRef = NULL;

NSDictionary * privateKeyAttr = @{(id)kSecAttrIsPermanent : @1,
                                  (id)kSecAttrApplicationTag : privateTag};

NSDictionary * publicKeyAttr = @{(id)kSecAttrIsPermanent : @1,
                                 (id)kSecAttrApplicationTag : privateTag};

NSDictionary * keyPairAttr = @{(id)kSecAttrKeySizeInBits : @(keySize),
                               (id)kSecAttrKeyType : (id)kSecAttrKeyTypeEC,
                               (id)kSecPrivateKeyAttrs : privateKeyAttr,
                               (id)kSecPublicKeyAttrs : publicKeyAttr};

OSStatus status = SecKeyGeneratePair((CFDictionaryRef)keyPairAttr, &publicKeyRef, &privateKeyRef);

这返回状态0,到目前为止一切顺利。实际的签名是这样的:

- (NSData *) signData:(NSData *)dataToSign withPrivateKey:(SecKeyRef)privateKey {
    NSData * digestToSign = [self sha1DigestForData:dataToSign];

    size_t signedHashBytesSize = SecKeyGetBlockSize(privateKey);

    uint8_t * signedHashBytes = malloc( signedHashBytesSize * sizeof(uint8_t) );
    memset((void *)signedHashBytes, 0x0, signedHashBytesSize);
    OSStatus signErr = SecKeyRawSign(privateKey,
                                kSecPaddingPKCS1,
                                digestToSign.bytes,
                                digestToSign.length,
                                (uint8_t *)signedHashBytes,
                                &signedHashBytesSize);
    NSLog(@"Status: %d", signErr);

    NSData * signedHash = [NSData dataWithBytes:(const void *)signedHashBytes length:(NSUInteger)signedHashBytesSize];
    if (signedHashBytes) free(signedHashBytes);

    return (signErr == noErr) ? signedHash : nil;
}

- (NSData *)sha1DigestForData:(NSData *)data {
    NSMutableData *result = [[NSMutableData alloc] initWithLength:CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(data.bytes, (CC_LONG) data.length, result.mutableBytes);

    return result;
}  

SecKeyRawSign()的调用会返回-1

这改编自https://forums.developer.apple.com/message/95740#95740

使用EC密钥签署数据的正确方法是什么?这里有一个RSA密钥的工作解决方案:Signing and Verifying on iOS using RSA但我无法使其适应EC密钥。

2 个答案:

答案 0 :(得分:3)

似乎问题的一部分是在创建指针和计算调用SecKeyRawSign的数据大小时使用正确的语法。 Swift 3 中的一个工作示例如下所示:

生成密钥,存储在Secure Enclave中(暂时存储在实例变量中):

func generateKeyPair() -> Bool {
    if let access = SecAccessControlCreateWithFlags(nil,
                                                    kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
                                                    [.userPresence, .privateKeyUsage],
                                                    nil) {

        let privateKeyAttr = [kSecAttrIsPermanent : 1,
                              kSecAttrApplicationTag : privateTag,
                              kSecAttrAccessControl as String: access
            ] as NSDictionary

        let publicKeyAttr = [kSecAttrIsPermanent : 0,
                             kSecAttrApplicationTag : publicTag
            ] as NSDictionary

        let keyPairAttr = [kSecAttrKeySizeInBits : 256,
                           kSecAttrKeyType : kSecAttrKeyTypeEC,
                           kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
                           kSecPrivateKeyAttrs : privateKeyAttr,
                           kSecPublicKeyAttrs : publicKeyAttr] as NSDictionary

        let err = SecKeyGeneratePair(keyPairAttr, &publicKey, &privateKey)
        return err == noErr
}

签署数据

func signData(plainText: Data) -> NSData? {
    guard privateKey != nil else {
        print("Private key unavailable")
        return nil
    }

    let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data

    let signature = UnsafeMutablePointer<UInt8>.allocate(capacity: 128)
    var signatureLength = 128
    let err = SecKeyRawSign(privateKey!,
                            .PKCS1SHA1,
                            [UInt8](digestToSign),
                            Int(CC_SHA1_DIGEST_LENGTH),
                            signature,
                            &signatureLength)

    print("Signature status: \(err)")

    let sigData = NSData(bytes: signature, length: Int(signatureLength))

    return sigData
}

func sha1DigestForData(data: NSData) -> NSData {
    let len = Int(CC_SHA1_DIGEST_LENGTH)
    let digest = UnsafeMutablePointer<UInt8>.allocate(capacity: len)
    CC_SHA1(data.bytes, CC_LONG(data.length), digest)
    return NSData(bytesNoCopy: UnsafeMutableRawPointer(digest), length: len)
}

验证签名

func verifySignature(plainText: Data, signature: NSData) -> Bool {
    guard publicKey != nil else {
        print("Public key unavailable")
        return false
    }

    let digestToSign = self.sha1DigestForData(data: plainText as NSData) as Data
    let signedHashBytesSize = signature.length

    let err = SecKeyRawVerify(publicKey!,
                              .PKCS1SHA1,
                              [UInt8](digestToSign),
                              Int(CC_SHA1_DIGEST_LENGTH),
                              [UInt8](signature as Data),
                              signedHashBytesSize)

    print("Verification status: \(err)")

    return err == noErr
}

如果您需要导出公钥以便其他应用程序或设备可以使用它,可以这样做:

let parameters = [
    kSecClass as String: kSecClassKey,
    kSecAttrKeyType as String: kSecAttrKeyTypeEC,
    kSecAttrLabel as String: "Public Key",
    kSecAttrIsPermanent as String: false,
    kSecValueRef as String: publicKey,
    kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
    kSecReturnData as String: true
    ] as CFDictionary
var data:AnyObject?
let status = SecItemAdd(parameters, &data)
print("Public key added \(status)")
if let keyData = data as? NSData {
    print("This is the key, send it where it needs to go:\n\(keyData)")
}

答案 1 :(得分:0)

ECDSA与RSA不同,在签名之前不需要散列数据。

Apple在iOS 10中发布了改进的API,以解决使用和计算原始数据的大小以及返回-1之类的通用错误代码的问题。较新的SecKeyCreateSignature代替SecKeyRawSign,返回了数据和错误对象,并且为清楚起见替换了EC旧式常量。这是一个更新的示例:

- (NSData *) signData:(NSData *)dataToSign withPrivateKey:(SecKeyRef)privateKey {
    NSData *signedData = nil;
    if (dataToSign && privateKey && SecKeyCreateSignature != NULL) //Also check for iOS 10 +
    {
        CFErrorRef error = NULL;
        CFDataRef signatureData = SecKeyCreateSignature(privateKey, kSecKeyAlgorithmECDSASignatureMessageX962SHA512, (__bridge CFDataRef)dataToSign, &error);
        if (signatureData)
        {
            if (error)
            {
                CFShow(error); // <-- here you get way more info than "-1"
                CFRelease(signatureData);
            }
            else
            {
                signedData = (__bridge NSData *)CFAutorelease(signatureData);
            }
        }

        if (error)
        {
            CFRelease(error);
        }
    }
    return signedData;
}

关于旧功能,iOS对于EC的参数非常挑剔。传递“错误的密钥”可以给您-1-50,尤其是因为工程师只将EC支持集中在使用安全区域的较新API上。这是生成兼容密钥的密钥生成的更新示例:

if (SecKeyCreateRandomKey != NULL && !(TARGET_IPHONE_SIMULATOR)) //iOS 10 + check, real device
{
    CFErrorRef error = NULL;
    SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, kSecAccessControlPrivateKeyUsage, &error);
    if (error)
    {
        CFShow(error); // <- error instead of OSStatus
        CFRelease(error);
        error = NULL;
    }

    if (accessControl)
    {
        static const uint8_t identifier[] = "com.company.yourKey";
        CFDataRef privateTag = CFDataCreate(kCFAllocatorDefault, identifier, sizeof(identifier));
        if (privateTag)
        {
            const void* accessKeys[] = { kSecAttrIsPermanent, kSecAttrApplicationTag, kSecAttrAccessControl };
            const void* accessValues[] = { kCFBooleanTrue, privateTag, accessControl };
            CFDictionaryRef accessDictionary = CFDictionaryCreate(kCFAllocatorDefault, accessKeys, accessValues, 3, NULL, NULL);
            if (accessDictionary)
            {
                CFMutableDictionaryRef parameters = CFDictionaryCreateMutable(kCFAllocatorDefault, 7, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
                if (parameters)
                {
                    SInt32 keySize = 256;
                    CFNumberRef keySizeNumber = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &keySize);
                    if (keySizeNumber)
                    {
                        CFDictionaryAddValue(parameters, kSecAttrKeySizeInBits, keySizeNumber);
                        CFRelease(keySizeNumber);
                    }

                    CFDictionaryAddValue(parameters, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom);
                    CFDictionaryAddValue(parameters, kSecAttrTokenID, kSecAttrTokenIDSecureEnclave);
                    CFDictionaryAddValue(parameters, kSecPrivateKeyAttrs, accessDictionary);

                    SecKeyRef privateKey = SecKeyCreateRandomKey(parameters, &error); // <- pass in an error object
                    if (privateKey)
                    {
                        SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey);
                        if (publicKey)
                        {
                            //...
                            CFRelease(publicKey);
                        }

                        //...
                        CFRelease(privateKey);
                    }

                    if (error)
                    {
                        CFRelease(error);
                    }

                    CFRelease(parameters);
                }
                CFRelease(accessDictionary);
            }
            CFRelease(privateTag);
        }
        CFRelease(accessControl);
    }
}