我有一个简单的文本字符串,需要在 Javascript 中进行签名并在服务器(PHP)上进行验证。为了开始测试,我首先创建了一个密钥对:
// Function to generate a new RSA key pair
async function generateKeyPair() {
const keyPair = await window.crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
hash: { name: "SHA-256" },
},
true,
["sign", "verify"]
);
const publicKey = await window.crypto.subtle.exportKey("spki", keyPair.publicKey);
const privateKey = await window.crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
return {
privateKey: privateKey,
publicKey: publicKey,
};
}
我获得了私钥和公钥:
privateKey: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDw4Ees4C+vsTUQodgWKIsj3Ni67RG3ny9xY1KjCaatu2o/ev5hS4yrxxWLZAFU9mt/rfNmzzby3mqlWPWm8Df91Mue6wNTsN2yMnHw+XvcvovCngSTH4H2zY+uAhEiG+u+vzGqbzxm0JB3ybX5kYEMK2iKoALq1ASJ781gyy7AsCf/Ck+OvIE4in1kNm4a5NUgbyuflWerMIB7FUQ7h/+XlLn3F2bvC1SWWxKsmQ/dF5fYpZaAV2KvVw2LMnkWdU536an9vxj5LZIJyzfNQv/foNGcUh8iT1tLe8jV4eYrAcqiLnG+iZFFc3X5F33WUILmRvCg11bec6ic1NwuY8eDAgMBAAECggEAQq7kSNCbgvj85sjXSHMa6ee2vDDrKblQ6gQEGYyPbyMmK8LB72951wg7R6Z80+eQJP2kF38gCCZYwcOZ5gg0h/nEEQ+gkSeyiCV886gtiRPbHxqdy5j6YrfPoe2Cjr3KCrllZ3h58UCl7fOShC+q2RKfU1ku1ZGyW/leEwDMxZy1PISHFHtmd43LdrkWgyNk4TIpNRzizx+gxNeyQUEZDfkUu4mFP/weWM26lLyaE+RkPqvFnLjXckvgno1bY8Hq2yywVkyvfBo0tQvVBtNP5WTEyOvNGylc46pnVBODrSUn5q4ZNdi56fd7WFPBFySAVQiA0uLgaWYOuBUGMyc5CQKBgQD+wcyYL2IgA5O2c6Jp9cJV9xQVvWEQ/sVUDJgRVhOxAqw9r2LmUe7tRnWYEI4Sz9g/ejFq8fkL+h2lQbGB+ZKlu5EVHrmfuYf3zo80QKMWC1XKbYnw0HKkMlOqxiMYyu6PqFX59icmcZ58k1m9h2br5f7GGGWAFY8yFgRUIUR6FwKBgQDyDSSbH7WoqUUhYNvY9wKUUYM8uZSAC1TPfuR/ZvAec3cZxMJyOnY88MPOh63vUMzTUt6AAyps2EFPa0UGuysevMaXSL+MAQQzDfnEC2KfeRqkVOKYPrjjjxIl5mQJCacpB7rdzLszmtJJ9G99lTqeGuVa3mhlJupqckYbbdO9dQKBgQD9zf4TMEHGO0oSX6nTfvCZzIrKDd6CnA/j6JgnzWXY2BzZZ75UUBSFd8j4MqYYv9FljEtnjKLd99VJKuW54/bh/rhQHkg4hRKdI8EwAaV49NoHzpG6xTExvKH2ZWfZ73M01DSzzzS57EBFRFgHpro3EvB8UxnsPY5oC99MIcijCQKBgBn16OguVXh6dyymS84QaBlqSK4ZpWC6VmVO0ckMTFKnxa1g2g4QUSAmHoonKTOSsfU0XSLTtBgqdY7EDYo0RuKsEoylQ84LSd0D8bbiFbjO71mStR7pE0Fs1eB0vmPtwhz3dEZXr/hP8Z/29II+oCPW9KRzWDUJIHk8OmK0u9IFAoGAEOWhm/zaXMNJ+oBcvBbCKTZ0XInzvV4SqhC6Bj9aC8wqCe5QKyKl9HglG9J+o3D+hIEcMXGvIv1KB3xDStCQQKcDOrD/8tGZtstSONaNzeGg0hUY9SKd7R2wMPEWufzccFE+zVG5hHUg+eQrnzXdXkG8hW1QxQxgoDkC3DNVsnE="
publicKey: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8OBHrOAvr7E1EKHYFiiLI9zYuu0Rt58vcWNSowmmrbtqP3r+YUuMq8cVi2QBVPZrf63zZs828t5qpVj1pvA3/dTLnusDU7DdsjJx8Pl73L6Lwp4Ekx+B9s2PrgIRIhvrvr8xqm88ZtCQd8m1+ZGBDCtoiqAC6tQEie/NYMsuwLAn/wpPjryBOIp9ZDZuGuTVIG8rn5VnqzCAexVEO4f/l5S59xdm7wtUllsSrJkP3ReX2KWWgFdir1cNizJ5FnVOd+mp/b8Y+S2SCcs3zUL/36DRnFIfIk9bS3vI1eHmKwHKoi5xvomRRXN1+Rd91lCC5kbwoNdW3nOonNTcLmPHgwIDAQAB"
这是我用来使用私钥对数据(字符串)进行签名的脚本:
// Function to sign the data
async function signRequestData(data, privateKey) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
const signatureBuffer = await window.crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
privateKey,
hashBuffer
);
const signatureArray = new Uint8Array(signatureBuffer);
const signatureBase64 = btoa(String.fromCharCode.apply(null, signatureArray));
return signatureBase64;
}
然后我创建了一个脚本来验证数据(仍然是 JavaScript),以验证我正在做的事情:
// Function to verify the signature
async function verifySignature(data, signatureBase64, publicKey) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const signatureArray = new Uint8Array(atob(signatureBase64).split("").map((c) => c.charCodeAt(0)));
const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
const isSignatureValid = await window.crypto.subtle.verify(
{ name: "RSASSA-PKCS1-v1_5" },
publicKey,
signatureArray,
hashBuffer
);
return isSignatureValid;
}
脚本返回了
true
,所以我继续下一步,验证 PHP 中的数据:
// Function to verify the signature
public function verifySignature($data, $signatureBase64, $publicKey) {
// Import public key
$publicKeyResource = openssl_pkey_get_public("-----BEGIN PUBLIC KEY-----" . "\n" . $publicKey . "\n" . "-----END PUBLIC KEY-----");
if ($publicKeyResource === false) {
// Handle error (unable to import public key)
die("Error importing public key");
}
// Verify the signature
$isSignatureValid = openssl_verify($data, $signatureBase64, $publicKeyResource, OPENSSL_ALGO_SHA256);
// Free the public key resource
openssl_free_key($publicKeyResource);
return ($isSignatureValid === 1)
}
此脚本永远不会返回 1 表示“有效”。我不确定问题是否在于密钥的生成方式。如果有帮助的话,我可以用 PHP 生成该对。 @Topaco 这就是我正在谈论的整个问题。
JavaScript 和 PHP 之间的签名编码或解码方式似乎可能存在问题。让我们确保数据处理方式的一致性。
在 JavaScript SignRequestData 函数中,使用 btoa 将签名转换为 Base64。在您的 PHP 代码中,您将 openssl_verify 与原始签名数据结合使用。签名在传递给 openssl_verify 之前应进行 base64 解码
async function signRequestData(data, privateKey) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
const signatureBuffer = await window.crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
privateKey,
hashBuffer
);
return new Uint8Array(signatureBuffer);
}
更新 JavaScript 中的 verifySignature 函数以直接使用原始签名字节:
async function verifySignature(data, signatureArray, publicKey) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hashBuffer = await window.crypto.subtle.digest("SHA-256", dataBuffer);
const isSignatureValid = await window.crypto.subtle.verify(
{ name: "RSASSA-PKCS1-v1_5" },
publicKey,
signatureArray,
hashBuffer
);
return isSignatureValid;
}
在验证之前更新您的 PHP 代码以对签名进行 Base64 解码:
public function verifySignature($data, $signatureBase64, $publicKey) {
// Import public key
$publicKeyResource = openssl_pkey_get_public("-----BEGIN PUBLIC KEY-----" . "\n" . $publicKey . "\n" . "-----END PUBLIC KEY-----");
if ($publicKeyResource === false) {
// Handle error (unable to import public key)
die("Error importing public key");
}
// Decode the base64-encoded signature
$signature = base64_decode($signatureBase64);
// Verify the signature
$isSignatureValid = openssl_verify($data, $signature, $publicKeyResource, OPENSSL_ALGO_SHA256);
// Free the public key resource
openssl_free_key($publicKeyResource);
return ($isSignatureValid === 1);
}