数据传输安全
161
3.
发送方使用接收方的公钥加密消息,然后使用自己的私钥签署加密的消息。
4.
把加密且签名后的数据传输给接收方,有时可能经由中间设备或服务。
5.
接收方使用发送方的公钥确认签名的消息,(如果有效)然后使用自己的
私钥加密消息。
消息经确认和解码后,便可按照需要处理。
具体示例
下面以
Node
为例说明具体如何实现这个过程。整个过程将分为三步:
1a/1b
生成两对公私钥。
1a
使用代码生成,不存储密钥
1b
更进一步,
把公私钥存储在文件中。
1a
是为了验证可行性,在生产环境中首选
1b
供的方式。跟着我们做时,你可以选择其中一种方式。
2
步:在发送方加密和签署数据,发给接收方。
3
步:解密并确认发送方发来的数据。
应该何时生成和使用密钥
我们展示的过程虽然可以一次性完成,但是在生产环境中可能不会这样划分
步骤。实际上,发送方设备通常要在接收方的服务中注册。公私钥应该在注
册时生成(第
1a
步或第
1b
步)。直到用户开始使用服务传输数据时(第
2
步和第
3
步),才会提取并使用这些密钥。
先来看如何生成密钥。
1a
步:生成密钥,但不存储在文件中
不使用文件系统存储异步密钥的完整代码在
https://github.com/iddatasecuritybook/
chapter7/blob/master/asymmetric-crypto/crypto_no_fs.js
162
7
为了生成所需的公私钥,首先要安装一个
Node
包,帮助我们生成、加密、解
密、签署和验证密钥。
有一个流行的包恰好能为我们做这些工作,这个包是 ursa,执行下述命令安
装:
npm install ursa --save
接下来,在
Node
脚本的顶部引入这个包:
var ursa = require('ursa');
安装好需要的包之后,该生成所需的公钥和私钥了。前面说过,这里我们将
把一对密钥赋值给变量,而不存储在文件中。
//
为发送方生成公钥和私钥
var senderKey = ursa.generatePrivateKey(1024, 65537);
var senderPrivKey = ursa.createPrivateKey(senderKey.toPrivatePem());
var senderPubKey = ursa.createPublicKey(senderKey.toPublicPem());
//
为接收方生成公钥和私钥
var recipientKey = ursa.generatePrivateKey(1024, 65537);
var recipientPrivKey = ursa.createPrivateKey(recipientKey.toPrivatePem());
var recipientPubKey = ursa.createPublicKey(recipientKey.toPublicPem());
这段代码执行相同的三行代码,分别为发送方和接收方生成密钥。
为了生成密钥,首先应该选用 ursa.generatePrivateKey(
...
) 法,它生成的
随机密钥集可用于生成公钥和私钥。
generatePrivateKey() 法的参数如下:
模数的位数(上例用的是
1024
)。
1024
或以上的数一般是安全的,如果
不指定,这个方法默认使用
2048
指数值,必须是奇数。这个选项也是可选的,默认为
65537
然后分别调用 ursa createPrivateKey(
...
) createPublicKey(
...
) 法生
成私钥和公钥。这两个方法都接受前一行生成的密钥集为参数。
数据传输安全
163
那个参数通过 senderkey.toPrivatePem() senderkey.toPublicPem() 传入,
分别用于生成私钥和公钥。
下面再次生成公私钥,不过这一次将把密钥存储到文件中。
1b
步:生成密钥,存储到文件中
使用文件系统存储异步密钥的完整代码在
https://github.com/iddatasecuritybook/
chapter7/blob/master/asymmetric-crypto/crypto_fs.js
对生产环境来说,一个密钥库中可能存储着上千对密钥,或者会把发送方的
密钥通过应用部署到用户设备上。
与前一个示例一样,我们将使用 ursa 包生成公私钥,除此之外还要使用几个
包:fs 包,生成文件,把数据存储在文件系统中;path 包,规范化文件夹和
文件路径;mkdirp 包,生成文件夹结构,以防出错。
fs path 标准库中,无需安装。不过另外两个包要从
npm
中安装:
npm install ursa --save
npm install mkdirp --save
我们已经做过好多次了,下面要把这些包添加到
Node
脚本的顶部:
var fs = require('fs');
var ursa = require('ursa');
var path = require('path');
var mkdirp = require('mkdirp');
我们将稍微简化一下生成密钥的过程。这一次,我们不为两对密钥编写重复
的代码,而是定义一个函数,然后调用它生成公钥和私钥:
function makeKeys(rootPath, subPath){
try {
mkdirp.sync(path.join(rootPath, subPath));
} catch (err) {
console.error(err);
}
164
7
var key = ursa.generatePrivateKey(1024, 65537);
var privatePem = key.toPrivatePem();
var publicPem = key.toPublicPem();
try {
fs.writeFileSync(path.join(rootPath, subPath, 'private.pem'),
privatePem, 'ascii');
fs.writeFileSync(path.join(rootPath, subPath, 'public.pem'),
publicPem, 'ascii');
} catch (err) {
console.error(err);
}
}
访问文件系统
访问文件系统有很多意外情况。因此,一定要按照最佳实践,以合适的方式
捕获并处理可能出现的错误。下述代码没有考虑这么多意外情况。
makekeys 函数接受两个参数:一个是根路径,公私钥都放在这里;另一个是
子路径,把双方的公钥和私钥分开,放在单独的文件夹中。最终得到的文件
夹结构类似下面这样:
./keys/sender
(存放发送方的公钥和私钥
.
pem
文件)。
./keys/receiver
(存放接收方的公钥和私钥
.pem
文件)。
上述代码先使用 mkdirp.sync() 创建所需的文件夹,然后使用
path
包规范化
根路径和子路径。
mkdirp
的优点
mkdirp 不会重复创建文件夹结构,而是接着往下执行。这样就能防止出现重
复的文件夹结构。接下来的三行使用 ursa 包生成公私钥,然后分别把公钥
和私钥赋值给一个变量。
最后,使用 fs.writeFileSync(
...
) 方法创建
.pem
文件,保存公钥和私钥。上
述示例中 writeFile(
...
) 法接受三个参数:
要写入文件的路径和名称。这里传入的是根路径和子路径,文件名是
private.pem
public.pem
数据传输安全
165
要写入的内容,从保存公钥和私钥的变量中获取。
内容类型,这里用的是
ASCII
定义好这个函数之后,可以通过下述三行代码为发送方和接收方创建公私钥:
var rootPath = './keys';
makeKeys(rootPath, 'sender');
makeKeys(rootPath, 'receiver');
现在,目录结构有了,四个
.pem
文件也创建好了。实际部署时,可能会把这
.pem
文件存储在安全的位置,或者把公钥单独存储在公开的密钥库中,以
加密和确认。
存储密钥的文件类型
公私钥的文件扩展名有好几个标准,包括(但不限于)
.pem
(可用于公钥,
或一对公私钥)
.key
(只用于私钥)
.pub
(只用于公钥)
.cert
(也是
.pem
文件,
Windows
资源管理器能识别这种文件扩展名)等。请根据需要选择最
适合的扩展名。如果想进一步了解这一话题,请访问
Server Fault exchange
http://serverfault.com/questions/9708/what-is-a-pem-file-and-how-does-it-
differ-from-other-openssl-generated-key-file/9717#9717
)。
生成密钥之后,可以参照第
1a
步的方法往下做,不过这一次我们将从刚才创
建的
.pem
文件中提取密钥的内容:
var rootPath = './keys';
//
为发送方生成公钥和私钥
var senderPubKey = ursa.createPrivateKey(
fs.readFileSync(path.join(rootPath, 'sender', 'private.pem')));
var senderpubkey = ursa.createPublicKey(
fs.readFileSync(path.join(rootPath, 'sender', 'public.pem')));
//
为接收方生成公钥和私钥
var recipientPrivKey = ursa.createPrivateKey(
fs.readFileSync(path.join(rootPath, 'receiver', 'private.pem')));
var recipientPubKey = ursa.createPublicKey(
fs.readFileSync(path.join(rootPath, 'receiver', 'public.pem')));
我们从指定的根路径(与生成密钥时相同)中获取密钥,通过 ursa
创建公钥和私钥。因为ursa 需要密钥文件中的内容,所以我们调用

Get Web开发的身份和数据安全 now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.