我试图在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密钥。
答案 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);
}
}