【问题标题】:Openssl verify fails with iOS Secure Enclave created signatureOpenssl 验证失败,iOS Secure Enclave 创建了签名
【发布时间】:2021-05-30 07:37:48
【问题描述】:

我正在尝试在 iOS (14.4) 上对用户数据进行哈希处理和签名,将其发送到我的服务器,并让服务器使用之前上传的公钥(在用户创建期间生成密钥对时发送)验证哈希和签名。似乎很多人都遇到了这个问题,但我能找到的所有答案都是very old,不要考虑使用 Apple 的 Secure Enclave,或者在同一个 iOS 上进行签名和验证设备。

一般的工作流程是:用户在 iOS 上创建一个帐户,在设备上创建一个随机密钥对,私钥留在 Secure Enclave 中,而公钥转换为 ASN.1 格式,PEM 编码并上传到服务器。当用户稍后对数据进行签名时,数据会经过 JSON 编码,使用 sha512 进行哈希处理,并由他们在 Secure Enclave 中的私钥进行签名。然后将其打包到 base64EncodedString 有效负载中,并发送到服务器进行验证。服务器首先使用 openssl_digest 验证哈希,然后使用 openssl_verify 检查签名。

我一直无法获取 openssl_verify 方法来成功验证签名。我还尝试使用 phpseclib 库(以更深入地了解验证失败的原因)但没有成功。我了解 phpseclib 使用 openssl 库(如果可用),但即使禁用此功能,phpseclib 的内部验证也会失败,因为模数后的结果值不匹配。有趣的是,phpseclib 将公钥转换为看起来像带有大量填充的 PKCS8 格式。

看来,openssl 正在正确解析和加载公钥,因为在验证之前正在创建正确的引用。但是,由于私钥是不透明的(位于 Secure Enclave 中),我无法从外部“检查”签名本身是如何生成/编码的,或者是否会在 iOS 设备之外创建相同的签名。我想知道我是否有编码错误,或者是否可以使用 Secure Enclave 中生成的密钥进行外部验证。

iOS 公钥上传方法- 我正在使用 CryptoExportImportManager 将原始字节转换为 DER,添加 ASN.1 标头,并添加 BEGIN 和 END 密钥标签。

public func convertPublicKeyForExport() -> String?
{
  let keyData       = SecKeyCopyExternalRepresentation(publicKey!, nil)! as Data
  let keyType       = kSecAttrKeyTypeECSECPrimeRandom
  let keySize       = 256
  let exportManager = CryptoExportImportManager()
  let exportablePEMKey = exportManager.exportECPublicKeyToPEM(keyData, keyType: keyType as String,
                                                               keySize: keySize)
        
  return exportablePEMKey
}

上传后其中一个公钥的外观示例

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf16tnH8YPjslaacdtdde4wRQs0PP
zj/nWgBC/JY5aeajHhbKAf75t6Umz6vFGBsdgM/AFMkeB4n2Qi96ePNjFg==
-----END PUBLIC KEY-----
let encoder = JSONEncoder()
guard let payloadJson = try? encoder.encode(["user_id": "\(user!.userID)", "random_id": randomID])
else
{
 onCompletion(nil, NSError())
 print("Failed creating data")
 return
}
let hash = SHA512.hash(data: payloadJson)
guard let signature               = signData(payload: payloadJson, key: (user?.userKey.privateKey)!) else
{
 print("Could not sign data payload")
 onCompletion(nil, NSError())
 return
}
let params = Payload(
 payload_hash: hash.hexString,
 payload_json: payloadJson,
 signatures: ["user": [
    "signature": signature.base64EncodedString(),
    "type": "ecdsa-sha512"
 ]]
)

let encoding = try? encoder.encode(params).base64EncodedString()

sign data 函数与 Apple 的文档代码非常接近,但我将其包括在内以供参考

private func signData(payload: Data, key: SecKey) -> Data?
{
  var error: Unmanaged<CFError>?
  guard let signature = SecKeyCreateSignature(key,
                                              SecKeyAlgorithm.ecdsaSignatureMessageX962SHA512,
                                              payload as CFData, &error)
  else
  {
     print("Signing payload failed with \(error)")
     return nil
  }
  print("Created signature as \(signature)")
  return signature as Data
}

【问题讨论】:

    标签: php ios swift phpseclib php-openssl


    【解决方案1】:

    实际上,我在编写此问题时进行额外的研究和实验时偶然发现了该解决方案。这个问题当然与密钥或算法无关,而与 Apple 散列数据对象的方式有关。

    在尝试确定为什么我的哈希值在服务器端与在 iOS 设备上创建的哈希值不匹配时,我发现了一个类似的问题。用户 JSONEncoded 数据被散列并签名为 base64Encoded 数据对象,但我不知道(并且不在我能发现的任何文档中)iOS 解码 Data 对象并对原始对象进行散列,然后重新编码(因为这是不透明的代码这可能并不完全准确,但结果是一样的)。因此,在检查用户数据的散列时,我必须先对对象进行 base64decode,然后再执行散列。我曾假设 Apple 会按原样对编码对象进行签名(为了不污染其完整性),但实际上,当 Apple 在签名之前创建摘要时,它会对解码的原始对象进行哈希处理并在原始对象上创建签名。

    因此解决方案是在将对象发送到 openssl_verify 函数之前再次对对象进行 base64decode。

    检查服务器上的哈希

    public function is_hash_valid($payload) {
    
        $server_payload_hash = openssl_digest(base64_decode($payload["payload_json"]), "SHA512");
        $client_payload_hash = $payload["payload_hash"];
    
        if ($client_payload_hash != $server_payload_hash) {
            return false;
        }
    
        return true;
    }
    

    验证服务器上的签名

    function is_signature_valid($data, $signature, $public_key) {
            
        $public_key = openssl_get_publickey($public_key);
    
        $ok = openssl_verify(base64_decode($data), base64_decode($signature), $public_key, "SHA512");
        if ($ok === 1) {
            return true;
        } else {
            return false;
        }
    }
    

    在发现这一点,并验证 openssl_verify 和 phpseclib 的验证功能正常工作后,我几乎考虑完全删除该问题,但意识到如果我在研究中发现了类似的问题,它可能会为我节省很多时间。希望对遇到类似问题的其他人来说,这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-28
      相关资源
      最近更新 更多