php 实现REST API数字签名

soat7uwm  于 2023-06-28  发布在  PHP
关注(0)|答案(1)|浏览(172)

我目前正在尝试实现一个带有数字签名的最小REST API。
我理解的基本概念是,发送方使用HMAC使用其私钥对有效载荷进行签名,接收方使用公钥验证此签名。
然而,我在实现一个工作示例时遇到了一些困难。
我的API是用PHP实现的,客户端是用Python实现的。
我在PHP中验证请求如下

public function verify_request(Request $request)
{
    $method = $request->method();
    $timestamp = $request->header("timestamp");
    $public_key = $request->header("api-key");
    $signature = $request->header("signature");
    $url = $request->fullUrl();

    $data = $method.$timestamp.$url;

    $result = openssl_verify($data, $signature, $public_key, OPENSSL_ALGO_SHA256);
    Logger::info("Result : ".$result);
    return $result;
}

我已经在其他几个API上使用这个Python中的最小示例成功地签署了请求

import requests
import hashlib
import hmac
import time

public_key = ""
private_key = ""

method = "GET"
base_url = "https://testing-api.com"
endpoint = "/private/test"
url = base_url + endpoint
timestamp = str(int(time.time()))

signature_data = method + timestamp + url

signature = hmac.new(private_key.encode("utf-8"), signature_data.encode("utf-8"), hashlib.sha256).hexdigest()

headers = {
    "api-key": public_key,
    "timestamp": timestamp,
    "signature": signature,
}

response = requests.request(method, url, headers=headers)
print(response)

但是我不明白的是用的是什么样的钥匙。
例如,我生成的RSA密钥如下

public_key = """-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL7Jz6mHX1cau3wlc6QrGW+kcVHO0HPj
iwUDxdM1B8KSLgkfle4+Snq6HhOp5VXdQXZkYF4u9ctWbx/sB2oiD1sCAwEAAQ==
-----END PUBLIC KEY-----"""

private_key = """-----BEGIN PRIVATE KEY-----
MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAvsnPqYdfVxq7fCVz
pCsZb6RxUc7Qc+OLBQPF0zUHwpIuCR+V7j5KeroeE6nlVd1BdmRgXi71y1ZvH+wH
aiIPWwIDAQABAkAnxyvkzLS0FH7Cg4x4zgOfo0l9JQGRJ//0K7UzM/tKNZOy881O
+qEDld7XMaUOmyGd/FxSskrJrzWTSqEZtQ8BAiEA7wsKUjGWFyTfuMkj/3/E0XI2
YYEaTRpROl4NN6FUttcCIQDMUnn7JDovxYlp17QhyOb41qc1I402QZ6zNiz33ytP
HQIgA2rr/drZo4ESdcjia9++x6PTZTd8Ucfji2sW00nKNUcCIFo7Oh9Mml2qcMrL
NYOOA2J0+RaggqYpSHqAPE+iwK+JAiB2YbMutRh5EDyTcI2ql/hYFLlAJVT/E1AN
/ItM1KCQQQ==
-----END PRIVATE KEY-----"""

但是它与我在其他几个API上测试的API密钥的格式不匹配,当我尝试将公钥传递到头中时,我得到了以下错误

requests.exceptions.InvalidHeader: Invalid return character or leading space in header: api-key

例如,在我测试的一个API上,密钥具有以下格式

public_key = "Tub3FH4CLjHezvRcSKdeE18a9VrtzL"
private_key = "tBssW3InKRq09OMLkveyuZ65LDRokjVX8FXO0CL5VpdaV73NWayUvJSSAtCs"

但是当我尝试使用这些API密钥登录到我的Python脚本时,我在PHP上遇到了以下错误

ErrorException: openssl_verify(): supplied key param cannot be coerced into a public key

所以我这里有两个关于API密钥格式的问题。
首先,我想知道这些API使用什么类型的键,这样我就可以将键作为简单的字符串而不是多行字符串。
其次,我想知道为什么openssl_verify不认为这些API密钥是正确的,因为我认为签名是如何验证的。

**编辑:**正如@Toppaco在他的回答中所说,我的问题来自这样一个事实,即我认为这些API使用非对称密钥,并且只保留公钥,而实际上他们使用两个不相关的密钥,一个用于身份验证,一个用于签名,因此我感到困惑。

gtlvzcf8

gtlvzcf81#

您似乎混淆了两个签名概念,HMACRSA。HMAC在两侧使用相同的密钥,RSA在两侧应用不同的密钥,形成密钥对(私钥和公钥)。私钥是秘密的,用于签名。公开不是秘密的,并且被传递到验证侧,在那里它被用于验证。更多信息s例如How to decide whether to use digital signature (RSA) or HMAC。这两种机制都确保了数据的完整性,RSA还验证了签名者。例如JWT: Choosing between HMAC and RSA
两者的关键类型完全不同。HMAC使用(随机)字节序列作为密钥,RSA需要以不同密钥格式提供的某些参数(模数、私有指数、公共指数等)。
例如,您发布的公钥是X.509/SPKI格式的PEM编码RSA公钥,而您发布的私钥是PKCS#8格式的PEM编码RSA私钥。
请注意,两个密钥都太小,(模数)大小为512位。如今,出于安全原因,它们的大小必须至少为2048位。
你的PHP代码使用RSA(用公钥验证),你的Python代码应用HMAC。两者都不兼容,您还必须在Python代码中使用RSA(使用私钥签名)。
如果没有进一步的信息,我无法将键 *Tub... * 和 *tBs... * 置于上下文中。但是它们不能用作HMAC密钥,因为HMAC在两侧应用相同的密钥。而且它们显然也不是RSA密钥。
编辑:根据OP在评论中链接的文档,两个发布的密钥public_key = "Tub..."private_key = "tBs..."是用于对Delta Exchange API的请求进行身份验证的API密钥和API秘密。API secret用于使用HMAC对请求进行签名 * 和 * 验证,如文档的示例代码所示。特别地,API秘密和API密钥不形成由如所使用的私钥和公钥组成的相关密钥对,例如在RSA的上下文中。

相关问题