Подписание данных с помощью ключа 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
Как правильно использовать ключ EC для подписи данных? Здесь есть рабочее решение для ключей RSA: подписывание и проверка на iOS с использованием RSA, но я не смог адаптировать его к ключам EC.
1 ответ
Похоже, что проблема заключается в правильном синтаксисе при создании указателей и вычислении размера данных для вызовов 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)")
}
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);
}
}