根据下面非常有用的评论,我重写了这个问题。
使用https://www.php.net/manual/en/function.openssl-verify.php中的代码作为示例:
<?php
// $data is assumed to contain the data to be signed
// fetch certificate from file and ready it
$fp = fopen("path/file.pem", "r");
$cert = fread($fp, 8192);
fclose($fp);
// state whether signature is okay or not
// use the certificate, not the public key
$ok = openssl_verify($data, $signature, $cert);
if ($ok == 1) {
echo "good";
} elseif ($ok == 0) {
echo "bad";
} else {
echo "ugly, error checking signature";
}
?>
我们正在开发一个项目,我们接收包含“签名”的数据。这是数据 (UTF8) 的已知部分,已由发送者使用私有 x.509 证书和 base64 编码进行签名。作为数据的一部分,我们收到他们的 Base64 公共证书。
我们可以根据发送的信息构建“检查数据”(
$data
),并且我们收到了$signature
,因此使用上面的代码示例应该可以工作,但是......
上面的示例从 PEM 文件中获取证书,但我们只是获得了 Base64 编码的公共证书。
以前没有使用过此类东西,如何处理 Base64 证书以便我可以将其用作上面代码中的 $cert ?
编辑:
感谢@Sammitch 对证书的澄清。我现在可以从证书访问公钥。然而,
openssl_verify
仍然无法验证,我想知道这是否是因为原始无符号数据是如何组合在一起的。
openssl_verify
要求 $data
是字符串,但在本例中,数据创建如下:
签名前缀是动词、目的地和日期时间戳的串联,所以类似于:
POST;https://example.com/webhook;2023-10-12T20:53:50+00:00
然后将其转换为字节。
执行此操作的 VB.Net 流程是这样的:
Dim signaturePrefix As String = verb + ";" + Destination + ";" + SignatureDate + ";"
Dim signaturePrefixBytes As Byte() = Encoding.UTF8.GetBytes(signaturePrefix)
Dim originalLength As Integer = signaturePrefixBytes.Length
Array.Resize(signaturePrefixBytes, originalLength + hashBytes.Length)
Array.Copy(hashBytes, 0, signaturePrefixBytes, originalLength, hashBytes.Length)
发送的 JSON 的哈希值也会转换为字节,然后合并两个字节数组。然后使用发送者的 X509 证书对该合并值进行签名。
所以...鉴于
openssl_verify
要求$data
是一个字符串,但这是一个字节数组,我该如何使用openssl_verify
来验证签名?
我们已经找到了问题的根源,并且这个解决方案有效:
<?php
// Obtain the necessary headers and context variables
$sentSignatureBase64 = "rYYN6S87D3###REDACTED###MfVYeWA==";
$publicCertBase64 = "MIIHA###REDACTED###O0m/kRrA=";
$signedTimestamp = "2023-10-16T12:00:30+01:00";
$contentHashBase64 = "PT8Zbb###REDACTED###PeZEZ3Tc=";
$verb = "POST";
$destination = "https://webhook.site/###REDACTED###";
$body = "[{\"payload\":{###REDACTED###}}]";
try {
// Create Certificate
$public_key_pem="-----BEGIN CERTIFICATE-----\n". wordwrap($publicCertBase64, 64, "\n", false) ."\n-----END CERTIFICATE-----";
$publicCertificate = openssl_pkey_get_public($public_key_pem);
echo "public_key_pem---------".PHP_EOL;
echo $public_key_pem.PHP_EOL;
echo "---------".PHP_EOL;
echo "Key Details---------".PHP_EOL;
$keyData = openssl_pkey_get_details($publicCertificate);
var_dump($keyData);
echo "---------".PHP_EOL;
// Get bytes for the sent signature
$sentSignatureBytes = base64_decode($sentSignatureBase64);
// Build the signature prefix based on the received data
$receivedSignaturePreifx = $verb . ";" . $destination . ";" . $signedTimestamp . ";";
echo "\$receivedSignaturePreifx = ".$receivedSignaturePreifx.PHP_EOL;
// Get the SHA-256 hash of the body
$body = empty($body) ? "{}" : $body;
$hashBytes = hash('sha256', $body, true);
// Combine the two sets of bytes
$combinedBytes = $receivedSignaturePreifx . $hashBytes;
// Verify the signature
$isSignatureValid = openssl_verify($combinedBytes, $sentSignatureBytes, $publicCertificate, "sha256WithRSAEncryption") === 1;
if (!$isSignatureValid) {
$problemDetail = array(
"statusCode" => 401,
"detail" => "Signature failed validation.",
"isSignatureValid" => $isSignatureValid
);
echo json_encode($problemDetail);
return;
}
$results = array(
"detail" => "Successful verification.",
"isSignatureValid" => $isSignatureValid
);
echo json_encode($results);
} catch (Exception $ex) {
$problemDetail = array(
"statusCode" => 500,
"detail" => "Error while decrypting signature: " . $ex->getMessage()
);
echo json_encode($problemDetail);
return;
}
?>