如何使用 x509 public 密钥来验证 signed/hashed Alexa 请求 body?

How to use x509 public key to verify a signed/hashed Alexa request body?

我正在尝试完成 here 列出的步骤,以获得我在 Java 中开发的 Alexa 技能。

我收到来自 Alexa 的请求 POST。 header 中的两个是签名证书链 url 和签名。

Amazon SHA1 散列然后使用 X509 密钥对整个 body Alexa 请求进行签名,然后对签名的 body 进行 base64 编码。这就是“签名”。签名证书链是 url,我可以在其中获取包含其 public 密钥的 X509 证书链。

我需要做的是base64解码签名,然后使用X509public密钥解密签名。这给我留下了一个 SHA1 散列请求 body。然后我需要自己对请求的 body 进行 SHA1 哈希并比较两者。

我验证证书链。我提取 public 键。我对 POST 的 body 进行哈希处理并生成派生的哈希值(其 SHA1withRSA)。我使用 base64 解码“签名”,然后使用 public 密钥对其进行解密以获得断言的哈希值。

我无法生成与声明的哈希值相匹配的派生哈希值。这就是我被困的地方,我不明白我做错了什么。我不太了解这种加密的东西,所以也许我遗漏了一些非常简单的东西。

上面 link 的第 8 步是我卡住的地方。

首先,我借用了alexa SDK的代码here。问题是这段代码似乎不起作用:


    Signature signature = Signature.getInstance(ServletConstants.SIGNATURE_ALGORITHM);
    signature.initVerify(signingCertificate.getPublicKey());
    signature.update(body);
        
    if (!signature.verify(Base64.decodeBase64(baseEncoded64Signature.getBytes(ServletConstants.CHARACTER_ENCODING)))) {
        throw new SecurityException("Failed to verify the signature/certificate for the provided skill request");
    }

SIGNATURE_ALGORITHM = SHA1withRSA
CHARACTER_ENCODING = UTF-8。
signingCertificate 是 X509 证书。

这段代码没有给我匹配的派生哈希值和断言哈希值。所以我跟着这个 tutorial 使用了密码 Class.

我创建了一个临时文件并将值硬编码在其中。我从请求中提取了 body。我使用 postman 获取了 x509 证书链的 body。我还从请求中获取了签名header。


    byte[] decodedSignature = Base64.decodeBase64(encodedSignature);
    Cipher cipher = Cipher.getInstance("RSA");
    PublicKey key = signingCertificate.getPublicKey();
    cipher.init(Cipher.DECRYPT_MODE, key);
    
    byte[] decryptedSig = cipher.doFinal(decodedSignature);
    byte[] hashedBody = DigestUtils.sha1(body);

body = 将alexa请求的body转换为byte。
encodedSignature = alexa 请求中提供的签名 header。

显然 decryptedSighashedBody 不匹配。见下文。

decryptedSig: 48,33,48,9,6,5,43,14,3,2,26,5,0,4,20,-68,25,70,-54 ,-63,91,-37,73,34,82,-63,62,45,48,-117,112,42,18,-24,-113

hashedBody: -107,76,55,24,9,79,77,-21,101,57,-103,25,42,-54,-28,5,34,-26,117 ,38

问题是:我不知道问题出在哪里。就像我说的,我不明白这些东西。我只是想建立一个 alexa 技能,这是亚马逊 requires.

的一部分

这是 base64 编码的签名:
fmBSIwM+GIN977W9ztbagtnMalXPJBWat8KwoWBauAIrXHaKvjVlY8hqA/vXEdzPYy7rL0B6Tw9uUeHYah6LU7xISIiUpZjm1Ls2t1Nt2LXbyTgLGdNU4RQJiSxoq+87BEmOOBUNTGiDOveZs/9+KTgQgLgyelG6wHwk34p6w/TgqardQ39vjpzqui63s5/2om1KgJs5e1gt24Cemapr6f+Slz0xmmdLmLZ1Hn7nNgnIB3UjQzcVxU6KYJ1rNfnzZNHFSPcnrZ9ArvUT+M7OM10NfkPp53M6Oy3/5pibV13iQKibFijTCZQEFGLl6fXBgoWpBr1iWyYZbUGTk2+yow==

这是请求的body:
{"version":"1.0","session":{"new":false,"sessionId":"amzn1.echo-api.session.4ebd9d8c- d76c-403f-b82b-952492fffa74","application":{"applicationId":"amzn1.ask.skill.1ac44f3a-696a-4cc0-9944-6b7d9440b394"},"user":{"userId":"amzn1.ask.account.AGO62C2OKQUIGVD4J6SWHKOERZDPPMYLKHP5GAA67TO6Y6KNOGDGGKFHJE6LEYSQTQQ6GJNGSCDIQUYLMYFQXJPV53YEZPLW4AJPOLH7TCMYDKUMZM2QXBSEDEJ43VRLKFF6WUBB47AW7MRKVDE427DQMYX3KIFKO7ZCDPJKQGANEMSNWLWZRICRGVPM6YBOHPV3BB47PZKGSHI"}},"context":{"Viewports":[{"type":"APL","id":"main","shape":"RECTANGLE","dpi":160," presentationType":"STANDARD","canRotate":false,"configuration":{"current":{"mode":"HUB","video":{"codecs":["H_264_42"," H_264_41"]},"size":{"type":"DISCRETE","pixelWidth":1024,"pixelHeight":600}}}}],"Viewport":{"experiences":[{ “arcMinuteWidth”:246,“arcMinuteHeight”:144,“canRotate”:false,“canResize”:false}],“mode”:“HUB”,“shape”:“RECTANGLE”,“pixelWidth”:1024,“pixelHeight” ":600,"dpi":160,"currentPixelWidth":1024,"currentPixelHeight":600,"touch":["SINGLE"],"video":{"codecs":["H_264_42", "H_264_41"]}},"S ystem":{"application":{"applicationId":"amzn1.ask.skill.1ac44f3a-696a-4cc0-9944-6b7d9440b394"},"user":{"userId":"amzn1.ask.account.AGO62C2OKQUIGVD4J6SWHKOERZDPPMYLKHP5GAA67TO6Y6KNOGDGGKFHJE6LEYSQTQQ6GJNGSCDIQUYLMYFQXJPV53YEZPLW4AJPOLH7TCMYDKUMZM2QXBSEDEJ43VRLKFF6WUBB47AW7MRKVDE427DQMYX3KIFKO7ZCDPJKQGANEMSNWLWZRICRGVPM6YBOHPV3BB47PZKGSHI" },"device":{"deviceId":"amzn1.ask.device.AGLWBJS53GJU5GT755HMYMOH7MCGVSVQAICMZGBZSUNVY2OE6DNQFG4K4UMM3R5NPJR6XSHAABZ44VV6BOUR7SVPZF5DJUXXCTEUAQTCCRZSXKHWWS7N4CAEHGK47VGBHJJABSPZ4C4LACWJX65ZBKZ5N6LGHZVXIPHJMGQBPCYGGWZIE","supportedInterfaces":{}},"apiEndpoint":"https://api.amazonalexa.com","apiAccessToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJhdWQiOiJodHRwczovL2FwaS5hbWF6b25hbGV4YS5jb20iLCJpc3MiOiJBbGV4YVNraWxsS2l0Iiwic3ViIjoiYW16bjEuYXNrLnNraWxsLjFhYzQ0ZjNhLTY5NmEtNGNjMC05OTQ0LTZiN2Q5NDQwYjM5NCIsImV4cCI6MTYxMDE1Mjk2MiwiaWF0IjoxNjEwMTUyNjYyLCJuYmYiOjE2MTAxNTI2NjIsInByaXZhdGVDbGFpbXMiOnsiY29udGV4dCI6IkFBQUFBQUFBQUFBb0FiNFRMdW84M2t4c0FmUEsrYjBpS3dFQUFBQUFBQUM0ekx5ZGFPeGlTdms4UlppLzIrWjduMjFvNEdGYmEveHdqcE5pREo5MzNyb2FyQU5WSnYwUHBBbGwvcWlyK0JhUWQxTjhoY2pkVnhXeTJxQUhrOXNBQmNIRnVJcE5hbTRHYVBKVk9QaTdVZXhic1ZrSEtQSm5Vc0lqSmdtL3JCRm80eGtpWEhNZi84UC9wd0VxMVZIK2xKTHo4ZWRoMGZHUUU3cGQ4TDVYcjcwUlZ0UDFkajEvbld5SGJINVR5RHhHVHU0ZUtGVkJ6ODNKTkRXeG5pa0NHeS9lQjcwNWZBMWxxSDd0QUVRN2lPZUhDR09ZeHh1NE9xcFBvTWgzNHlvUk5wbVZidGd4MGJDQ0dzL3crZHIxRVlIRG50RTdyWnRVM29zei9FOE01UThLV2NCcy8zNERqZlAvaUxvTXVHaWNnOWtRbGNZdm1qWEhpRkNJMDczUEo5NlZweWE3cURVbzc1YmxSZWZuM00wQnJNVVl1VGYzTnN4aE .FAaSO9NwDL_lTSST16Fs0Cs-VlYLDpBfD02-m5zYwYvxKDNXcDooRN5SjsLetsNnXT0tyCq20QboCBCqESaDaq9K5RBzkhEQc2BWYp31P9gyEpGIn23YQbm_2JpEDzGwIcZ6CwtXlGyJee7IdZCqDcD9uC7Ytnjf2k-mUAjrTtx4t5XCoy67HhSACh14ySgooW6PRYXKiNrdrOz1VW1dmQKy1obHcAX2fHU7SIEdrQU1Q11-6J2dUH6S2RuMncshhg17GWuzGXGIJW7n-JY5VPEoPSnxXOHnAXZeaCxabVBR9ryaeZUwUxGMF6ZQTBR13L8ea3575os8eBcM6ALtUQ"}},"request":{"type":"SessionEndedRequest","re​​questId":"amzn1.echo-api.request.d5bd94a6-2011-4f72-b39d-5fcc3c276536"," timestamp":"2021-01-09T00:37:42Z","locale":"en-US","re​​ason":"USER_INITIATED"}}

我真的希望它是一些愚蠢而简单的东西。我感谢任何花时间阅读本文以帮助我的人。

首先,数字签名不是用私钥加密的;亚马逊在那里欺骗你,参见 https://security.stackexchange.com/questions/159282/can-openssl-decrypt-the-encrypted-signature-in-an-amazon-alexa-request-to-a-web 除了没有 Java 之外,这基本上是同一个问题。 Java 加密加剧了这一点,因为它是在 1990 年代设计的,当时这个错误仍然相当普遍,因此 打算 Cipher 对象是用于加密和解密接受 'backwards' 使用 RSA 密钥并在内部将它们更改为 Signature 方案 'NoneWithRSA' 中使用的操作(可能是被认为是一个伪方案,因为它并不真正匹配 PKCS1)。

在这一点上展开,您的 'decrypted'(更准确地说,已恢复)值和简单散列之间的区别在于,现在这里使用的是 PKCS1v1 签名方案改名 RSASSA-PKCS1-v1_5 in PKCS1v2, actually has four steps:

#1 散列数据

#2 将散列值和算法编码到 DigestInfo ASN.1 结构中,以 DER 编码,这相当于为每个算法添加一个固定前缀

#3-5 预先填充 00 01 FF...(至少 8)00

(8.2.1#2) 将结果视为数字 m,应用执行 m ^ d mod n 的 RSASP1(或验证 8.2.2#2 应用执行 s ^ e [ 的 RSAVP1 =53=] n; 这是在上面的三个填充步骤之前声明的,但实际上也可以在之后)

backwards-Cipher 操作仅执行或反转上面的第三步和第四步;您已经添加了第一步,但没有添加第二步,因此 您的 'decrypted' 值实际上是一个包含一些元数据的 DigestInfo 结构,SHA1 算法的 OID,应该对应数据的哈希值。

创建或删除 DigestInfo 结构失败也是一个非常常见的错误和问题;在 https://crypto.stackexchange.com/questions/87006/why-is-data-signed-with-sha256-rsa-pkcs-and-digest-signed-with-rsa-pkcs-differen/#87022 查看我的列表。

但不匹配。 嵌入在恢复的 DigestInfo 中的散列值与您对数据计算的散列值不同(我也得到)。这强烈表明 一些 在您的数据和亚马逊签署的数据之间发生了变化,但我不知道是什么;当然,您的数据表面上看起来像 Alexa 请求。抱歉:-)