【问题标题】:Signing data with kSecAttrKeyTypeEC key on iOS在 iOS 上使用 kSecAttrKeyTypeEC 密钥对数据进行签名
【发布时间】:2016-11-10 22:02:28
【问题描述】:

我正在尝试在 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 密钥的有效解决方案:Signing and Verifying on iOS using RSA 但我无法将其调整为 EC 密钥。

【问题讨论】:

    标签: ios cryptography elliptic-curve commoncrypto


    【解决方案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)")
    }
    

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-08-17
        • 2012-05-25
        • 1970-01-01
        相关资源
        最近更新 更多