关于使用 Cloud KMS 进行签名验证的问题

Posted

技术标签:

【中文标题】关于使用 Cloud KMS 进行签名验证的问题【英文标题】:Question about signature verification using Cloud KMS 【发布时间】:2021-09-15 08:19:06 【问题描述】:

我正在尝试验证使用 Google 的云 KMS 生成的签名,但我不断收到无效响应。

这是我的测试方法:

const versionName = client.cryptoKeyVersionPath(
      projectId,
      locationId,
      keyRingId,
      keyId,
      versionId
    )

    const [publicKey] = await client.getPublicKey(
      name: versionName,
    )

    const valueToSign = 'hola, que tal'

    const digest = crypto.createHash('sha256').update(valueToSign).digest()

    const [signResponse] = await client.asymmetricSign(
      name: versionName,
      digest: 
        sha256: digest,
      ,
    )

    const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)

    if (!valid) return console.log('INVALID SIGNATURE')

    console.log('SIGNATURE IS VALID!')

// output: INVALID SIGNATURE

此代码将始终记录“无效签名”除非我使用原始消息而不是其哈希:

const valid = crypto.createVerify('sha256').update(valueToSign).verify(publicKey.pem, signResponse.signature) // true

但是使用本地私钥,我可以对消息进行签名并使用它们的哈希验证它们:

const valueToSign = 'hola, the tal'
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');

const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');

const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);
const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true

为什么会这样? kms 签名有什么不同吗?

【问题讨论】:

【参考方案1】:

根据加密模块文档中的this example 和您的观察,我想说您可能误解了client.asymmetricSign 的工作原理。让我们分析一下会发生什么:

您的本地私钥代码:

const valueToSign = 'hola, the tal'

// Create sha256 hash
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');

// Let signer sign sha256(hash)
const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');
// We now got sign(sha256(hash))

// Let verifier verify sha256(hash)
const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);

const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true

我们正在使用verify(sha256(hash)) 验证sign(sha256(hash))


您的 KMS 代码:

const valueToSign = 'hola, que tal'

// Create sha256 hash
const digest = crypto.createHash('sha256').update(valueToSign).digest()

// Let KMS sign the hash
const [signResponse] = await client.asymmetricSign(
    name: versionName,
    digest: 
        sha256: digest, // we already say "we hashed our data using sha256"
    ,
);
// We now got `sign(hash)`, NOT `sign(sha256(hash))` (where hash == digest)

// Let verifier verify sha256(hash)
const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)

我们正在使用verify(sha256(hash)) 验证sign(hash)


基本上,您在本地签署您的哈希并验证已签名的哈希。使用 KMS,您正在签署 您的数据 并验证已签名的哈希,这实际上是您已签署的 数据,因此您第二次尝试使用 .update(valueToSign) 有效。

解决方案?在让 KMS 签名之前再次散列您的 sha256 哈希,因为 KMS 期望待签名数据的 sha256 散列,而 crypto 期望待签名数据 (考虑到你传递给createSign的算法,它会自行散列)

【讨论】:

感谢您的详细回答。我实际上误解了加密货币的工作原理,但现在很清楚了。给你150分!【参考方案2】:

换句话说,答案与 Kevin 的答案非常相似,但从不同的角度来看。

当您使用crypto.createSign(<algorithm>)crypto.createVerify(<algorithm>) 时,您表示将分别用于签名创建和验证的摘要algorithm

当您在返回的 SignVerify 对象上调用 update 时,您需要按原样提供数据crypto 将在您适当地消化该信息时signverify 稍后。

相比之下,GCP KMS asymmetricSign operation 需要在原始数据上使用指定的algorithm 生成消息digest 作为参数。这就是为什么您需要首先使用crypto.createHash 计算消息摘要。

但是,如前所述,请注意,这一事实不会改变crypto 验证过程的行为,它始终需要原始数据作为输入,这就是为什么您的代码在您传递原始数据时有效的原因散列。

尽管您在问题中提供了一个工作示例以供参考,但 GCP documentation 提供了其他示例。

【讨论】:

以上是关于关于使用 Cloud KMS 进行签名验证的问题的主要内容,如果未能解决你的问题,请参考以下文章

php SHA256WithRSA签名验签&加密解密

Cloud Function 使用 KMS 密钥解密的权限被拒绝

pay支付参数验签失败咋回事

接口安全接口合法性验证加密验签SIGN 签名规则

使用 Google Cloud KMS 进行 Firebase 实时数据库加密的最佳实践 [关闭]

JMeter BeanShell 实现接口签名验签及加解密