在数字安全领域,数字签名是一种验证数据完整性和身份验证的重要技术。本文将介绍如何使用WebCrypto API在Web环境中进行数字签名和验证。
数字签名是一种使用私钥对数据进行加密的技术,任何人都可以使用对应的公钥对签名进行解密。如果解密后的数据与原始数据一致,则签名有效,从而证明了数据的完整性和签名者的身份。
WebCrypto API是现代浏览器提供的一个原生JavaScript API,用于执行基本的加密操作,如生成密钥、签名和验证签名等。它支持多种加密算法,包括RSA、ECDSA等。
通常,私钥和证书存储在PFX(Personal Information Exchange)文件中。要使用WebCrypto API进行签名,首先需要加载PFX文件并提取私钥和证书。以下是加载PFX文件的HTML表单代码:
<form name="form1" id="form1" method="post" action="">
<label for="firstname">First name:</label>
<input type="text" name="firstname" id="firstname" required>
<br>
<label for="lastname">Last name:</label>
<input type="text" name="lastname" id="lastname" required>
<br>
</form>
使用jQuery可以轻松获取表单内容:
$('form1').serialize();
用户需要选择PFX文件并输入私钥密码:
<label for="pfx">Select PFX/P12 file:</label>
<br>
<input name="pfx" type="file" id="pfx" accept=".pfx,.p12" required /><br>
<label for="pfxp">Enter Private Key password:</label>
<br>
<input name="pfxp" type="password" id="pfxp" /><br>
使用forge.js库可以读取PFX文件并将其转换为结构化数据。以下是读取PFX文件的JavaScript代码:
var fileInput = document.getElementById('pfx');
var file = fileInput.files[0];
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
var pkcs12Der = arrayBufferToString(contents);
var pkcs12B64 = forge.util.encode64(pkcs12Der);
var privateKey;
var pkcs12Asn1 = forge.asn1.fromDer(pkcs12Der);
var password = $('#pfxp').val();
var pkcs12 = forge.pkcs12.pkcs12FromAsn1(pkcs12Asn1, false, password);
for (var sci = 0; sci < pkcs12.safeContents.length; ++sci) {
var safeContents = pkcs12.safeContents[sci];
for (var sbi = 0; sbi < safeContents.safeBags.length; ++sbi) {
var safeBag = safeContents.safeBags[sbi];
if (safeBag.type === forge.pki.oids.keyBag) {
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) {
privateKey = safeBag.key;
} else if (safeBag.type === forge.pki.oids.certBag) {
cert = safeBag.cert;
}
}
}
}
reader.readAsArrayBuffer(file);
这段代码将读取私钥和证书,并存储在变量privateKey
和cert
中。
将私钥导入到WebCrypto PKCS#8格式:
function importCryptoKeyPkcs8(privateKey, extractable) {
var privateKeyInfoDerBuff = privateKeyToPkcs8(privateKey);
return crypto.subtle.importKey(
'pkcs8',
privateKeyInfoDerBuff,
{ name: 'RSASSA-PKCS1-v1_5', hash: {name: 'SHA-256'} },
extractable,
['sign']
);
}
导入成功后,可以使用私钥进行签名:
importCryptoKeyPkcs8(privateKey, true).then(function(cryptoKey) {
var digestToSignBuf = stringToArrayBuffer(ser);
var pem = forge.pki.certificateToPem(cert);
$('#pfxc').val(forge.util.encode64(pem));
crypto.subtle.sign(
{name: 'RSASSA-PKCS1-v1_5'},
cryptoKey,
digestToSignBuf
).then(function(signature){
sign = arrayBufferToString(signature);
signatureB64 = forge.util.encode64(sign);
});
});
这段代码将签名结果存储为Base64编码的字符串。
验证签名需要数据、签名和证书。以下是验证签名的JavaScript代码:
function publicKeyToPkcs8(pk) {
var subjectPublicKeyInfo = forge.pki.publicKeyToAsn1(pk);
var der = forge.asn1.toDer(subjectPublicKeyInfo).getBytes();
return stringToArrayBuffer(der);
}
function Verify() {
var pem = ...;
var signature64 = ...;
var signature = forge.util.decode64(signature64);
var data = ...;
var cert = forge.pki.certificateFromPem(pem);
window.crypto.subtle.importKey(
"spki",
publicKeyToPkcs8(cert.publicKey),
{
name: "RSASSA-PKCS1-v1_5",
hash: {name: "SHA-256"}
},
false,
["verify"]
).then(function(k) {
window.crypto.subtle.verify(
{
name: "RSASSA-PKCS1-v1_5"
},
k,
stringToArrayBuffer(signature),
stringToArrayBuffer(data)
).then(function(isvalid) {
if (!isvalid) {
// Invalid signature
} else {
// Valid signature
}
}).catch(function(err) {
// Invalid signature or something not worked
});
});
}
这段代码将验证签名是否有效。