phpseclib 未验证 window.subtlecrypto 中生成的签名
phpseclib not verifying a signature generated in window.subtlecrypto
所以是的,我的老板想在他的系统中加密,他希望消息在 js 中签名并在 php 中验证。目前,我正在使用 mozilla 的 subtlecrypto api 生成 RSA-PSS 密钥并签名,并使用 phpseclib 进行验证。事实是,事实并非如此。
使用js密钥,phpseclib可以很好地进行签名验证,但是无法处理js签名。
这是我的代码。
JS:
function keys(){
var cryptoObj = window.crypto || window.msCrypto;
let msg = '///';
if(!cryptoObj)
{
alert("Crypto API is not supported by the Browser");
return;
}
window.crypto.subtle.generateKey({
name: "RSA-PSS",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
)
.then(function(key) {
publicKey = key.publicKey;
privateKey = key.privateKey;
// For Demo Purpos Only Exported in JWK format
if (document.getElementById('public').value == "") {
window.crypto.subtle.exportKey("spki", key.publicKey).then(
function (keydata) {
publicKeyhold = keydata;
let exported = publicKeyhold;
const exportedAsString = ab2str(exported);
const exportedAsBase64 = window.btoa(exportedAsString);
const pemExported = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
document.getElementById('public').value = pemExported;
}
);
}
if (document.getElementById('private').value == "" ) {
msg = document.getElementById('msg').value;
window.crypto.subtle.exportKey("pkcs8", key.privateKey).then(
function (keydata) {
privateKeyhold = keydata;
const priv = privateKeyhold;
const privExportedAsString = ab2str(priv);
const privExportedAsBase64 = window.btoa(privExportedAsString);
const privPemExported = `-----BEGIN RSA PRIVATE KEY-----\n${privExportedAsBase64}\n-----END RSA PRIVATE KEY-----`;
document.getElementById('privJSON').data = key.privateKey;
document.getElementById('private').value = privPemExported;
}
);
}})
window.crypto.subtle.sign({
name: "RSA-PSS",
saltLength: 128, //the length of the salt
},
//from generateKey or importKey above
document.getElementById('privJSON').data,
getMessageEncoding())
//ArrayBuffer of data you want to sign
.then(function(signature) {
//returns an ArrayBuffer containing the signature
console.dir(ab2str(signature));
document.getElementById("cryptmsg").value = window.btoa(ab2str(signature)) ;
})
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function asciiToUint8Array(str) {
var chars = [];
for (var i = 0; i < str.length; ++i)
chars.push(str.charCodeAt(i));
return new Uint8Array(chars);
}
function bytesToHexString(bytes) {
if (!bytes)
return null;
bytes = new Uint8Array(bytes);
var hexBytes = [];
for (var i = 0; i < bytes.length; ++i) {
var byteString = bytes[i].toString(16);
if (byteString.length < 2)
byteString = "0" + byteString;
hexBytes.push(byteString);
}
return hexBytes.join("");
}
function getMessageEncoding() {
const messageBox = document.getElementById('msg');
let message = messageBox.value;
let enc = new TextEncoder();
return enc.encode(message);
}
php:
<?php
include('Crypt/RSA.php');
define('CRYPT_RSA_PKCS15_COMPAT', true);
$pubK = $_POST['public'];
$privK = $_POST['private'];
echo $pubK."<br><br>";
echo $privK."<br><br>";
$sign = ($_POST['cryptmsg']);
$txt = $_POST['msg'];
$rsa = new Crypt_RSA();
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PSS);
$rsa->setSaltLength(128);
$rsa->setMGFHash("SHA256");
$rsa->setHash("SHA256");
$rsa->loadKey($privK);
$serverSign = $rsa->sign($txt);
$rsa->loadKey($pubK);
echo "text: ".$txt;
echo "<br><br>js sign: <br>".($sign)."<br><br>";
echo "seclib sign: <br>".base64_encode($serverSign);
echo "<br><br>";
$jsTest = $rsa->verify($txt, base64_decode($sign)) ? 'verified' : 'unverified';
$selfTest = $rsa->verify($txt, $serverSign) ? 'verified' : 'unverified';
echo 'js signature: '. $jsTest;
echo'<br> phpseclib signature (same keys): '. $selfTest;
在 PHP 代码中,摘要必须用 sha256
指定,即用小写字母代替大写字母:
$rsa->setMGFHash("sha256");
$rsa->setHash("sha256");
phpseclib
不知道大写名称,因此使用默认摘要 sha1
而不是 [1]。这可以通过输出与 $rsa->hashName
一起使用的摘要名称来轻松测试。
该错误并不那么容易找到,如果 phpseclib
显示错误消息或警告可能比默默地使用默认值更好。
附带说明:在 JavaScript 代码中,私钥以 PKCS8 格式导出,但使用 PKCS1 格式的页眉和页脚,。但是,phpseclib
容忍这种不一致。
所以是的,我的老板想在他的系统中加密,他希望消息在 js 中签名并在 php 中验证。目前,我正在使用 mozilla 的 subtlecrypto api 生成 RSA-PSS 密钥并签名,并使用 phpseclib 进行验证。事实是,事实并非如此。
使用js密钥,phpseclib可以很好地进行签名验证,但是无法处理js签名。
这是我的代码。 JS:
function keys(){
var cryptoObj = window.crypto || window.msCrypto;
let msg = '///';
if(!cryptoObj)
{
alert("Crypto API is not supported by the Browser");
return;
}
window.crypto.subtle.generateKey({
name: "RSA-PSS",
modulusLength: 2048, //can be 1024, 2048, or 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
)
.then(function(key) {
publicKey = key.publicKey;
privateKey = key.privateKey;
// For Demo Purpos Only Exported in JWK format
if (document.getElementById('public').value == "") {
window.crypto.subtle.exportKey("spki", key.publicKey).then(
function (keydata) {
publicKeyhold = keydata;
let exported = publicKeyhold;
const exportedAsString = ab2str(exported);
const exportedAsBase64 = window.btoa(exportedAsString);
const pemExported = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
document.getElementById('public').value = pemExported;
}
);
}
if (document.getElementById('private').value == "" ) {
msg = document.getElementById('msg').value;
window.crypto.subtle.exportKey("pkcs8", key.privateKey).then(
function (keydata) {
privateKeyhold = keydata;
const priv = privateKeyhold;
const privExportedAsString = ab2str(priv);
const privExportedAsBase64 = window.btoa(privExportedAsString);
const privPemExported = `-----BEGIN RSA PRIVATE KEY-----\n${privExportedAsBase64}\n-----END RSA PRIVATE KEY-----`;
document.getElementById('privJSON').data = key.privateKey;
document.getElementById('private').value = privPemExported;
}
);
}})
window.crypto.subtle.sign({
name: "RSA-PSS",
saltLength: 128, //the length of the salt
},
//from generateKey or importKey above
document.getElementById('privJSON').data,
getMessageEncoding())
//ArrayBuffer of data you want to sign
.then(function(signature) {
//returns an ArrayBuffer containing the signature
console.dir(ab2str(signature));
document.getElementById("cryptmsg").value = window.btoa(ab2str(signature)) ;
})
}
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
function asciiToUint8Array(str) {
var chars = [];
for (var i = 0; i < str.length; ++i)
chars.push(str.charCodeAt(i));
return new Uint8Array(chars);
}
function bytesToHexString(bytes) {
if (!bytes)
return null;
bytes = new Uint8Array(bytes);
var hexBytes = [];
for (var i = 0; i < bytes.length; ++i) {
var byteString = bytes[i].toString(16);
if (byteString.length < 2)
byteString = "0" + byteString;
hexBytes.push(byteString);
}
return hexBytes.join("");
}
function getMessageEncoding() {
const messageBox = document.getElementById('msg');
let message = messageBox.value;
let enc = new TextEncoder();
return enc.encode(message);
}
php:
<?php
include('Crypt/RSA.php');
define('CRYPT_RSA_PKCS15_COMPAT', true);
$pubK = $_POST['public'];
$privK = $_POST['private'];
echo $pubK."<br><br>";
echo $privK."<br><br>";
$sign = ($_POST['cryptmsg']);
$txt = $_POST['msg'];
$rsa = new Crypt_RSA();
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PSS);
$rsa->setSaltLength(128);
$rsa->setMGFHash("SHA256");
$rsa->setHash("SHA256");
$rsa->loadKey($privK);
$serverSign = $rsa->sign($txt);
$rsa->loadKey($pubK);
echo "text: ".$txt;
echo "<br><br>js sign: <br>".($sign)."<br><br>";
echo "seclib sign: <br>".base64_encode($serverSign);
echo "<br><br>";
$jsTest = $rsa->verify($txt, base64_decode($sign)) ? 'verified' : 'unverified';
$selfTest = $rsa->verify($txt, $serverSign) ? 'verified' : 'unverified';
echo 'js signature: '. $jsTest;
echo'<br> phpseclib signature (same keys): '. $selfTest;
在 PHP 代码中,摘要必须用 sha256
指定,即用小写字母代替大写字母:
$rsa->setMGFHash("sha256");
$rsa->setHash("sha256");
phpseclib
不知道大写名称,因此使用默认摘要 sha1
而不是 [1]。这可以通过输出与 $rsa->hashName
一起使用的摘要名称来轻松测试。
该错误并不那么容易找到,如果 phpseclib
显示错误消息或警告可能比默默地使用默认值更好。
附带说明:在 JavaScript 代码中,私钥以 PKCS8 格式导出,但使用 PKCS1 格式的页眉和页脚,phpseclib
容忍这种不一致。