公钥(Public Key)与私钥(Private Key)是通过一种算法得到的一个密钥对(即一个公钥和一个私钥),公钥是密钥对中公开的部分,私钥则是非公开的部分。公钥通常用于加密会话密钥、验证数字签名,或加密可以用相应的私钥解密的数据。
公钥和私钥是成对出现的,我们会保留有自己的私钥,同时公开自己的公钥。一个很典型的例子是GitHub的使用。我们通常不会使用账号密码来管理自己的项目,而是通过将自己的公钥上传到GitHub的里,而自己的电脑里则保留有相对应的私钥,从而达到免密码提交代码。
当然私钥和公钥对是唯一的,而你也可以随时重新生成自己的公钥和私钥密码对,但当你从新生成密钥对并覆盖了就有的密钥时,你之前的公钥就作废了。
简单来说就是:
公钥加密,私钥解密,
私钥签名,公钥验证。
SHA256是一种安全哈希算法。
简单介绍一下就是,对于任何一段信息,通过SHA256变换之后,都会是一个固定的 256 位由 0 和 1 组成的输出。同时,对于相同的信息,输出一样,而对于不一样的信息,输出则有很大的区别,即使只有一丁点的输入不一样,输出也会有非常大的不一样。
同时,虽然信息输入是无限的,而 SHA256 的输出是有限的(256位),但是根据我们目前的条件和计算能力水平来说,几乎找不到有两个不同的输入,可以得到相同的输出。也即我们可以认为是不可能的。
接下来我们,我们使用 Js 的 ursa 库来生成公钥和私钥
generatePrivateAndPubKey.js
const ursa = require("ursa");
const fs = require("fs");
const path = require("path");
const MODULUSBIT = 1024;
class Key {
static generateKeys(pathName) {
const key = ursa.generatePrivateKey(MODULUSBIT, 65537);
const privatePem = key.toPrivatePem("utf8");
const privateKey = ursa.createPrivateKey(privatePem); // generate private key
const privateFileName = path.join(pathName, "private.pem");
const publicPem = key.toPublicPem("utf8");
const publicKey = ursa.createPublicKey(publicPem); // generate public key
const publicFileName = path.join(pathName, "public.pub");
if (!fs.existsSync(pathName)) {
fs.mkdirSync(pathName);
}
fs.writeFileSync(privateFileName, privatePem, "utf8");
fs.writeFileSync(publicFileName, publicPem, "utf8");
return {
privateKey: privateKey,
publicKey: publicKey
};
}
static signMessage(privateKey, message) {}
}
module.exports = exports = Key;
test.js
const Key = require("./generatePrivateAndPubKey.js");
const SHA256 = require("crypto-js/sha256");
const ursa = require("ursa");
const fs = require("fs");
const encoding = "base64";
const algorigthm = "sha256";
const message = "Hello world";
let buffer = new Buffer(message, encoding);
// use Bob's private key to signed message
const BobKeys = Key.generateKeys("Bob");
const signedMessage = BobKeys.privateKey.sign(
algorigthm,
buffer,
encoding,
encoding
);
// use Bob's public to verify the message
const isValid = BobKeys.publicKey.verify(
algorigthm,
buffer,
signedMessage,
encoding
);
// use Bob's public key to encrypt message
const encrypted = BobKeys.publicKey.encrypt(message, encoding, "base64");
// use Bob's private key to dencrypt message
const decrypted = BobKeys.privateKey.decrypt(encrypted, "base64", encoding);
console.log("Original Message:", message, "\n");
console.log("Singed Message:", signedMessage, "\n");
console.log("isValid", isValid, "\n");
console.log("Encrypted message:", encrypted, "\n");
console.log("Decrypted message:", decrypted, "\n");
输出
Original Message: Hello world
Singed Message: ARSvIgmY7iIznhuk70stOIj8bBzj4A58wlHKKUPerU1WRob+Mk3U7p5hEhX4+lyLi/F6m3O4dfJ4dESQ2aO+deocjQd4F4zv2s2RC7AhST0nIT0F4iJYAct73R1Azum8MQK+7tesKLUYPLTfjQcvaNDhG+yDl9KfMrGO/Jgtoms=
isValid true
Encrypted message: 5ItG99B3H0h4lbpDDLwD8rza1nPWleWlvOok475lFLxYaI5AvdDetGZq+Cw5K/kcOb6qnpYcoNij1OCgiuSAad352vc596jEFExRZlhsTCqXTtqXblfEPOn4PUtrzrS/jn2FEUke3icF4fqtvVKFYeYpTl1rfrANhe3NrY9F5ik=
Decrypted message: HelloworlQ==
接下来,让我们来测试一下 SHA256
let sha256Message = SHA256("1111").toString();
console.log("1111 After SHA256:", sha256Message, "\n");
sha256Message = SHA256("1112").toString();
console.log("1112 After SHA256:", sha256Message, "\n");
sha256Message = SHA256("111111111111111111").toString();
console.log("111111111111111111 After SHA256:", sha256Message, "\n");
sha256Message = SHA256("111211111111111111").toString();
console.log("111211111111111111 After SHA256:", sha256Message, "\n");
/*
Output:
1111 After SHA256: 0ffe1abd1a08215353c233d6e009613e95eec4253832a761af28ff37ac5a150c
1112 After SHA256: fe91a760983d401d9b679fb092b689488d1f46d92f3af5e9e93363326f3e8aa4
111111111111111111 After SHA256: 26980a9bf7dba794bd14eae90dbda34109d37f66950c16cce7b87dffd4535b40
111211111111111111 After SHA256: 308c3ae83c039dee107a232c4ccb4ee4a65ef3fb1e41357a6b9b240e88b09c0e
*/
比特币里用的编码方式是 Base58, 但是因为我们使用的测试库 ursa并不支持 Base58, 因此我们只能使用Base64。Base64 和 Base64 并没有什么太大的区别,在这里并不会影响我们对加解密,数字签名的理解。
为什么比特币要使用Base58呢?这里引用一下比特币源码里的注释。
//
// Why base-58 instead of standard base-64 encoding?
// - Don’t want 0OIl characters that look the same in some fonts and
// could be used to create visually identical looking account numbers.
// - A string with non-alphanumeric characters is not as easily accepted as an account number.
// - E-mail usually won’t line-break if there’s no punctuation to break at.
// - Doubleclicking selects the whole number as one word if it’s all alphanumeric.
//
简单来说就是去掉 O, I, L 这些不好区分的字符。