电子签名的定义比哈希复杂得多。虽然您可以仅从消息中生成哈希,但对于数字签名,您通常需要一个私钥,并强制只有知道私钥的人才能生成有效签名。接下来,您显然需要相应的公钥来验证消息。
因此,通常有以下3个步骤:
1. 您需要创建公钥/私钥对。
2. 您需要在可信系统上使用私钥对消息进行签名。只有该系统应具有私钥。
3. 使用公钥验证消息,以确保可信系统已对其进行签名。
因此,第一个有趣的问题是,您想如何存储/分发密钥,并且要在哪种类型的系统上进行签名/验证?签名和验证使用不同的编程语言是一种常见用例。但是现在让我们假设您想在JavaScript中完成所有操作。
另外,请始终记住一个简单的问题:
如果您无法确定消息来自有效发送者,那么您如何确保用于验证消息的公钥来自有效发送者?您可以将其与软件一起分发,但对于网站,您必须信任TLS连接。如果您信任TLS连接,则也可以使用它来传输消息本身。
我认为最好的解决方案是使用
Web Cryptography API。
这里您可以找到有用的示例。
首先,您需要生成密钥:
async function generateKey() {
const key = await window.crypto.subtle.generateKey({
name: "RSASSA-PKCS1-v1_5",
modulusLength: 4096,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {
name: "SHA-512"
},
},
true,
["sign", "verify"]
);
return {
privateKey: await window.crypto.subtle.exportKey(
"jwk",
key.privateKey,
),
publicKey: await window.crypto.subtle.exportKey(
"jwk",
key.publicKey,
),
};
}
window.crypto.subtle.exportKey
可以给你一个JSON,你可以通过JSON.stringify
/JSON.parse
来将其转换为简单字符串并进行存储。
下一步是对您的消息进行签名。请注意,您可能希望在不同的时间生成密钥。
async function sign(privateKeyJwk, message) {
const privateKey = await window.crypto.subtle.importKey("jwk", privateKeyJwk, {
name: "RSASSA-PKCS1-v1_5",
hash: {name: "SHA-512"},
}, false, ['sign']);
const data = new TextEncoder().encode(message);
const signature = await window.crypto.subtle.sign({
name: "RSASSA-PKCS1-v1_5",
},
privateKey,
data,
);
return new Uint8Array(signature).join(':');
}
这个函数需要我们在第一步创建的私钥的JWK和一个简单字符串作为要签名的消息。它会返回一个用冒号分隔的字符串表示的签名。目前这个方式可以工作,即使使用Base64可能更有效率。
现在最后一步是验证你的消息。你可能想在另一台机器上进行此操作,在那里你只有公钥和可能已经损坏的消息,并且你想验证消息是否已被损坏。你绝对需要确信私钥没有被损坏。
async function verify(publicKeyJwk, signatureStr, message) {
const signatureArr = signatureStr.split(':').map(x => +x);
const signature = new Uint8Array(signatureArr).buffer
const publicKey = await window.crypto.subtle.importKey("jwk", publicKeyJwk, {
name: "RSASSA-PKCS1-v1_5",
hash: {name: "SHA-512"},
}, false, ['verify']);
const data = new TextEncoder().encode(message);
const ok = await window.crypto.subtle.verify({
name: "RSASSA-PKCS1-v1_5",
},
publicKey,
signature,
data
);
return ok;
}
为此,您需要第一步创建的公钥的jwk、第二步使用的消息作为字符串以及第二步创建的签名作为冒号分隔的字符串。
这将导致一个布尔值,指示消息是否有效。