简体   繁体   English

Openssl 验证失败,iOS Secure Enclave 创建签名

[英]Openssl verify fails with iOS Secure Enclave created signature

I am attempting to hash and sign user data on iOS (14.4), send that to my server, and have the server verify the hash and the signature with a previously uploaded public key (sent on keypair generation during user creation).我正在尝试 hash 并在 iOS (14.4) 上签署用户数据,将其发送到我的服务器,并让服务器验证 hash 和在用户创建期间生成的公钥签名。 It seems a number of people have run into issues with this, but all of the answers I've been able to find are very old , don't factor in using Apple's Secure Enclave, or revolve around signing and verifying on the same iOS device.似乎很多人都遇到了这个问题,但我能找到的所有答案都非常老了,不要考虑使用 Apple 的 Secure Enclave,或者在同一个 iOS 设备上签名和验证.

The general workflow is: User creates an account on iOS, and a random keypair is created on the device with the private key remaining in the Secure Enclave, while the public key is converted to ASN.1 format, PEM encoded and uploaded to the server.一般的工作流程是:用户在 iOS 上创建一个账户,在设备上创建一个随机密钥对,私钥保留在 Secure Enclave 中,而公钥转换为 ASN.1 格式,PEM 编码并上传到服务器. When the user later signs data, the data is JSONEncoded, hashed with sha512, and signed by their private key in the Secure Enclave.当用户稍后对数据进行签名时,数据会经过 JSON 编码,使用 sha512 进行哈希处理,并由他们在 Secure Enclave 中的私钥进行签名。 This is then packaged into a base64EncodedString payload, and sent to the server for verification.然后将其打包到 base64EncodedString 有效负载中,并发送到服务器进行验证。 The server first verifies the hash using openssl_digest and then checks the signature using openssl_verify.服务器首先使用 openssl_digest 验证 hash,然后使用 openssl_verify 检查签名。

I have been unable to get the openssl_verify method to successfully verify the signature.我一直无法获得 openssl_verify 方法来成功验证签名。 I have also attempted using the phpseclib library (to get more insight into why the verification fails) without success.我还尝试使用 phpseclib 库(以更深入地了解验证失败的原因)但没有成功。 I understand phpseclib uses the openssl library if it is available, but even if this is disabled, phpseclib's internal verification fails because the resulting values after modulus do not match.我了解 phpseclib 使用 openssl 库(如果可用),但即使禁用此功能,phpseclib 的内部验证也会失败,因为模数后的结果值不匹配。 Interestingly, phpseclib converts the public key to what looks like PKCS8 formatting with a large amount of padding.有趣的是,phpseclib 将公钥转换为看起来像带有大量填充的 PKCS8 格式。

It appears the public key is being parsed and loaded properly by openssl, as a proper reference is being created prior to verification. openssl 似乎正在正确解析和加载公钥,因为在验证之前正在创建正确的参考。 However, since the private key is opaque (residing in the Secure Enclave) I don't have a way to externally "check" how the signatures themselves are generated/encoded or if the same signature would be created outside of the iOS device.但是,由于私钥是不透明的(位于 Secure Enclave 中),我无法从外部“检查”签名本身是如何生成/编码的,或者是否会在 iOS 设备之外创建相同的签名。 I'm wondering if I have an encoding error, or if external verification is possible with keys generated in the Secure Enclave.我想知道我是否有编码错误,或者是否可以使用 Secure Enclave 中生成的密钥进行外部验证。

iOS Public Key Upload method - I am using CryptoExportImportManager which converts the raw bytes to DER, adds the ASN.1 header, and adds the BEGIN and END key tags. iOS 公钥上传方法- 我正在使用CryptoExportImportManager将原始字节转换为 DER,添加 ASN.1 header,并添加 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
}

An example of what one of the public keys looks like after upload上传后其中一个公钥的外观示例

-----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()

The sign data function is pretty close to Apple's documentation code, but I'm including it for reference符号数据 function 非常接近 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
}

I actually stumbled upon the solution while doing additional research and experimentation while writing this question.实际上,我在写这个问题时做额外的研究和实验时偶然发现了这个解决方案。 The problem of course had nothing to do with the keys or algorithms, and everything to do with the way Apple hashes data objects.这个问题当然与密钥或算法无关,而与 Apple 散列数据对象的方式有关。

I had discovered a similar problem when trying to determine why my hashes were not matching on the server-side vs the ones created on the iOS device.在尝试确定为什么我的哈希在服务器端与在 iOS 设备上创建的哈希不匹配时,我发现了一个类似的问题。 The user JSONEncoded data is hashed and signed as a base64Encoded data object, but unknown to me (and not in any documentation I could discover) iOS decodes the Data object and hashes the raw object, and re-encodes it (since this is opaque code it's possible this is not precisely accurate, but the result is the same). The user JSONEncoded data is hashed and signed as a base64Encoded data object, but unknown to me (and not in any documentation I could discover) iOS decodes the Data object and hashes the raw object, and re-encodes it (since this is opaque code这可能并不完全准确,但结果是一样的)。 Therefore when checking the hash on the user data, I had to first base64decode the object, and then perform the hash.因此,在检查用户数据上的 hash 时,我必须先对 object 进行 base64decode,然后再执行 hash。 I had assumed that Apple would sign the encoded object as is (in order to not contaminate its integrity), but in fact, when Apple creates the digest before signing, it hashes the decoded raw object and creates a signature on the raw object.我曾假设 Apple 会按原样对编码的 object 进行签名(为了不污染其完整性),但实际上,当 Apple 在签名之前创建摘要时,它会对解码的原始 object 进行哈希处理,并在原始 ZA8CFDE6331C49EB2ACZ96F8 上创建签名

Therefore the solution was to again base64decode the object before sending it to the openssl_verify function.因此,解决方案是在将 object 发送到 openssl_verify function 之前再次对其进行 base64 解码。

Checking the hash on the server检查服务器上的 hash

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

Verifying the signature on the server验证服务器上的签名

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

After discovering this, and verifying that openssl_verify and phpseclib's verify function worked correctly, I almost considered deleting the question entirely but realized that if I had discovered a question similar to this in my research, it might have saved me a good deal of time.在发现这一点并验证 openssl_verify 和 phpseclib 的 verify function 工作正常后,我几乎考虑完全删除该问题,但意识到如果我在研究中发现了与此类似的问题,它可能会为我节省大量时间。 Hopefully to anyone else that has a similar issue, this will prove helpful.希望对其他有类似问题的人来说,这将证明是有帮助的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM