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语言进行非对称加密RSA,R语言进行AES对称加密,R语言配合openssl生成管理应用x509证书,用R语言实现RSA+AES混合加密。
加密算法涉及到密码学的很多深入的知识,我并没有深入研究,本文只针对于openssl的使用,如果有任何与专业教材不符合的描述,请以专业教材为准。
目录
- R语言openssl包介绍
- 生成RSA私钥和公钥
- 加密通信
- 数字签名
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
[…] AES 是对称加密算法,优点:加密速度快;缺点:如果秘钥丢失,就容易解密密文,安全性相对比较差。RSA 是非对称加密算法 , 优点:安全 ;缺点:加密速度慢。RSA算法介绍,请参见文章用openssl生成RSA私钥和公钥。 […]
[…] 由于最近对密码学产生兴趣,使用R语言做一个密码学的研究,因此对R语言中openssl包进行了研究,本文为openssl的第三篇文章,R语言配合openssl生成管理应用x509证书。openssl的文章分别是,R语言进行非对称加密RSA,R语言进行AES对称加密,R语言配合openssl生成管理应用x509证书 […]