• Posts tagged "rsa"

Blog Archives

用R语言实现RSA+AES混合加密

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹,分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-crypto-aes-rsa/

前言

我们在日常的数据通信过程中,互联网各种应用软件,虽然让我们越来越方便,但也存在各种隐私泄露,数据被盗取的情况。基于我们对RSA、AES和证书的知识,结合R语言工具,自治一个数据通讯系统吧,保证自己在和别人传输数据过程中的数据隐私。

由于最近对密码学产生兴趣,让用R语言做一个密码学的访问,因此对R语言中openssl包进行了研究,本文为openssl的第四篇文章,用R语言实现RSA+AES混合加密。openssl的文章分别是,R语言进行非对称加密RSAR语言进行AES对称加密R语言配合openssl生成管理应用x509证书用R语言实现RSA+AES混合加密

加密算法涉及到密码学的很多深入的知识,我并没有深入研究,本文只针对于openssl的使用,如果有任何与专业教材不符合的描述,请以专业教材为准。

目录

  1. 用openssl生成秘钥key
  2. RSA+AES混合加密

1. 用openssl生成秘钥key

用openssl包,有6种秘钥的生成方案,分别是aes秘钥,rsa秘钥,dsa秘钥,ec秘钥,x25519秘钥,ed25519秘钥。

  • aes_keygen(length = 16),AES对称加密,只有一个秘钥。
  • rsa_keygen(bits = 2048),RSA非对称加密,包括公钥和私钥。
  • dsa_keygen(bits = 1024),DSA非对称加密,包括公钥和私钥。
  • ec_keygen(curve = c(“P-256”, “P-384”, “P-521”)),椭圆曲线非对称加密,包括公钥和私钥。
  • x25519_keygen(),非对称加密,包括公钥和私钥。
  • ed25519_keygen(),非对称加密,包括公钥和私钥。

生成AES的秘钥


# 生成秘钥
> aes<-aes_keygen(length = 16);aes
aes e0:ad:28:fa:05:04:c0:6b:7c:ba:ad:f9:00:1d:44:22 

# 查看结构
> str(aes)
 'aes' raw [1:16] e0 ad 28 fa ...

生成RSA公钥和私钥

code>
# 生成秘钥
> rsa<-rsa_keygen(bits = 2048);rsa
[2048-bit rsa private key]
md5: c4db98bb1d161082cc41b56b5863cce9
sha256: c4d58b626f4e7ebc20fe12830bd7e1b647ef12151672008eaf9bf709bdbeee50

# 查看结构
> str(rsa)
List of 4
 $ type  : chr "rsa"
 $ size  : int 2048
 $ pubkey:List of 5
  ..$ type       : chr "rsa"
  ..$ size       : int 2048
  ..$ ssh        : chr "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCt7BCaSxvC4UkPiDcwrZFgliC960t+uo+cGMRivq ..."
  ..$ fingerprint: 'hash' raw [1:32] c4 d5 8b 62 ...
  ..$ data       :List of 2
  .. ..$ e: 'bignum' raw [1:3] 01 00 01
  .. ..$ n: 'bignum' raw [1:257] 00 ad ec 10 ...
 $ data  :List of 8
  ..$ e : 'bignum' raw [1:3] 01 00 01
  ..$ n : 'bignum' raw [1:257] 00 ad ec 10 ...
  ..$ p : 'bignum' raw [1:129] 00 e7 61 0c ...
  ..$ q : 'bignum' raw [1:129] 00 c0 6d d7 ...
  ..$ d : 'bignum' raw [1:256] 2e 20 dc 4e ...
  ..$ dp: 'bignum' raw [1:128] 4b 61 43 ce ...
  ..$ dq: 'bignum' raw [1:129] 00 92 1c 55 ...
  ..$ qi: 'bignum' raw [1:129] 00 af 11 51 ...

# 获得公钥
> rsa$pubkey
[2048-bit rsa public key]
md5: c4db98bb1d161082cc41b56b5863cce9
sha256: c4d58b626f4e7ebc20fe12830bd7e1b647ef12151672008eaf9bf709bdbeee50

其他的非对称加密算法的生成公钥和私钥的代码,与RSA的代码结构是一致的。

2. RSA+AES混合加密

在前面两篇文章中,我们已经分别掌握了AES对称加密技术RSA非对称加密技术,两种技术其实都自己的特征和使用的场景。

  • AES对称加密技术,加密和解密使用同一个密钥,加密速度快,密钥最长只有256个bit,执行速度快,易于硬件实现,但只有一个秘钥,一方泄露就会不安全。
  • RSA非对称加密技术,使用公钥加密和私钥解密,加密速度慢,私钥长度可以设置1024bit,2048bit,4096bit,长度越长,越安全,但是生成密钥越慢,加解密也越耗时。

结合上面两种加密技术的特征,我们可以设计一套RSA+AES混合加密方案,让数据加密传输能够使用到两种技术的优点。

方案优势:

  • 单纯的使用 RSA(非对称加密)方式的话,效率会很低,因为非对称加密解密方式虽然很保险,但是过程复杂,需要时间长;
  • 但是,RSA 优势在于数据传输安全,且对于几个字节的数据,加密和解密时间基本可以忽略,所以用它加密 AES 秘钥(一般16个字节)再合适不过了;
  • 单纯的使用 AES(对称加密)方式的话,死板且不安全。这种方式使用的密钥是一个固定的密钥,客户端和服务端是一样的,一旦密钥被人获取,那么,我们所发的每一条数据都会被都对方破解;
  • 但是,AES有个很大的优点,那就是加密解密效率很高,而我们传输正文数据时,正号需要这种加解密效率高的,所以这种方式适合用于传输量大的数据内容;

我从互联网找了RSA+AES混合加密的一个设计思路,RSA+AES的混合加密时,AES用于给传输的数据加密,然后通过RSA给AES的秘钥加密,所以接收到数据后,就需要先解密得到AES的秘钥,然后通过AES秘钥再去解密得到数据。

我画了一个示意图:

操作步骤:

  • 第1步,B系统:一次性,生成RSA私钥(rsa_key)和公钥(rsa_pubkey)。
  • 第2步,A系统:一次性,生成AES秘钥(aes_key),获得把RSA公钥(rsa_pubkey),用RSA公钥对AES秘钥加密,生成(aes_key_mi)。
  • 第3步,B系统:一次性,获得把加密后的AES秘钥(aes_key_mi),用RSA私钥(rsa_key)对AES私钥解密,得到(aes_key2)。
  • 第4步,A系统:生成获得数据(dat)并进行序列化(dat_serial),用AES秘钥(aes_key)进行加密生成(dat_mi)
  • 第5步,B系统:获得加密后的数据(dat_mi),用AES秘钥(aes_key2)进行解密(x_serial2),再反序列化获得原始数据(dat2)。

接下来,我们用R语言来模拟实现。

第一步,B系统:一次性,生成RSA私钥(rsa_key)和公钥(rsa_pubkey)。


# RSA私钥
> rsa_key <- rsa_keygen();rsa_key
[2048-bit rsa private key]
md5: 2fbb4bada8e942967577155c8d0e3251
sha256: 29e6aaabfd49fe884c505ed12130eb61d32090e8251503b2f69bb06a6c23c3c5

# RSA公钥
> rsa_pubkey <- rsa_key$pubkey; rsa_pubkey
[2048-bit rsa public key]
md5: 2fbb4bada8e942967577155c8d0e3251
sha256: 29e6aaabfd49fe884c505ed12130eb61d32090e8251503b2f69bb06a6c23c3c5

第2步,A系统:一次性,生成AES秘钥(aes_key),获得把RSA公钥(rsa_pubkey),用RSA公钥对AES秘钥加密,生成(aes_key_mi)。

 
# RSA公钥
> rsa_pubkey
[2048-bit rsa public key]
md5: 2fbb4bada8e942967577155c8d0e3251
sha256: 29e6aaabfd49fe884c505ed12130eb61d32090e8251503b2f69bb06a6c23c3c5

# AES秘钥
> aes_key <- aes_keygen();aes_key
aes d5:bf:bd:f0:6a:05:0b:f7:0d:04:4f:68:64:f5:7c:02 

# 把AES秘钥加密
> aes_key_mi <- rsa_encrypt(aes_key, rsa_pubkey)

第3步,B系统:一次性,获得把加密后的AES秘钥(aes_key_mi),用RSA私钥(rsa_key)对AES私钥解密,得到(aes_key2)。

code>
# 获得加密后的AES秘钥
> aes_key_mi

# 用RSA私钥对AES私钥解密
> aes_key2<-rsa_decrypt(aes_key_mi,rsa_key);aes_key2
 [1] d5 bf bd f0 6a 05 0b f7 0d 04 4f 68 64 f5 7c 02

第4步,A系统:生成获得数据(dat)并进行序列化(dat_serial),用AES秘钥(aes_key)进行加密生成(dat_mi)。


> # 原始数据
> dat<-iris[1:3,];dat
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa

# 序列化
> dat_serial <- serialize(dat, NULL)

# 用AES秘钥进行加密生成
> dat_mi <- aes_cbc_encrypt(x_serial, key = aes_key)

第5步,B系统:获得加密后的数据(dat_mi),用AES秘钥(aes_key2)进行解密(x_serial2),再反序列化获得原始数据(dat2)。


# 获得加密数据
> dat_mi

# 解密
> dat_serial2<-aes_cbc_decrypt(dat_mi,aes_key2);x_serial2

# 反序列化
> dat2<-unserialize(x_serial2);dat2
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa

有了本文的思路基础,接下来就可以自己设计一套软件程序了,把重要的信息加密,给对的人,也不怕中间过程被截获了。话说微信传输,已经没有隐私可言了,只能信自己了。

本文是介绍openssl包使用的第四篇文章,原想着R语言做密码学知识到哪天才能用上,没想到马上就用上了。

转载请注明出处:
http://blog.fens.me/r-crypto-aes-rsa/

打赏作者

R语言进行AES对称加密

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹,分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-crypto-aes/

前言

本文是介绍openssl包使用的第二篇文章,主要介绍AES算法的使用,还搭配了digest包做对比。虽然R语言不是专门做密码研究的工具,但实现个算法还是很方便的。作为数据分析师又学到了一些冷门知识,说不定哪天就会有用呢。openssl的文章分别是,R语言进行非对称加密RSAR语言进行AES对称加密R语言配合openssl生成管理应用x509证书用R语言实现RSA+AES混合加密

加密算法涉及到密码学的很多深入的知识,我并没有深入研究,本文只针对于openssl的使用,如果有任何与专业教材不符合的描述,请以专业教材为准。

目录

  1. AES算法介绍
  2. 用digest包进行AES加密解密
  3. 用openssl包进行AES加密解密

1. AES算法介绍

AES高级加密标准(Advanced Encryption Standard)为最常见的对称加密算法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES(Data Encryption Standard)。

AES的区块长度固定为128位,密钥长度则可以是128 bit,192 bit 或256位 bit 。换算成字节长度,就是密码必须是 16个字节,24个字节,32个字节。AES密码的长度更长,破解难度就增大了,所以就更安全。

AES 是对称加密算法,优点:加密速度快;缺点:如果秘钥丢失,就容易解密密文,安全性相对比较差。RSA 是非对称加密算法 , 优点:安全 ;缺点:加密速度慢。RSA算法介绍,请参见文章用openssl生成RSA私钥和公钥

AES算法的使用场景,发送方将要发送的明文数据X使用秘钥K进行AES加密后会得到密文Y,将密文进行网络传输,接受方在收到密文Y后使用秘钥K进行AES解密后技能得到明文X,这样即使密文Y在网络上传输时被截获了,没有秘钥也难以破解其真实意思。

AES的加密模式有以下几种

  • 电码本模式(Electronic codebook,ECB):需要加密的消息按照块密码的块大小被分为数个块,并对每个块进行独立加密。
  • 密码分组链接模式(CBC):将整段明文切成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
  • 计算器模式(CTR):每个分组对应一个逐次累加的计数器,并通过对计数器进行加密来生成密钥流。
  • 密码反馈模式(CFB):前一个密文分组会被送入密码算法的输入端,再将输出的结果与明文做异或。与ECB和CBC模式只能够加密块数据不同,CFB能够将块密文(Block Cipher)转换为流密文。
  • 输出反馈模式(OFB):前一组密码算法输出会输入到下一组密码算法输入。先用块加密器生成密钥流,然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的。

在这五种模式里,只有ECB和CBC模式明文数据要求填充至长度为分组长度(16)的整数倍,因为ECB,CBC的加密运算会影响结果,而OFB,CFB,CTR只是最后一步的异或明文,所以不会影响结果,所以我们需要填充。

2. 用digest包进行AES加密解密

我们可以使用digest包的AES函数,进行AES的加密和解密操作,digest包的详细介绍,请参考文章R语言创建哈希摘要digest

使用AES函数时,需要输入3个参数:key, mode, IV。

  • key, 分别作为AES-128、AES-192或AES-256,对应为16、24或32字节的原始向量的密钥。
  • mode, 要使用的加密模式。目前只支持 “电子密码本”(ECB)、”密码块链”(CBC)、”密码反馈”(CFB)和 “计数器”(CTR)模式。
  • IV,偏移量,在CBC和CFB模式的初始向量或CTR模式的初始计数器

2.1 ECB模式
使用ECB模式进行加密,可以使用同一个AES实例进行加密和解密。要求输入的原始数据为16的整数倍。


> library(digest)

# 把明文的转成二进制数据
> msg<- as.raw(c(1:16,1:32));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

# key转成二进制数据
> key <- as.raw(1:16)#;key

# 建立ECB的AES实例
> aes <- AES(key, mode="ECB")

# 加密
> a<-aes$encrypt(msg)

# 解密
> b<-aes$decrypt(a, raw=TRUE);b
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

2.2 CBC模式
使用CBC模式进行加密时,不能使用同一个AES实例进行加密和解密,需要一个新的AES实例进行解密。


# 把明文的转成二进制数据
> msg<- as.raw(c(1:16,1:32));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

# key转成二进制数据
> key <- as.raw(1:16)#;key

# 偏移量转成二进制数据
> iv <- rand_bytes(16)#;iv

# 建立CBC的AES实例
> aes <- AES(key, mode="CBC",iv)

# 加密
> a<-aes$encrypt(msg)

建立另一个实例,进行解密。


> aes2 <- AES(key, mode="CBC",iv)

# 解密
> b<-aes2$decrypt(a, raw=TRUE);b
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

2.3 CFB模式
使用CFB模式进行加密时,不能使用同一个AES实例进行加密和解密,需要一个新的AES实例进行解密,同时要求IV的偏移量长度,与块的大小一致。


# 原始数据
> msg<- as.raw(c(1:16,1:32));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

# 创建IV偏移量,16字节
> iv <- rand_bytes(16)#;iv

# 创建AES实例
> aes <- AES(key, mode="CFB", iv)

# 查看块大小
> aes$block_size()
[1] 16

# 加密
> code <- aes$encrypt(msg)

# 新建实例
> aes2 <-  AES(key, mode="CFB", iv)

# 解密
> aes2$decrypt(code,raw=TRUE)
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13
[36] 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20

2.4 CTR模式


> msg<- as.raw(c(1:16,1:16));msg
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
> key <- as.raw(1:16)
> iv <- rand_bytes(16)
> aes <- AES(key, mode="CTR", iv)
> code<-aes$encrypt(msg)
> aes2 <-  AES(key, mode="CTR", iv)
> aes2$block_size()
[1] 16
> aes2$decrypt(code,raw=TRUE)
 [1] 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10

2.5 报错:Text length must be a multiple of 16 bytes
在加密过程中,aes$encrypt(msg),msg文本可以是一个单元素的字符向量或一个原始向量,被要求字节长度是16字节长度的倍数。如果原始文本的二进制为非16字节长度,则会有 Text length must be a multiple of 16 bytes 错误。


# 任意原始文字
> msg<- charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4
> key <- as.raw(1:16)
> aes <- AES(key, mode="ECB")

# 非16长度倍数
> a<-aes$encrypt(msg)
Error in aes$encrypt(msg) : Text length must be a multiple of 16 bytes

2.6 PKCS5和PKCS7填充
当出现上面的错误的时候,我们需要用PKCS5和PKCS7进行填充补值。加密算法要求明文需要按一定长度对齐,叫做块大小(BlockSize),比如16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。

  • PKCS5,PKCS7Padding的子集,块大小固定为8字节。。
  • PKCS7,块大小固定为1-256字节的长度进行分组,最后分剩下那一组,不够长度,就需要进行补齐。

由于digest包,没有提供实现,我自己写了3个函数,用于加密时填充补值和解密是移除补值。

原始字符转二进制补齐16的倍数长度
A4141 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f
AB41 4241 42 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e 0e
ABC41 42 4341 42 43 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d 0d
ABCD41 42 43 4441 42 43 44 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c
ABCDE41 42 43 44 4541 42 43 44 45 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b
ABCDEF41 42 43 44 45 4641 42 43 44 45 46 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a
ABCDEFG41 42 43 44 45 46 4741 42 43 44 45 46 47 09 09 09 09 09 09 09 09 09
ABCDEFGH41 42 43 44 45 46 47 4841 42 43 44 45 46 47 48 08 08 08 08 08 08 08 08
ABCDEFGHI41 42 43 44 45 46 47 48 4941 42 43 44 45 46 47 48 49 07 07 07 07 07 07 07
ABCDEFGHIJ41 42 43 44 45 46 47 48 49 4a41 42 43 44 45 46 47 48 49 4a 06 06 06 06 06 06
ABCDEFGHIJK41 42 43 44 45 46 47 48 49 4a 4b41 42 43 44 45 46 47 48 49 4a 4b 05 05 05 05 05
ABCDEFGHIJKL41 42 43 44 45 46 47 48 49 4a 4b 4c41 42 43 44 45 46 47 48 49 4a 4b 4c 04 04 04 04
ABCDEFGHIJKLM41 42 43 44 45 46 47 48 49 4a 4b 4c 4d41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 03 03 03
ABCDEFGHIJKLMN41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 02 02
ABCDEFGHIJKLMNO41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 01
ABCDEFGHIJKLMNOP41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 5041 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
ABCDEFGHIJKLMNOPQ41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 5141 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f

接下来,自己写个函数进行实现


# PKCS5填充
> pkcs5_padding<-function(text){
+   bit<-8
+   if(!is.raw(text)){
+     text<-charToRaw(text)
+   }
+   b<- bit - (length(text)+bit) %% bit
+   c(text,as.raw(rep(as.hexmode(b),b)))
+ }

# PKCS7填充
> pkcs7_padding<-function(text,bit=16){
+   if(bit>256 | bit<1){
+     stop("bit is not in 1-256")
+   }
+   if(!is.raw(text)){
+     text<-charToRaw(text)
+   }
+   b<- bit - (length(text)+bit) %% bit
+   c(text,as.raw(rep(as.hexmode(b),b)))
+ }

# PKCS7移除
> pkcs_strip<-function(rtext){
+   n<-length(rtext)
+   pos<-as.integer(rtext[n])
+   rtext[1:c(n-pos)]
+ }

输出自定的字符串,使用pkcs7_padding()来自动补齐数据。


# 原始明文转二进制
> plaintext<-charToRaw("ABCDj@*(;dj! 测试一下中文");plaintext
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4

# pkcs7_padding填充
> ptext<-pkcs7_padding(plaintext)

# 创建AES对象
> aes <- AES(key, mode="ECB")

# 加密
> a <- aes$encrypt(ptext);a
 [1] 63 e6 d4 e5 74 c1 13 b3 d5 be 1f 0f a6 db 75 50 d7 d8 3d fb 53 b0 4e 61 67 d1 91 a6 db fe b8 e0

# 解密
> b<-aes$decrypt(aes128, raw=TRUE);b
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4 06 06 06 06 06 06

# 移除增加的补齐数据
> pkcs7_strip(b)
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4

# 把二进制转明文
> rawToChar(pkcs7_strip(b))
[1] "ABCDj@*(;dj! 测试一下中文"

3. 用openssl包进行AES加密解密

我们可以使用openssl包的AES函数,进行AES的加密和解密操作,openssl包的详细介绍,请参考文章用openssl生成RSA私钥和公钥

在openssl包中,支持CBC,CTR,GCM三种模式,分别对应aes_cbc_encrypt(), aes_ctr_encrypt(), aes_gcm_encrypt()的三种函数。

在CBC模式下使用AES块状密码进行低级别的对称加密/解密。密钥是一个原始向量,例如一些秘密的散列。当没有共享秘密时,可以使用随机密钥,通过非对称协议(如RSA)进行交换。

从API的使用上,openssl包提供的API函数,要比digest包提供的API函数,看起来更容易一点。

3.1 CBC模式


> library(openssl)

# 设置key 和 iv
> key <- aes_keygen();key
aes e2:40:0a:12:33:9e:83:e9:04:d5:5c:aa:6f:40:d2:cc 
> iv <- rand_bytes(16);iv
 [1] 9a a7 24 f9 1d eb da 25 30 f9 ba b3 1b 69 12 e4

# 原始字符转字节码
> msg<-charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4

# 加密
> blob <-    aes_cbc_encrypt(msg, key, iv = iv)

# 解密
> message <- aes_cbc_decrypt(blob, key, iv)

# 字节码转字符串
> out <- rawToChar(message);out
[1] "ABCDj@*(;dj! 测试一下中文"

3.2 CTR模式


> key <- aes_keygen();key
aes 11:65:5a:17:2d:28:f4:92:38:44:90:b1:45:cb:d2:5f 
> iv <- rand_bytes(16);iv
 [1] 94 e0 f6 b1 6a 64 e6 0a a0 cf c1 13 ea be 65 dd
> msg<-charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4
> blob <-    aes_ctr_encrypt(msg, key, iv = iv)
> message <- aes_ctr_decrypt(blob, key, iv)
> out <- rawToChar(message);out
[1] "ABCDj@*(;dj! 测试一下中文"

3.3 GCM模式


> key <- aes_keygen();key
aes 38:31:8a:ea:66:96:c7:7d:6d:72:d5:bd:75:62:92:db 
> iv <- rand_bytes(12);iv
 [1] 99 ca b1 36 bf b9 af 78 c5 22 a4 06
> msg<-charToRaw("ABCDj@*(;dj! 测试一下中文");msg
 [1] 41 42 43 44 6a 40 2a a3 a8 3b 64 6a 21 20 b2 e2 ca d4 d2 bb cf c2 d6 d0 ce c4
> blob <-    aes_gcm_encrypt(msg, key, iv = iv)
> message <- aes_gcm_decrypt(blob, key, iv)
> out <- rawToChar(message);out
[1] "ABCDj@*(;dj! 测试一下中文"

本文介绍对称加密算法AES在digest包和openssl包的使用,虽然加密和解密不是R语言的主业,但是真的实现起来,没想到还是挺方便的。哈哈哈,又学到一招。

本文代码已上传到github: https://github.com/bsspirit/encrypt/blob/master/aes.r

转载请注明出处:
http://blog.fens.me/r-crypto-aes/

打赏作者

R语言进行非对称加密RSA

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹,分析师/程序员/Quant: R,Java,Nodejs
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-crypto-openssl-rsa/

前言

openssl是加密法的核心包,在各种编程语言中都有支持,基于openssl包就可以建立起密码学的世界。在R语言中,也有各种的场景都涉及到了加密计算,数据加密解密,数字签名,数据证书等内容。让我们开始密码学从学会用openssl包开始吧。

由于最近对密码学产生兴趣,让用R语言做一个密码学的访问,因此对R语言中openssl包进行了研究,本文为openssl的第三篇文章,R语言配合openssl生成管理应用x509证书。openssl的文章分别是,R语言进行非对称加密RSAR语言进行AES对称加密R语言配合openssl生成管理应用x509证书用R语言实现RSA+AES混合加密

加密算法涉及到密码学的很多深入的知识,我并没有深入研究,本文只针对于openssl的使用,如果有任何与专业教材不符合的描述,请以专业教材为准。

目录

  1. R语言openssl包介绍
  2. 生成RSA私钥和公钥
  3. 加密通信
  4. 数字签名

1. openssl包介绍

R语言中openssl包,一个专业的用于加密和解密的包,基于底层的libssl和libcrypto库,可以自定义的SSH公钥解析器,支持RSA、DSA和NIST曲线P-256、P-384和P-521。加密签名可以手动创建和验证,也可以通过x509证书创建和验证。AES区块密码在CBC模式下用于对称加密;RSA用于非对称(公共密钥)加密。

安装openssl简单,源代码编译安装需要联网环境和rtools的依赖环境,也可以使用二进制安装,就是一条命令搞定,就不需要额外的依赖了。

# 安装openssl
> install.pacakges(openssl)

# 加载openssl
> library(openssl)

查看openssl的基本配置信息,包括version版本,ec(Elliptic Curve)椭圆曲线算法, x25519蒙哥马利椭圆曲线算法, figs(Federal Information Processing Standards,FIPS)独立的FIPS模块支持。


# 查看openssl的环境信息
> openssl_config()
$version
[1] "OpenSSL 1.1.1k  25 Mar 2021"

$ec
[1] TRUE

$x25519
[1] TRUE

$fips
[1] FALSE

# 是否开启FIPS模式
> fips_mode()
[1] FALSE

2. 生成RSA私钥和公钥

使用RSA做非对称加密时,我们会生成私钥和公钥。公钥和私钥组成一个密钥对,必须配对使用。一般公钥公开,私钥自己保留。

用公钥加密的数据只有对应的私钥可以解密. 用私钥加密的数据只有对应的公钥可以解密. 如果可以用公钥解密,则必然是对应的私钥加的密. 如果可以用私钥解密,则必然是对应的公钥加的密. 公钥和私钥是相对的,两者本身并没有规定哪一个必须是公钥或私钥。.

  • 公钥加密,私钥解密,一般用于加密通信、传输数据。
  • 私钥加密,公钥解密,一般用于数字签名、验证身份。

2.1 生成私钥和公钥

通过openssl包的函数来进行RSA的加密和解密操作。生成私钥,默认为2048位,查看数据结构,key是一个list对象,包括了私钥和公钥。


# 生成私钥,默认为2048位
> key <- rsa_keygen();key
[2048-bit rsa private key]
md5: 9e4b265a81b9f6aa2cff74a5d2d2ec78
sha256: d3ab5853ae80c27e57f58482b28f4a74ff19812e2d344c2affafd67ec1fc47bb

# 查看数据结构
> str(key)
List of 4
 $ type  : chr "rsa"
 $ size  : int 2048
 $ pubkey:List of 5
  ..$ type       : chr "rsa"
  ..$ size       : int 2048
  ..$ ssh        : chr "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC ..."
  ..$ fingerprint: 'hash' raw [1:32] d3 ab 58 53 ...
  ..$ data       :List of 2
  .. ..$ e: 'bignum' raw [1:3] 01 00 01
  .. ..$ n: 'bignum' raw [1:257] 00 ac 68 65 ...
 $ data  :List of 8
  ..$ e : 'bignum' raw [1:3] 01 00 01
  ..$ n : 'bignum' raw [1:257] 00 ac 68 65 ...
  ..$ p : 'bignum' raw [1:129] 00 d6 0b bb ...
  ..$ q : 'bignum' raw [1:129] 00 ce 33 5f ...
  ..$ d : 'bignum' raw [1:256] 6e f5 18 de ...
  ..$ dp: 'bignum' raw [1:129] 00 a3 61 40 ...
  ..$ dq: 'bignum' raw [1:128] 52 72 40 30 ...
  ..$ qi: 'bignum' raw [1:128] 04 12 e8 c5 ...

取出公钥pubkey

# 提取公钥
> pubkey <- key$pubkey;pubkey
[2048-bit rsa public key]
md5: 0f0ddca360d5e22e2683c9b23a36fccc
sha256: 18ba38c42cc149ab52a4f6cff2d595629f8da839fbb5a92f1443da0282a1c2fe

# 查看公钥数据结构
> str(pubkey)
List of 5
 $ type       : chr "rsa"
 $ size       : int 2048
 $ ssh        : chr "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC ..."
 $ fingerprint: 'hash' raw [1:32] 18 ba 38 c4 ...
 $ data       :List of 2
  ..$ e: 'bignum' raw [1:3] 01 00 01
  ..$ n: 'bignum' raw [1:257] 00 a7 8e 09 ...

3. 加密通信

当我们有了RSA的公钥和私钥后,就可以进行应用了。

加密通信,基于RSA时加密和解密是采用不同的 key,公钥用来加密信息,私钥用来解密信息。双方通信之前,要先互相交换公钥,这样才能建立起安全的双向通道。

3.1 公钥加密,私钥解密
使用公钥和私密,进行加密和解密的测试。


# 原始数据
> value<-"abc=!@#$%^&*()_+abc试试中文"

# 文本转为raw数据
> secretChar <- charToRaw(value);secret
[1] 66 65 6e 73 2e 6d 65

# 用公钥加密,输出密文
> ciphertext <- rsa_encrypt(secretChar, pubkey);ciphertext
  [1] 9c 6a 21 90 e0 58 6a 90 4c 65 f6 fc 20 49 a9 ac 43 11 04 b9 4b
 [22] 2b ad c9 d7 e0 b4 3d 9a 59 90 19 b8 66 6b 4b 0f 37 99 cc 4d a3
 [43] 03 77 ba 06 e5 61 ab 85 b1 58 6d 00 8a e1 d5 5b 54 99 70 a3 52
 [64] e9 18 a8 60 5d 80 9b f0 fb f0 41 f8 3d 77 50 81 98 ff 5a e2 80
 [85] b9 37 63 91 d9 a9 f7 1a 4e f1 3a 14 df 15 0d a7 85 9e ee 2f 73
[106] f2 c0 6a f9 c8 c2 68 61 47 31 ba 30 42 33 1b 8a 90 4d e4 fb c7
[127] c4 0b 1d 8d af 45 8c ea 6b 54 61 96 d8 06 76 b0 6b 4c 1f 35 59
[148] 88 2d a2 35 c0 bf 86 0a 97 19 fb 9f 29 47 31 ca 8f 4f 34 38 c6
[169] 96 4c 0d f1 99 ca c2 1c 86 dd fc 6b 70 39 6a 82 71 c4 79 ee 40
[190] 0d 3b 45 bf 68 11 ab e5 ce 1f 4c c6 59 01 d9 92 dc c3 2e 8f 81
[211] be da 8a 1b c9 4a 32 37 ff 26 c3 7a d5 73 64 b4 f5 d7 a1 64 ca
[232] f5 d7 9d ff 40 e5 1d b6 55 46 fe 03 70 4d 22 7e 5b 0d 0c f8 90
[253] 2a 44 be e6

# 用私钥解密
> rawChar<-rsa_decrypt(ciphertext, key);rawChar
 [1] 61 62 63 3d 21 40 23 24 25 5e 26 2a 28 29 5f 2b 61 62 63 ca d4
[22] ca d4 d6 d0 ce c4

# 把raw数据转为文本
> rawToChar(rawChar)
[1] "abc=!@#$%^&*()_+abc试试中文"

3.2 私钥加密,私钥解密

# 原始数据
> secretChar <- charToRaw(value);secret
[1] 66 65 6e 73 2e 6d 65

# 私钥加密
> ciphertext <- rsa_encrypt(secretChar, key);ciphertext
  [1] 6b b3 7e 3e 81 4f 01 ff a4 e7 e4 36 7e f2 68 29 88 d8 5e a6 53
 [22] 02 31 ff 5c ee 39 56 67 c8 a3 15 d0 3e 76 2f 64 cc 48 77 0b 4a
 [43] f7 0e ab 38 41 52 ee 74 a3 5b b9 d1 27 3a a7 68 f8 54 ed fc ac
 [64] 8f 82 b6 85 30 5b 36 b6 71 74 06 29 a4 b8 a7 72 40 77 7d 3c b1
 [85] be 53 63 d5 56 16 34 3f 60 4d db 53 97 50 e7 07 b4 59 83 9a 73
[106] 7e 45 eb e9 a0 cb 00 c3 f3 cc e8 8d ce 1c a1 3e ca c2 fa 70 e2
[127] ac 03 38 9f 44 b1 d1 c5 60 84 34 13 01 98 6a 2b 1a 4a da c5 23
[148] a5 d3 62 4c 50 0b 69 1f 28 6d 24 8b 04 3a a8 68 e7 28 1c 80 91
[169] aa 3a 57 25 29 4d b8 0d 50 c3 f6 1b 72 b8 1c 2a 78 ad 7d 72 1c
[190] 54 0e 6f 43 a2 ea 06 02 98 5e a6 af af 52 9a bf 8a e3 10 72 8c
[211] f7 bb 94 97 ce b6 09 91 40 50 d2 32 45 7d 13 2f fe 70 55 ef 66
[232] b2 42 cf 38 52 a8 f0 70 76 34 1e f6 bc 5e d8 b5 4e 6c b8 f9 36
[253] ab 69 87 2a

# 私钥解密
> rawChar<-rsa_decrypt(ciphertext, write_der(key));rawChar
 [1] 61 62 63 3d 21 40 23 24 25 5e 26 2a 28 29 5f 2b 61 62 63 ca d4
[22] ca d4 d6 d0 ce c4

# 输出解密结果
> rawToChar(rawChar)
[1] "abc=!@#$%^&*()_+abc试试中文"

3.3 私钥加密,公钥解密
当使用私钥加密,公钥解密的时候,一直报错。


# 原始数据
> secretChar <- charToRaw(value);secret
[1] 66 65 6e 73 2e 6d 65

# 私钥加密
> ciphertext <- rsa_encrypt(secretChar, key);ciphertext
  [1] 70 96 7b 28 a6 26 39 f0 de 4e 95 19 f3 55 b4 50 69 87 16 95 95
 [22] 1d eb 0f 64 58 6f b8 1b 0a 33 83 0b 3f 19 ee 0a 74 da af e1 77
 [43] da db b8 de 46 c5 ff 1d 11 ea 19 73 b8 fe 3d 02 37 11 bd 4f d0
 [64] 7f e3 20 3b 67 39 30 a0 52 31 b9 76 79 02 58 e4 e6 00 a8 61 a6
 [85] 84 41 80 bf 54 16 ba 72 dd 55 50 a7 0b e6 95 c2 60 79 0b 2f 94
[106] c6 0c ed 0c 06 60 62 7c da ad 14 8e 51 e0 1d 7d 42 e0 1a ea bb
[127] 76 ad 09 f9 42 da 9a 32 cf b1 c7 8f 45 93 87 c4 c7 ee 70 17 d2
[148] 1d 37 a2 f2 40 0c 62 b2 f6 e7 7d 96 71 d7 a0 66 30 37 5c 4c 11
[169] f5 4d e2 b3 63 ca 99 9f 1f 4d 87 ca 06 13 97 92 59 7c 46 6b fb
[190] 91 dd 1b d7 a0 56 ca d1 83 b7 91 96 ea e4 78 aa 53 66 84 c1 68
[211] 6f a3 c7 a4 41 ae 48 3c 59 c8 16 ad e3 16 65 1d 3a 5d 35 dd 31
[232] e3 03 bd 14 60 b8 b0 be 9f 32 70 a5 03 35 a0 a1 ae e7 d1 c0 d3
[253] 62 69 79 25

# 公钥解密,报错了。。。
> rawChar<-rsa_decrypt(ciphertext, write_der(pubkey));rawChar
Error: OpenSSL error in asn1_check_tlen: wrong tag

在加密通信的场景中,我们并不能使用私钥加密,然后公钥解密来操作。私钥加密公钥解密,就引出了数字签名。

4. 数字签名

数字签名,是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。

对于数字签名,我们使用私钥通过签名算法对文件进行签名,生成签名,然后可以用公钥、签名和文件,通过解密算法,对签名进行验证,判断签名是不是发送者的。私钥是签名key,公钥是验证的key。签名并不是文件本身进行加密,只是加密了签名的hash值。

数字签名必须保证以下三点:

  • 身份确认性,接收者能够核实发送者对报文的签名。
  • 数据完整性,接收者不能伪造对报文的签名或更改报文内容,防篡改。
  • 不可否认性,发送者事后不能抵赖对报文的签名。

数字签名的R语言实现,签名可以对文件,明文,二进制,hash值等不同类型,分别进行签名。


# 用私钥签名
> myfile <- system.file("DESCRIPTION")
> sig <- signature_create(myfile, key = key);sig
 [1] 30 44 02 20 27 ca 6d 53 b2 b7 1a 68 c6 63
[15] 7f e8 0b 85 98 37 c4 51 65 e3 46 54 65 70
[29] 85 0e 50 ce a0 82 45 f5 02 20 35 4c 95 61
[43] 2a 3c 81 ca e6 2b 0a 5a 1d b3 4d 68 c2 0b
[57] 8b f3 e3 1d 99 9c 62 11 7e 44 b5 d6 85 c0

# 公钥验证
> signature_verify(myfile, sig, pubkey = pubkey)
[1] TRUE

对序列化后的二进制数据进行签名,关于序列化的介绍,请参考文章R语言中的编码和解码


# 序列化
> data <- serialize(iris, NULL)

# 签名
> sig <- signature_create(data, sha256, key = key);sig
 [1] 30 44 02 20 77 08 4a dc dc e2 d1 29 1a ec 0b 0c 8a 2e 6e 48 68 e3 26 6b 6b e5 49 e3 09 6a cb bf 08 a2 94 6b
[37] 02 20 3b 0b 79 1b fb 0b 43 2a 56 16 a9 55 71 bd 79 6d bd 61 6a 96 00 d7 c9 ae c8 69 b6 bc 82 ec 07 06

# 签名验证
> signature_verify(data, sig, sha256, pubkey = pubkey)
[1] TRUE

对md5的值进行签名


# 生成md5
> md <- md5(data);md
md5 8c:c5:96:11:23:30:47:ed:e5:01:2f:b1:a4:c1:f9:af 

# 签名和验证
> sig <- signature_create(md, hash = NULL, key = key)
> signature_verify(md, sig, hash = NULL, pubkey = pubkey)
[1] TRUE

使用椭圆曲线的秘钥进行签名。


# 序列化
> data <- serialize(iris, NULL)

# 生成椭圆曲线的秘钥
> key <- ec_keygen();key
[256-bit ecdsa private key]
md5: e90d2f8c5e019977f1c80bf6865b2577
sha256: de2a16fb5b5bcc8d7261a8696006f9abbf72bd73c63cd62bcb71f1c4ec21c3de
> pubkey <- key$pubkey;pubkey
[256-bit ecdsa public key]
md5: e90d2f8c5e019977f1c80bf6865b2577
sha256: de2a16fb5b5bcc8d7261a8696006f9abbf72bd73c63cd62bcb71f1c4ec21c3de

# 签名和验证
> sig <- signature_create(data, sha256, key = key)
> signature_verify(data, sig, sha256, pubkey = pubkey)
[1] TRUE

# 提取椭圆曲线的r和s值
> params <- ecdsa_parse(sig);params
$r
[b] 99519986419825124208370725135241493845144616234791239519109053675434998381718
$s
[b] 98142266778762515701344468735986151961806847723506160989847269974259055214221

# 把r和s签名值进行解密
> out <- ecdsa_write(params$r, params$s);out
 [1] 30 46 02 21 00 dc 06 50 f1 72 8b 6d 76 36 23 60 93 0c 8a 40 7d 64 da dd e8 a3 9c 0c fc 58 c9 8f e3 57 32 3c
[37] 96 02 21 00 d8 fa 8d f3 c8 2b b7 5a 24 ff f0 42 1b e6 65 2a 55 a2 7d 72 fb 33 ff 4d a5 d6 e4 6c 55 78 3e 8d
> identical(sig, out)
[1] TRUE

本文是我们使用openssl包的一个初探,以场景的角度分别介绍是获取网站证书并验证和RSA生成私钥和公钥,这两个场景都是互联网应用的基础设施。只有我们理解了互联网的底层技术,才能让我们的应用程序更安全。

后面会继续围绕openssl的其他功能继续进行介绍。

本文代码已上传到github: https://github.com/bsspirit/encrypt/blob/master/rsa.r

转载请注明出处:
http://blog.fens.me/r-crypto-openssl-rsa/

打赏作者

Node.js加密算法库Crypto

从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发。Nodejs框架是基于V8的引擎,是目前速度最快的Javascript引擎。chrome浏览器就基于V8,同时打开20-30个网页都很流畅。Nodejs标准的web开发框架Express,可以帮助我们迅速建立web站点,比起PHP的开发效率更高,而且学习曲线更低。非常适合小型网站,个性化网站,我们自己的Geek网站!!

关于作者

  • 张丹(Conan), 创业者,程序员(Java,R,Javascript)
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/nodejs-crypto/

crypto

前言

密码技术是互联网应用的一项最基本的技术之一,主要保证了数据的安全。安全定义是多维度的,通过不可逆的hash算法可以保证登陆密码的安全;通过非对称的加密算法,可以保证数据存储的安全性;通过数字签名,可以验证数据在传输过程中是否被篡改。

我们要做互联网应用,数据安全性一个是不容忽视的问题。不然可能会造成,如CSDN的100万用户明文密码被泄露事情;携程网,100万用户个人信息泄露事情等。

Node.js的Crypto库就提供各种加密算法,可以非常方便地让我们使用密码技术,解决应用开发中的问题。

目录

  1. Crypto介绍
  2. Hash算法
  3. Hmac算法
  4. 加密和解密算法
  5. 签名和验证算法
  6. salt算法
  7. 程序代码

1. Crypto介绍

Crypto库是随Nodejs内核一起打包发布的,主要提供了加密、解密、签名、验证等功能。Crypto利用OpenSSL库来实现它的加密技术,它提供OpenSSL中的一系列哈希方法,包括hmac、cipher、decipher、签名和验证等方法的封装。

Crypto官方文档:http://nodejs.org/api/crypto.html

2. Hash算法

哈希算法,是指将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母,随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入,在计算上是不可能的,所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。

通常我们对 登陆密码,都是使用Hash算法进行加密,典型的哈希算法包括 ‘md5′,’sha’,’sha1′,’sha256′,’sha512′,’RSA-SHA’。下面我们就做一下算法的测试。

系统环境

  • Win7 64bit
  • Nodejs:v0.10.31
  • Npm:1.4.23

创建项目


~ cd D:\workspace\javascript>
~ D:\workspace\javascript>mkdir nodejs-crypto && cd nodejs-crypto

新建文件hash.js,打印出所支持的hash算法。


~ vi hash.js

var crypto = require('crypto');  # 加载crypto库
console.log(crypto.getHashes()); # 打印支持的hash算法

运行程序


~ node hash.js

[ 'DSA',
  'DSA-SHA',
  'DSA-SHA1',
  'DSA-SHA1-old',
  'RSA-MD4',
  'RSA-MD5',
  'RSA-MDC2',
  'RSA-RIPEMD160',
  'RSA-SHA',
  'RSA-SHA1',
  'RSA-SHA1-2',
  'RSA-SHA224',
  'RSA-SHA256',
  'RSA-SHA384',
  'RSA-SHA512',
  'dsaEncryption',
  'dsaWithSHA',
  'dsaWithSHA1',
  'dss1',
  'ecdsa-with-SHA1',
  'md4',
  'md4WithRSAEncryption',
  'md5',
  'md5WithRSAEncryption',
  'mdc2',
  'mdc2WithRSA',
  'ripemd',
  'ripemd160',
  'ripemd160WithRSA',
  'rmd160',
  'sha',
  'sha1',
  'sha1WithRSAEncryption',
  'sha224',
  'sha224WithRSAEncryption',
  'sha256',
  'sha256WithRSAEncryption',
  'sha384',
  'sha384WithRSAEncryption',
  'sha512',
  'sha512WithRSAEncryption',
  'shaWithRSAEncryption',
  'ssl2-md5',
  'ssl3-md5',
  'ssl3-sha1',
  'whirlpool' ]

我们看到支持Hash真是不少,究竟怎么选择适合,我也说不清楚。以我对算法的理解,我会以加密计算时间和编码长度,做选择的依据。下面就简单地比较一下几种常见算法。

编辑hash.js文件


~ vi hash.js

///////////////////////////
// Hash算法
///////////////////////////
var crypto = require('crypto')
    ,fs = require('fs');

function hashAlgorithm(algorithm){
    var s1 = new Date();

    var filename = "package.json";
    var txt = fs.ReadStream(filename);

    var shasum = crypto.createHash(algorithm);
    txt.on('data', function(d) {
        shasum.update(d);
    });

    txt.on('end', function() {
        var d = shasum.digest('hex');
        var s2 = new Date();

        console.log(algorithm+','+(s2-s1) +'ms,'+ d);
    });
}

function doHash(hashs){
    hashs.forEach(function(name){
        hashAlgorithm(name);
    })
}

//var algs = crypto.getHashes();
var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];
doHash(algs);

运行程序


~ node hash.js

md5,6ms,85cd416f811574bd4bdb61b241266670
sha,18ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdb
sha1,20ms,0777e65066dca985569fa892fa88e21b45dc656d
sha256,21ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654c
sha512,23ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e443d059903a9cb2dded03a94c8143
RSA-SHA,24ms,b1fc6647fa4fdb4b1b394f8dc7856ac140e2fbdb
RSA-SHA1,25ms,0777e65066dca985569fa892fa88e21b45dc656d
RSA-SHA256,26ms,5e4aea76f93ee87f422fcbd9458edad0e33ddf256d5d93bcc47977e33cb1654c
RSA-SHA512,26ms,94249ec2f83b006354774dd8f8ec81125ea9e1e00f94393d8b20bbd7678e63db53fab6af125e139f9257fd7dbb6c69474e4433d059903a9cb2dded03a94c8143

输出以逗号分隔,分别是算法名、时间、密文。最常见的md5,密文长度最短,计算时间也最少;sha和sha1比较接近;sha512密文最长,计算时间也最长。

由于md5已经有了大量的字典库,对于安全级别一般的网站用sha1吧;如果安全级别要求很高,CPU配置也很牛,可以考虑用sha512。

3. Hmac算法

HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code),HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。HMAC可以有效防止一些类似md5的彩虹表等攻击,比如一些常见的密码直接MD5存入数据库的,可能被反向破解。

定义HMAC需要一个加密用散列函数(表示为H,可以是MD5或者SHA-1)和一个密钥K。我们用B来表示数据块的字节数。(以上所提到的散列函数的分割数据块字长B=64),用L来表示散列函数的输出数据字节数(MD5中L=16,SHA-1中L=20)。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。应用程序中使用的密钥长度若是比B大,则首先用使用散列函数H作用于它,然后用H输出的L长度字符串作为在HMAC中实际使用的密钥。一般情况下,推荐的最小密钥K长度是L个字节。

由于HMAC就是使用散列函数,所以我们同样选择上面的几种算法进行测试。新建文件hmac.js。


~ vi hmac.js

///////////////////////////
// Hmac算法
///////////////////////////

var crypto = require('crypto')
    ,fs = require('fs');

function hmacAlgorithm(algorithm,key){
    var s1 = new Date();

    var filename = "package.json";
    var txt = fs.ReadStream(filename);

    var shasum = crypto.createHmac(algorithm,key);
    txt.on('data', function(d) {
        shasum.update(d);
    });

    txt.on('end', function() {
        var d = shasum.digest('hex');
        var s2 = new Date();

        console.log(algorithm+','+(s2-s1) +'ms,'+ d);
    });
}

function doHmac(hashs,key){
    console.log("\nKey : %s", key);
    console.log("============================");
    hashs.forEach(function(name){
        hmacAlgorithm(name,key);
    })
}

//var algs = crypto.getHashes();
var algs = [ 'md5','sha','sha1','sha256','sha512','RSA-SHA','RSA-SHA1','RSA-SHA256','RSA-SHA512'];

// 短KEY的测试
setTimeout(function(){
    doHmac(algs,"abc");
},1)

// 长KEY的测试
setTimeout(function(){
    var key = "jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda";
    doHmac(algs,key);
},2*1000)

运行程序


~ node hmac.js
Key : abc
============================
md5,6ms,bf106a077abcfa0fffe6ec0da039545b
sha,6ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5
sha1,6ms,aead69a72da77d0615a854dda1086d885807574a
sha256,7ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5d
sha512,8ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8f
RSA-SHA,10ms,a43a00981346ac64bb7b6fb0641b72a101fb04a5
RSA-SHA1,11ms,aead69a72da77d0615a854dda1086d885807574a
RSA-SHA256,12ms,98ac955cb2205ba01a6337951d0ed3fd9b68753544cf81275eced365da57fc5d
RSA-SHA512,13ms,054f37e34b55a19e64a7f88fb60b1122dc0a30e9864ca28d01d61115b13c74de292ab66e85bf007e1a463a52d7c30fdff174618ef954401bc9c2c3318e762c8f

Key : jifdkd;adkfaj^&fjdifefdafda,ijjifdkd;adkfaj^&fjdifefdafdaljifdkd;adkfaj^&fjdifefdafda
============================
md5,5ms,164a8fee6e37bb3e40a9d5dff5c2fd66
sha,5ms,ba06f536856553c3756aa36254a63ef35e225d38
sha1,7ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489
sha256,7ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380d
sha512,8ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dc
RSA-SHA,11ms,ba06f536856553c3756aa36254a63ef35e225d38
RSA-SHA1,12ms,f3a89b0a5ee8a55c2bb6a861748d43e9d44dc489
RSA-SHA256,14ms,f2df911f40e74b2b9bb3d53a7ca4b78d438d511e015d4b50431eaea65339380d
RSA-SHA512,16ms,5b4b57386b1fcc4f1945c47788bf38c013e1cde356fc15e1f946e6bf6738b5dc52ecf17b3ddc80b2ff21f985a1a707df9357fe305e9aa143da073d2cafd794dc

通过比对短key和长key,在编码比较长的算法上面会有一些影响。由于Hmac有了第二参数key,所以会比单独的hash加密登陆密码,有更好的安全性上的保证。

对于网站登陆密码的设计,我们可以做成2个字段存储,用password字段存密文,passkey字段存储key,把算法直接封装到程序里面。


{
  username: 'xxxx'
  password: 'aead69a72da77d0615a854dda1086d885807574a',
  passkey:'abc'
}

就算数据库被攻击,黑客也只是拿了密文和key,密码明文并没有被泄露。并且在不知道加密算法的情况下,也很难通过彩虹表进行攻击。

4. 加密和解密算法

对于登陆密码来说,是不需要考虑解密的,通常都会用不可逆的算法,像md5,sha-1等。但是,对于有安全性要求的数据来说,我们是需要加密存储,然后解密使用的,这时需要用到可逆的加密算法。对于这种基于KEY算法,可以分为对称加密和不对称加密。

  • 对称加密算法的原理很容易理解,通信一方用KEK加密明文,另一方收到之后用同样的KEY来解密就可以得到明文。
  • 不对称加密算法,使用两把完全不同但又是完全匹配的一对Key:公钥和私钥。在使用不对称加密算法加密文件时,只有使用匹配的一对公钥和私钥,才能完成对明文的加密和解密过程。

对于这种类型的操作,Crypto包也提供了大量的算法支持。新建文件cipher.js,打印出所支持的算法。


~ vi cipher.js

var crypto = require('crypto');
console.log(crypto.getCiphers());

运行程序


~ node cipher.js

[ 'CAST-cbc',
  'aes-128-cbc',
  'aes-128-cbc-hmac-sha1',
  'aes-128-cfb',
  'aes-128-cfb1',
  'aes-128-cfb8',
  'aes-128-ctr',
  'aes-128-ecb',
  'aes-128-gcm',
  'aes-128-ofb',
  'aes-128-xts',
  'aes-192-cbc',
  'aes-192-cfb',
  'aes-192-cfb1',
  'aes-192-cfb8',
  'aes-192-ctr',
  'aes-192-ecb',
  'aes-192-gcm',
  'aes-192-ofb',
  'aes-256-cbc',
  'aes-256-cbc-hmac-sha1',
  'aes-256-cfb',
  'aes-256-cfb1',
  'aes-256-cfb8',
  'aes-256-ctr',
  'aes-256-ecb',
  'aes-256-gcm',
  'aes-256-ofb',
  'aes-256-xts',
  'aes128',
  'aes192',
  'aes256',
  'bf',
  'bf-cbc',
  'bf-cfb',
  'bf-ecb',
  'bf-ofb',
  'blowfish',
  'camellia-128-cbc',
  'camellia-128-cfb',
  'camellia-128-cfb1',
  'camellia-128-cfb8',
  'camellia-128-ecb',
  'camellia-128-ofb',
  'camellia-192-cbc',
  'camellia-192-cfb',
  'camellia-192-cfb1',
  'camellia-192-cfb8',
  'camellia-192-ecb',
  'camellia-192-ofb',
  'camellia-256-cbc',
  'camellia-256-cfb',
  'camellia-256-cfb1',
  'camellia-256-cfb8',
  'camellia-256-ecb',
  'camellia-256-ofb',
  'camellia128',
  'camellia192',
  'camellia256',
  'cast',
  'cast-cbc',
  'cast5-cbc',
  'cast5-cfb',
  'cast5-ecb',
  'cast5-ofb',
  'des',
  'des-cbc',
  'des-cfb',
  'des-cfb1',
  'des-cfb8',
  'des-ecb',
  'des-ede',
  'des-ede-cbc',
  'des-ede-cfb',
  'des-ede-ofb',
  'des-ede3',
  'des-ede3-cbc',
  'des-ede3-cfb',
  'des-ede3-cfb1',
  'des-ede3-cfb8',
  'des-ede3-ofb',
  'des-ofb',
  'des3',
  'desx',
  'desx-cbc',
  'id-aes128-GCM',
  'id-aes192-GCM',
  'id-aes256-GCM',
  'idea',
  'idea-cbc',
  'idea-cfb',
  'idea-ecb',
  'idea-ofb',
  'rc2',
  'rc2-40-cbc',
  'rc2-64-cbc',
  'rc2-cbc',
  'rc2-cfb',
  'rc2-ecb',
  'rc2-ofb',
  'rc4',
  'rc4-40',
  'rc4-hmac-md5',
  'seed',
  'seed-cbc',
  'seed-cfb',
  'seed-ecb',
  'seed-ofb' ]

同样地,在这么一大堆的算法面前,完全不知道如何去选择。我还是以加密和解密的计算时间为参考指标,选出几个常见的算法进行测试。


///////////////////////////
// 加密解密算法
///////////////////////////
var crypto = require('crypto')
    ,fs = require('fs');

//加密
function cipher(algorithm, key, buf ,cb){
    var encrypted = "";
    var cip = crypto.createCipher(algorithm, key);
    encrypted += cip.update(buf, 'binary', 'hex');
    encrypted += cip.final('hex');
    cb(encrypted);
}

//解密
function decipher(algorithm, key, encrypted,cb){
    var decrypted = "";
    var decipher = crypto.createDecipher(algorithm, key);
    decrypted += decipher.update(encrypted, 'hex', 'binary');
    decrypted += decipher.final('binary');
    cb(decrypted);
}

function cipherDecipherFile(filename,algorithm, key){
    fs.readFile(filename, "utf-8",function (err, data) {
        if (err) throw err;
        var s1 = new Date();

        cipher(algorithm, key,data,function(encrypted){
            var s2 = new Date();
            console.log('cipher:'+algorithm+','+(s2-s1) +'ms');

            decipher(algorithm, key,encrypted,function(txt){
                var s3 = new Date();
                console.log('decipher:'+algorithm+','+(s3-s2) +'ms');
//                console.log(txt);
            });
        });
    });
}

//console.log(crypto.getCiphers());
var algs = ['blowfish','aes-256-cbc','cast','des','des3','idea','rc2','rc4','seed'];
var key = "abc";
var filename = "book.pdf";//"package.json";
algs.forEach(function(name){
    cipherDecipherFile(filename,name,key);
})

运行程序


~ node cipher.js

cipher:blowfish,46ms
decipher:blowfish,95ms
cipher:des,67ms
decipher:des,104ms
cipher:idea,54ms
decipher:idea,88ms
cipher:rc4,16ms
decipher:rc4,44ms
cipher:des3,158ms
decipher:des3,193ms
cipher:aes-256-cbc,19ms
decipher:aes-256-cbc,47ms
cipher:cast,46ms
decipher:cast,82ms
cipher:seed,64ms
decipher:seed,98ms
cipher:rc2,104ms
decipher:rc2,99ms

输出一共3列,第一列,cipher(加密),decipher(解密);第二列,是算法名称;第三列是计算时间。

在选中的这几个算法中,rc4和aes-256-cbc是表现不错的算法,加密和解密时间都比较短,加密时间:解密时间=1:3;其他的算法,总体时间相对较长,有的加密时间:解密时间=1:1。所以,怎么选算法,另外的一个标准就要看业务需求了。如果业务上,解密操作的次数远大于加密操作的次数,而且是在服务器计算,那么我们最好找到,加密时间:解密时间=N:1,N>1的算法;如果加密在服务器端,解密在客户端完成,那么aes-256-cbc算法的计算时间比例就非常适合了。

5. 签名和验证算法

我们除了对数据进行加密和解密,还需要判断数据在传输过程中,是否真实际和完整,是否被篡改了。那么就需要用到签名和验证的算法,利用不对称加密算法,通过私钥进行数字签名,公钥验证数据的真实性。

数字签名的制作和验证过程,如下图所示。

screenshot.62

下面我们用程序,来现实图中的操作流程,由于证书是我们自己制作的,不打算对外公开,没有到CA进行认证,所以下过程将不包括公钥伪造,再到CA认证的过程。

首先,我们要用openSSL命令先生成,私钥server.pem和公钥cert.pem。


# 生成私钥
~ D:\workspace\javascript\nodejs-crypto>openssl genrsa  -out server.pem 1024
Generating RSA private key, 1024 bit long modulus
..................++++++
..................++++++
e is 65537 (0x10001)

# 生成公钥
~ D:\workspace\javascript\nodejs-crypto>openssl req -key server.pem -new -x509 -out cert.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:
Email Address []:

接下来,我们利用生成私钥生成数学签名,然后再用公钥验证数据,是否被篡改。新建文件signer.js。


~ vi signer.js

///////////////////////////
// 签名验证算法
// openssl genrsa  -out server.pem 1024
// openssl req -key server.pem -new -x509 -out cert.pem
///////////////////////////

var crypto = require('crypto')
    ,fs = require('fs');

function signer(algorithm,key,data){
    var sign = crypto.createSign(algorithm);
    sign.update(data);
    sig = sign.sign(key, 'hex');
    return sig;
}

function verify(algorithm,pub,sig,data){
    var verify = crypto.createVerify(algorithm);
    verify.update(data);
    return verify.verify(pubkey, sig, 'hex')
}

var algorithm = 'RSA-SHA256';
var data = "abcdef";   //传输的数据
var privatePem = fs.readFileSync('server.pem');
var key = privatePem.toString();
var sig = signer(algorithm,key,data); //数字签名

var publicPem = fs.readFileSync('cert.pem');
var pubkey = publicPem.toString();
console.log(verify(algorithm,pubkey,sig,data));         //验证数据,通过公钥、数字签名 =》是原始数据
console.log(verify(algorithm,pubkey,sig,data+"2"));    //验证数据,通过公钥、数字签名  =》不是原始数据

运行程序


~ node signer.js
true
false

两行输出结果,第一行验证的结果是true,表示数据在传输过程中,没有被篡改;第二行验证的结果是false,表示数据在传输过程中被篡改,不是原始的数据。当然,如何保证私钥和公钥的匹配,需要CA第三方来认证,与Crypto库无关本文不再介绍。

6. salt算法

我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。

盐(Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。

加盐的实现过程通常是在需要散列的字段的特定位置增加特定的字符,打乱原始的字串,使其生成的散列结果产生变化。比如,用户使用了一个密码:


123465

经过MD5散列后,可以得出结果:


3d9188577cc9bfe9291ac66b5cc872b7

但是由于用户密码位数不足,短密码的散列结果很容易被彩虹表破解,因此,在用户的密码末尾添加特定字串:


123465abcdefghijklmnopqrstuvwxyz

因此,加盐后的密码位数更长了,散列的结果也发生了变化:


27e20c64ccb8cce9ad68b8ccff6252cf

新建文件salt.js,实现上面的程序。


~ vi salt.js

//////////////////////////////
// salt算法
//////////////////////////////
var crypto = require('crypto');
var md5 = crypto.createHash('md5');
var txt = "123465";

md5.update(txt);
console.log(md5.digest('hex'));

md5 = crypto.createHash('md5');
var salt = "abcdefghijklmnopqrstuvwxyz";
md5.update(txt+salt);
console.log(md5.digest('hex'));

我们可以不用自己动手加盐,而使用crypto.pbkdf2()函数,默认会调用hmac算法,用sha1的散列函数,并且可以设置迭代次数和密文长度。具体的使用代码如下。


~ vi salt.js

var crypto = require('crypto');
var txt = "123465";
var salt = "abcdefghijklmnopqrstuvwxyz";

// 生成密文,默认HMAC函数是sha1算法
crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {
    if (err) { throw err; }
    console.log(hash.toString('hex'));
})

运行程序,生成256位的密文。


~ node salt.js

29c7de002d942ebcbf3afe05f3eb0ff620f0515fe6b8f19176736273cd70805afdfcc828a6f227152dfa4d0c4f96da184fbd060d4d4c86a5deb8d704699c3e8653acdb0e5bc3e584e0890a44206bb2926f0289fc8e0abe49fd1876461fcc50f06dc7991c4b93cc4e80076529c73b2f2c56f16b5b319368edf017f3d3583a33aa44fd30f89801f0d8877eb8262925f5fdc40a5c57f1b275e5674784dca635c75bc58b6c22264e0f29e363eb25dedf1a242429084e3e17d344b59cab3b9723db03ee4838b632786d1a9eb968f2523404286e5d0a41a1707577650cc3cc2f1ab65714a4cb31f068e4aefa259c6be68174e0a475d5610168305a4935a14bb221a516

如果加盐每次都是固定的值也会不安全,我们还可以利用随机randomBytes()函数,配合pbkdf2()函数,让每次都是不同的salt,生成安全级别更高的密文。


~ vi salt.js

//通过伪随机码生成salt,进行加密
crypto.randomBytes(128, function (err, salt) {
    if (err) { throw err;}
    salt = salt.toString('hex');
    console.log(salt); //生成salt

    crypto.pbkdf2(txt, salt, 4096, 256, function (err,hash) {
        if (err) { throw err; }
        hash = hash.toString('hex');
        console.log(hash);//生成密文
    })
})

运行程序


~ node salt.js

# 随机生成的salt
78e59de99f16697e3eb684dcfa8efa086db0940c7cd47d33f9311e3bfcf9d58bf30915f54b3f72793b5c8568d32f1f15c55cc87affd043d96f1ed1f56c25a8054b3d83a306636f3b9e3bc9e48c3303aff54da006f92e370023165857fce0a1d1ff0b89178ae8c1416747275daba25652ea864d52a80427658ea69dbe500a7261

# 通过salt生成的密文
48943eb51ea702b436f95bf1dacc7f64bc41cf7cfa4cb40d101d5550c28caafc58ca720934352238430634f21fd5a6a4ef63fe5828c2665362e9902adc0305f93d2523fbd28521ad00947a74ff8229f63ad5796f2e12677cbed6af02b9973ee0187a69ad67e86790471d95f18d6d2c43ef904f7d17a5d8264f8236f227363a016ae2c14559c17236d540e06c5fd443af740721897f76bdbd9711c8499d7a34cae2e917f900fc364f72f9afaf301845c6e0b5c37def949b4af62336a39dbd1e829405d6189536092c7769a5d7e427b8e97419988da4e1bad49c69f25ac4e96f74a0ce3eab9e1433277568105b1dcc0cf9e1f9c91a7ed391c5825eefcd71ef5ca1

我们把salt和密文一起进行存储,保证用户密码的安全性。

7. 程序代码

本文的程序代码,可以直接从Github上面下载本文项目中的源代码,按照片文章中的介绍学习Crypto,下载地址:https://github.com/bsspirit/nodejs-crypto

也可以直接用github命令行来下载:


~ git clone git@github.com:bsspirit/nodejs-crypto.git   # 下载github项目
~ cd nodejs-crypto                                      # 进入下载目录

最后要声明一下:本人对密码技术理解不深,本文所涉及的密码技术仅仅是Crypto库在应用上的,文中观点不对或描述不清楚的地方,还请专家来指正!!

参考文章:

转载请注明出处:
http://blog.fens.me/nodejs-crypto/

打赏作者