Подписание данных ключом kSecAttrKeyTypeEC на iOS

Я пытаюсь подписать данные и проверить подпись с помощью алгоритма эллиптической кривой на iOS. Создание ключей работает достаточно хорошо, но попытка подписать данные возвращает ошибку -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: Подписание и проверка на iOS с использованием RSA но мне не удалось адаптировать его к ключам ЕС.


person SaltyNuts    schedule 10.11.2016    source источник


Ответы (2)


Похоже, что часть проблемы связана с правильным синтаксисом при создании указателей и вычислении размера данных для вызовов 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)")
}
person SaltyNuts    schedule 14.11.2016

ECDSA, в отличие от RSA, не требует хеширования данных перед подписанием.

Apple выпустила улучшенный API в iOS 10 для решения проблем работы с необработанными данными и расчета их размера, а также вернула общие коды ошибок, такие как -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);
    }
}
person NSDestr0yer    schedule 25.04.2020