• Archive by category "R语言实践"

Blog Archives

用R语言10分钟上手神经网络模型

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-nn-neuralnet/

前言

“神经网络”,“深度学习”,已经不是纯学术的词了,有很多的算法已经落地为我们生活中的各种产品,比如人脸识别,智能客服,从图片中提词,AI作诗,AI作画,chatGPT,AphlaGo等,各种的AI算法应用出圈,打破很多行业壁垒,甚至机器比人类更有创造力,做的更好。这些AI应用出圈的背后,很大程度上是依赖于神经网络的高速发展。

跟上时代的脚步,我将用R语言详细地介绍神经网络建模和应用的过程,以动手操作为主。本文是神经网络的第一篇文章:用R语言10分钟上手神经网络模型neuralnet。

目录

  1. 神经网络介绍
  2. neuralnet包介绍
  3. 神经元模型:零隐藏层
  4. 单层神经网络(感知机):一个隐藏层
  5. 单层神经网络:多分类
  6. 两层神经网络(多层感知器):多分类
  7. 多层神经网络(深度学习)

1. 神经网络介绍

神经网络,也称为人工神经网络 (ANN) 是机器学习的子集,并且是深度学习算法的核心。其名称和结构是受人类大脑的启发,模仿了生物神经元信号相互传递的方式。

人工神经网络 (ANN) 由节点层组成,包含一个输入层、一个或多个隐藏层和一个输出层。 每个节点也称为一个人工神经元,它们连接到另一个节点,具有相关的权重和阈值。 如果任何单个节点的输出高于指定的阈值,那么该节点将被激活,并将数据发送到网络的下一层。 否则,不会将数据传递到网络的下一层。

从单层神经网络(感知器)开始,到包含一个隐藏层的两层神经网络,再到多层的深度神经网络,一共有三次兴起过程。

在神经网络中,处理单元通常按层次分布于神经网络的输入层、隐层和输出层中,因此分别称之为输入节点、隐节点和输出节点,各自的功能如下所示:

  • 输入节点:接受与处理训练数据集中的各输入变量值
  • 隐藏点:实现非线性数据的线性变换
  • 输出节点:给出输出变量的分类或预测结果

2. neuralnet包介绍

在R语言里面,有好几个包都支持神经网络模型,neuralnet包就一个常用包。

neuralnet包,安装过程很简单。


# 安装
> install.packages("neuralnet")

# 加载
> library(neuralnet)

# 设置工作路径
> setwd("C:/work/R/neural/neural_networks")

neuralnet包中的,neuralnet()函数可用于神经网络的模型训练,是这个包中最核心的函数,neuralnet()函数参数很多,是需要我们仔细了解的。

neuralnet()函数,在训练神经网络模型时,支持使用反向传播(BP)、弹性反向传播(RPROP)、无权重回溯、修正的全球收敛版本(GRPROP)训练神经网络,函数允许通过自定义选择误差和激活函数进行灵活设置。此外,还实现了广义权重的计算。

查看neuralnet()函数


> neuralnet
function (formula, data, hidden = 1, threshold = 0.01, stepmax = 1e+05, 
    rep = 1, startweights = NULL, learningrate.limit = NULL, 
    learningrate.factor = list(minus = 0.5, plus = 1.2), learningrate = NULL, 
    lifesign = "none", lifesign.step = 1000, algorithm = "rprop+", 
    err.fct = "sse", act.fct = "logistic", linear.output = TRUE, 
    exclude = NULL, constant.weights = NULL, likelihood = FALSE) 

...

参数列表:

  • formula: 定义模型的公式。
  • data: data.frame格式,训练集原始数据。
  • hidden: 整数向量,指定每层中隐藏神经元的数量,如c(3,2)为二个隐藏层,第一层3个节点,第二层2个节点。
  • threshold: 数值型,停止条件阈值,指定误差函数的偏导数作为停止标准的阈值。
  • stepmax: 数值型,停止条件最大迭代次数,达到这个最大值就会停止神经网络的训练过程。
  • rep: 神经网络训练的重复次数。
  • startweights: 模型起始值的权重向量,设置为NULL用于随机初始化。
  • learningrate.limit: 一个向量或一个列表,包含学习率的最低和最高限制,仅用于RPROP和GRPROP。
  • learningrate.factor: 一个向量或一个列表,包含学习率的上限和下限的乘法系数,仅用于RPROP和GRPROP。
  • learningrate: 数值型,指定传统反向传播使用的学习率,只用于传统的反向传播。
  • lifesign: 字符串,指定函数在计算神经网络时的打印量,’none’, ‘minimal’ or ‘full’。
  • lifesign.step: 整数型,指定在完全lifesign模式下打印最小阈值的步长。
  • algorithm: 字符串,包含用于计算神经网络的算法类型。以下类型是可能的:”backprop”,”rprop+”,”rprop-“,”sag”,或 “slr”。backprop’指的是反向传播,’rprop+’和’rprop-‘指的是带有和不带有权重回溯的弹性反向传播,而’sag’和’slr’则是诱导使用修改后的全局收敛算法(GRPROP)。
  • err.fct: 损失函数,用于计算误差的函数,可以使用字符串’sse’和’ce’,它们代表平方误差之和和交叉熵。
  • act.fct: 激活函数,用于平滑协变量或神经元与权重的交叉积的结果。字符串 “logistic”和 “tanh” 可以用于logistic函数和切线双曲。
  • linear.output:逻辑值,是否线性输出,即是回归还是分类。TRUE表示输出节点的激活函数为线性函数,FALSE表示非线性函数,在B-P中为FALSE
  • exclude: 一个向量或一个矩阵,指定从计算中排除的权重。如果给定的是一个向量,必须知道权重的确切位置。一个有n行3列的矩阵将排除n个权重,其中第一列代表层,第二列代表输入神经元,第三列代表权重的输出神经元。
  • constant.weights: 一个向量,指定训练过程中不需要训练的权重,固定值。
  • likelihood: 逻辑值,如果误差函数等于负对数似然函数,将计算信息准则AIC和BIC。

在实际建模过程中,我们主要使用formula,data,hidden,threshold,algorithm,err.fct,act.fct,linear.output这个参数。

3. 神经元模型:零隐藏层

我们先建立一个最简单的网络模型,只有输入节点和输出节点,不包括隐藏层。这种结构都不算是神经网络,是神经网络的前身神经元模型。神经元模型是一个包含输入,输出与计算功能的模型。输入可以类比为神经元的树突,而输出可以类比为神经元的轴突,计算则可以类比为细胞核。

查看iris数据集


> head(iris)
  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
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

以iris数据集的全量数据做为输入,对单一目标Species==”setosa”,做二分类的模型,判断是否属于是setosa的种类。


# 没有隐藏层
> nn <- neuralnet(Species=="setosa"~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, 
+                 data = iris,hidden = 0)

# 查看网络结构 
> plot(nn)

从输出的神经网络结构图中,我们可以看到,四个输入节点为iris数据集的4个参数(Sepal.Length Sepal.Width Petal.Length Petal.Width Species),输出节点(Species)为模型训练目标,蓝色节点1为这一层的偏差权重(截距)值,从输入节点到输出节点的连线上,有权重值。我们训练神经网络模型,就是为了算出这些权重值,反复迭代。

接下来,我们查看模型的各种输出结果,都在nn这个对象中。

  • call:模型函数
  • response:因变量数据
  • covariate:自变量数据
  • model.list:模型函数
  • err.fct:损失函数
  • act.fct:激活函数
  • linear.output:是否线性输出
  • data:原始数据集
  • exclude:指定从计算中排除的权重
  • net.result:预测值结果
  • weights:各个节点的权重值列表
  • generalized.weights:广义权重
  • startweights:各个节点的初始权重(初始权值为在的正态分布随机数)
  • result.matrix:结果矩阵,终止迭代时各个节点的权重,迭代次数,损失函数值和权重的最大调整量

首先,来查看结果矩阵result.matrix。

> nn$result.matrix
                                             [,1]
error                                1.533438e+00
reached.threshold                    9.212915e-03
steps                                6.297000e+03
Intercept.to.Species == "setosa"     1.202943e-01
Sepal.Length.to.Species == "setosa"  6.432600e-02
Sepal.Width.to.Species == "setosa"   2.441786e-01
Petal.Length.to.Species == "setosa" -2.230368e-01
Petal.Width.to.Species == "setosa"  -5.935682e-02
  • error,损失函数值,1.533438,值越小
  • reached.threshold,终止条件,权重的最大调整量9.212915e-03
  • steps,整个训练执行了6297步
  • Intercept.to.Species,偏差权重(截距),值为1.202943e-01
  • Sepal.Length.to.Species,Sepal.Length变量权重
  • Sepal.Width.to.Species,Sepal.Width变量权重
  • Petal.Length.to.Species,Petal.Length变量权重
  • Petal.Width.to.Species,Petal.Width变量权重

查看连接的初始的权重,随机分配的,分别对应5个不同输入节点到输出节点的权重。


> nn$startweights 
[[1]]
[[1]][[1]]
           [,1]
[1,]  0.5797599
[2,] -0.3526484
[3,]  1.7074446
[4,]  1.3395568
[5,]  0.7781903

查看模型最终的连接权重列表,从startweights初始到weights最终结果。


> nn$weights
[[1]]
[[1]][[1]]
            [,1]
[1,]  0.12029435
[2,]  0.06432600
[3,]  0.24417858
[4,] -0.22303683
[5,] -0.05935682

查看模型中的广义权重,分别对应每个样本数据的权重值


# 取前6条
> head(nn$generalized.weights[[1]])
           [,1]       [,2]       [,3]        [,4]
[1,]  3.1084343  11.799476 -10.777840  -2.8683080
[2,]  0.4883183   1.853634  -1.693141  -0.4505957
[3,]  0.7288879   2.766825  -2.527265  -0.6725814
[4,]  0.4490363   1.704522  -1.556939  -0.4143483
[5,] 20.4485892  77.621919 -70.901170 -18.8689374
[6,] -3.7104548 -14.084718  12.865219   3.4238224

查看模型的计算函数


# 模型公式
> nn$call
neuralnet(formula = Species == "setosa" ~ Sepal.Length + Sepal.Width + 
    Petal.Length + Petal.Width, data = iris, hidden = 0)

# 损失函数
> nn$err.fct
function (x, y) 
{
    1/2 * (y - x)^2
}


attr(,"type")
[1] "sse"

# 激活函数
> nn$act.fct
function (x) 
{
    1/(1 + exp(-x))
}


attr(,"type")
[1] "logistic"

查看模型的变量


# 模型的变量定义
> nn$model.list       
$response
[1] "Species == \"setosa\""

$variables
[1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width" 

# 查看自变量,取前6条
> head(nn$covariate)
     Sepal.Length Sepal.Width Petal.Length Petal.Width
[1,]          5.1         3.5          1.4         0.2
[2,]          4.9         3.0          1.4         0.2
[3,]          4.7         3.2          1.3         0.2
[4,]          4.6         3.1          1.5         0.2
[5,]          5.0         3.6          1.4         0.2
[6,]          5.4         3.9          1.7         0.4

查看因变量的实际值,对模型预测输出变量的预测概率值。


# 查看因变量,实际值,取前6条
> head(nn$response)
  Species == "setosa"
1                TRUE
2                TRUE
3                TRUE
4                TRUE
5                TRUE
6                TRUE

# 模型预测结果,取前6条
> head(nn$net.result[[1]])
          [,1]
[1,] 0.9788590
[2,] 0.8439046
[3,] 0.9021788
[4,] 0.8267209
[5,] 0.9968443
[6,] 1.0170459

通过建立一个最简单的网络模型,我们就能了解 neuralnet() 函数的是怎么使用的了,涉及到的内容还是很多的。对于零隐藏层的网络而言,就可以理解为一个线性模型。

4. 单层神经网络(感知机):一个隐藏层

接下来,我们建立一个真正的神经网络模型,包括输入节点、输出节点、1个隐藏层节,还是以iris数据集的全量数据做为输入,对单一目标Species==”setosa”,做二分类的模型,判断是否属于是setosa的种类。

代码只是改动一处,让hidden=1。


# 训练
> n1 <- neuralnet(Species=="setosa"~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, 
+ data = iris,hidden=1) 

# 画出网络结构 
> plot(n1)

从图的输出,我们看到网络结构中,多了一个隐藏层。

查看结果矩阵


> n1$result.matrix
                                          [,1]
error                             1.899890e-04
reached.threshold                 9.246323e-03
steps                             2.260000e+02
Intercept.to.1layhid1             1.384797e-01
Sepal.Length.to.1layhid1          5.431059e-01
Sepal.Width.to.1layhid1           3.122608e+00
Petal.Length.to.1layhid1         -2.053056e+00
Petal.Width.to.1layhid1          -8.719302e+00
Intercept.to.Species == "setosa" -6.759521e-04
1layhid1.to.Species == "setosa"   1.001522e+00

error的损失值为0.00019,整个训练执行了226步,比神经元模型的零隐藏层,有了大幅的提升。一下子就把分类问题解决了,太神奇了吧。

5. 单层神经网络:多分类

我们增加点难度,用单层神经网路,做个多分类的。


# 单层网络
> n2a <- neuralnet(Species~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, 
+ data = iris,hidden=1) 

# 画图
> plot(n2a)

从网络结构看,输出为4个节点,隐藏层为一个节点,输出3个节点,为3类。输出结果惨不忍睹,error竟然达到了25,基本都是错了。

接下来,我们试试增加隐藏层节点数,从1个变成2个,看看做个多分类的效果。


# 单层网络,2个节点
> n2b <- neuralnet(Species~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, 
+ data = iris,hidden=c(2)) 

> plot(n2b)

从网络结构看,输出为4个节点,隐藏层为两个节点,输出3个节点,为3类。输出结果一下就不错了,error为1.9,步骤是15981,这个结果是能用的。

由于一层神经网络的感知器也只能做线性分类任务,在10年后,才对于两层神经网络的研究才带来神经网络的复苏。

6. 两层神经网络(多层感知器):多分类

下面我们要建立一个两层神经网络模型,包括输入层、输出层、2个隐藏层,还是以iris数据集的全量数据做为输入,对单一目标Species做三分类的模型。

首先,先尝2个隐藏层,每层一个节点,试试模型效果。


# 2个隐藏层,每层1个节点
> n3a <- neuralnet(Species~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, + data = iris,hidden=c(1,1)) > n3a$result.matrix
                                  [,1]
error                     25.000112366
reached.threshold          0.007309118
steps                    358.000000000
Intercept.to.1layhid1      2.677698687
Sepal.Length.to.1layhid1  -0.737245035
Sepal.Width.to.1layhid1    3.844506938
Petal.Length.to.1layhid1  -1.018677154
Petal.Width.to.1layhid1  -10.512582993
Intercept.to.2layhid1     -0.771711595
1layhid1.to.2layhid1       3.469423693
Intercept.to.setosa       -0.510039484
2layhid1.to.setosa         1.612291660
Intercept.to.versicolor    0.754101331
2layhid1.to.versicolor    -0.804506879
Intercept.to.virginica     0.755525924
2layhid1.to.virginica     -0.807147653

error达到了25,又是一个不可接受的结果。

在层数不变的情况下,继续增加每层的节点数,调整为每个隐藏层2个节点。从结果矩阵看,多个很多参数的输出,这些节点都是有神经网络自动生成了,也就是不可解释的因素。


# 2个隐藏层,每层2个节点
> n3b <- neuralnet(Species~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, + data = iris,hidden=c(2,2)) # 结果矩阵 > n3b$result.matrix
                                 [,1]
error                    24.999473404
reached.threshold         0.008004802
steps                    83.000000000
Intercept.to.1layhid1    -0.493094700
Sepal.Length.to.1layhid1 -1.519304930
Sepal.Width.to.1layhid1  -1.217278013
Petal.Length.to.1layhid1  3.491582287
Petal.Width.to.1layhid1   3.469063900
Intercept.to.1layhid2    -0.921659140
Sepal.Length.to.1layhid2 -2.263419044
Sepal.Width.to.1layhid2   0.410851516
Petal.Length.to.1layhid2  1.581399076
Petal.Width.to.1layhid2  -1.453986728
Intercept.to.2layhid1     0.898644804
1layhid1.to.2layhid1     -2.635824185
1layhid2.to.2layhid1     -2.577338580
Intercept.to.2layhid2    -0.829242612
1layhid1.to.2layhid2     -0.706157301
1layhid2.to.2layhid2      1.549670915
Intercept.to.setosa      -0.436567651
2layhid1.to.setosa        1.527644298
2layhid2.to.setosa        1.165157296
Intercept.to.versicolor   0.374083691
2layhid1.to.versicolor   -1.298521851
2layhid2.to.versicolor    1.805614407
Intercept.to.virginica    0.455763243
2layhid1.to.virginica    -1.181002787
2layhid2.to.virginica     1.253298145

# 画图
> plot(n3b)

效果依然不好,error为24.99,完全不能使用。

下来的操作,就是继续加节点。在层数不变的情况下,继续增加每层的节点数,调整为每个隐藏层3个节点。


# 2个隐藏层,每层3个节点
> n3c <- neuralnet(Species~ Sepal.Length + Sepal.Width + Petal.Length + Petal.Width, + data = iris,hidden=c(3,3)) > n3c$result.matrix
                                  [,1]
error                     9.861760e-01
reached.threshold         9.760993e-03
steps                     1.842800e+04
Intercept.to.1layhid1     7.178501e-01
Sepal.Length.to.1layhid1  2.725448e-01
Sepal.Width.to.1layhid1   1.376482e+00
Petal.Length.to.1layhid1 -1.262652e+00
Petal.Width.to.1layhid1  -1.997635e+00
Intercept.to.1layhid2    -2.753156e+00
Sepal.Length.to.1layhid2 -1.465864e+00
Sepal.Width.to.1layhid2   4.125512e+00
Petal.Length.to.1layhid2 -6.026538e-01
Petal.Width.to.1layhid2   2.743376e+00
Intercept.to.1layhid3     1.379399e+00
Sepal.Length.to.1layhid3 -2.256887e-01
Sepal.Width.to.1layhid3   1.027688e+00
Petal.Length.to.1layhid3  3.221391e-01
Petal.Width.to.1layhid3  -2.847147e+00
Intercept.to.2layhid1    -2.353385e+00
1layhid1.to.2layhid1     -1.315389e+03
1layhid2.to.2layhid1      3.555585e+01
1layhid3.to.2layhid1      6.903238e+01
Intercept.to.2layhid2    -6.702997e-01
1layhid1.to.2layhid2      1.352140e+00
1layhid2.to.2layhid2     -5.184099e-02
1layhid3.to.2layhid2     -2.994567e-01
Intercept.to.2layhid3    -3.106167e+00
1layhid1.to.2layhid3      4.958443e+00
1layhid2.to.2layhid3     -2.185125e-01
1layhid3.to.2layhid3     -1.677417e+00
Intercept.to.setosa       4.026726e-01
2layhid1.to.setosa       -1.575905e-02
2layhid2.to.setosa       -1.461912e+00
2layhid3.to.setosa        2.965282e+00
Intercept.to.versicolor   6.790011e-01
2layhid1.to.versicolor   -9.706799e-01
2layhid2.to.versicolor    1.210438e+00
2layhid3.to.versicolor   -2.833834e+00
Intercept.to.virginica   -2.389590e-01
2layhid1.to.virginica     9.950653e-01
2layhid2.to.virginica     7.442383e-01
2layhid3.to.virginica    -3.957608e-01

# 画图
> plot(n3c)

瞬间,模型效果又达到了惊人的新高度,error为0.986,只能用神奇来形容,我也不知道为什么,算法的“玄学” 由此而来。

7. 多层神经网络(深度学习):多个隐藏层

在上个步骤中,我们是层数不变增加每层节点数,当然我们也可以节点数不变,增加更多的层数来操作。继续增加到3层的,保持节点数每个隐藏层2个节点进行测试。


# 3个隐藏层,每层2个节点
> n3d <- neuralnet(Species~ Sepal.Length + Sepal.Width + Petal.Length + 
                         Petal.Width, + data = iris,hidden=c(2,2,2)) 

# 结果矩阵 
> n3d$result.matrix
                                  [,1]
error                     9.814611e-01
reached.threshold         9.856631e-03
steps                     2.599500e+04
Intercept.to.1layhid1     4.377454e+00
Sepal.Length.to.1layhid1  2.988690e+00
Sepal.Width.to.1layhid1   4.895009e+00
Petal.Length.to.1layhid1  4.739920e+00
Petal.Width.to.1layhid1   5.049575e+00
Intercept.to.1layhid2     1.713367e+00
Sepal.Length.to.1layhid2  4.900288e-01
Sepal.Width.to.1layhid2   6.486628e-01
Petal.Length.to.1layhid2 -1.317743e+00
Petal.Width.to.1layhid2  -1.286672e+00
Intercept.to.2layhid1    -5.678148e+01
1layhid1.to.2layhid1     -5.584444e+01
1layhid2.to.2layhid1      9.250502e+02
Intercept.to.2layhid2     1.417653e+01
1layhid1.to.2layhid2      1.370346e+01
1layhid2.to.2layhid2     -3.770254e+01
Intercept.to.3layhid1    -1.702465e+00
2layhid1.to.3layhid1      1.833016e+00
2layhid2.to.3layhid1      4.370202e+00
Intercept.to.3layhid2    -2.525917e+00
2layhid1.to.3layhid2      8.186075e+00
2layhid2.to.3layhid2      1.849959e+00
Intercept.to.setosa       1.991064e+00
3layhid1.to.setosa       -2.193503e+00
3layhid2.to.setosa        1.780463e-01
Intercept.to.versicolor  -2.462871e+00
3layhid1.to.versicolor    2.184454e+00
3layhid2.to.versicolor    1.303726e+00
Intercept.to.virginica    1.471821e+00
3layhid1.to.virginica     9.088568e-03
3layhid2.to.virginica    -1.481810e+00

# 画图
> plot(n3d)

三个隐藏的模型效果,同样能达到非常好的效果,error为0.981。

通过不停的增加层数的节点数,神经网络模型会越来越好,但是计算的复杂度会越来越高,从结果矩阵的输出,我们就可感觉到,输出的变量一次比一次多,都不知道这些变量是干什么用的,要想去解释这个模型根本就无从下手。

保持理性的思维,虽然神经网络可以无限帮我们提升模型的效果,但由于不可以解释,“玄之又玄”,需要在适合场景来使用,而不是全都交给神经网络。本文为神经网络的入门文章第一篇,针对这个主题,我们也准备多写几篇文章,来用R语言进行详细的描述。

说多少理论都不如上手一试。本文代码已上传到github: https://github.com/bsspirit/neural_networks/blob/main/01_neuralnet.r

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

打赏作者

用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语言配合openssl命令行生成和管理x509证书

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-cert/

前言

openssl工具博大精深,本文尝试了使用 openssl 的命令行工具结合R语言中的openssl包的工具,进行了秘钥生成,证书生成,证书管理,证书下载,证书验证等的功能尝试,对秘钥和x509证书进行生成、管理和应用。本文为openssl的第三篇文章,R语言配合openssl生成管理应用x509证书。

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

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

目录

  1. X.509证书的基本知识
  2. 用 openssl 命令工具生成证书
  3. 用 R语言进行证书管理
  4. 获取网站证书并验证

1. X.509证书的基本知识

在互联网应用中,为保证服务器端和客户端的相互信息、数据安全等的要求,做大量使用到证书用来确认双方身份。X.509证书主要用于识别互联网通信和计算机网络中的身份,保护数据传输安全。

X.509证书的结构优势在于它是由公钥和私钥组成的密钥对而构建的。公钥和私钥能够用于加密和解密信息,验证发送者的身份和确保消息本身的安全性。基于X.509的PKI最常见的用例是使用SSL证书让网站与用户之间实现HTTPS安全浏览。X.509协议同样也适用于应用程序安全的代码签名、数字签名和其他重要的互联网协议。

证书文件的常用的扩展名和秘钥扩展名。

  • .crt .cer .der,证书文件(Certificate),通常是DER二进制格式的
  • .pem,der编码的证书再进行Base64编码的数据存放在”—–BEGIN CERTIFICATE—–“和”—–END CERTIFICATE—–“之中
  • .key,密钥文件,私钥和公钥匙
  • .csr,证书认证签名请求(Certificate signing request)

在本地的window环境中,可以运行 certmgr.msc 来查看本地系统中,所有提前预制的CA机构证书。

2. 用 openssl 命令工具生成证书

我们来模拟一下网站向CA申请证书的互联网应用场景,由于并不是去真实的CA机构申请证书,我们可以把CA机构一起进行模拟。自己设定一个CA机构生成一个CA机构证书,再生成一个自己网站(fens.me)的证书,然后用CA的证书对自己的网站证书进行签名,来模拟一个实际网站申请证书的过程。

我接下来通过命令行openssl工具生成证书。本机环境为window10,通过安装Git工具,使用自带的Mingw64的工具中的openssl环境。


# 查看openssl 版本
dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$ openssl version
OpenSSL 1.1.1d  10 Sep 2019

2.1 生成CA根证书的步骤
我们要生成自签名的证书,所以我们需要自己扮演CA,作为CA颁发证书的机构。

操作步骤:生成CA私钥(ca.key)–>生成自签名得到根证书(.crt)

第一步,生成CA私钥(ca.key),生成CA认证机构的证书密钥key。
使用命令:openssl genrsa -out ca.key 2048
参数:

  • -out ca.key:将生成的私钥保存至ca.key文件。
  • 2048为要生成的私钥的长度。

# 生成CA私钥ca.key
dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$ openssl genrsa -out ca.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
.............................................................................+++++
...............................+++++
e is 65537 (0x010001)

# 查看生成的ca.key的内容
$ cat ca.key
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA1iLmJeJHYeVutw6Ui+V/tZs2SS4GFzTWRkoRmmt4Z+5jQMt/
dv94WbjmKyvNNrXiNiGz1jZQsw2Bj+22rSKjGEh4Qipjpcc+AOfl99V3diH8YM/c
wj6dNFKqqmpJHtzDSbhOudPbwDsFugq3dyVsV1hMKrlEBetI7Qj/8kZSie1KL+QS
00G+ISCHUes8f/eFHgd7sMXpOb65NJhAKZGSeT49V+IeF1M82VJEyV+x1p/C3ppA
L9PTt3RFftTQJjXW48tIfS3R85E0libyeQnlxM3slEf2UnO16wq/7j3KmWiPD9vm
tPnhNjKbBr6PbtAR+CAsSpxaVLMpA1PDydqsCQIDAQABAoIBAQDKI22QK3do0TTf
NrNBECarH1ZyZDC5rVXsSjUtyO+DZIRrAO5VPbm1MJaOAC4azD07Djt+Jg2tyD0a
ybIc48qDvL1Q8ETalCdSoZXzip3XdSORnAZvkr3BS5vxfJ2Dta/fok0mm/ggEVdJ
lJ41qa0JEr/rwLGWyNExeMJrSLjXmt4S3fkZNJ12icCRKddeFyTL17LEf6lENXol
e/SWuvB1+RhZb73eJVMPTJcoRXulJ0/pUD2QxLMn/YhvMVH8A13v9TipmS51mwLP
FN8F9BNm2K6QXHgQLghjYbpDk1pRTzj+4Yr6dbWZipHSWWJy1WKMm93M+5VNuG6a
tKGj/HQ1AoGBAPhhIxczFZxhd4qs6t09OkU2aEpGYcdMGEYFd7OUYhjUOhXHUMMC
uUWAq2555VaUrKhlqovp+uTdMESop7Ac6gfSH0yG1rTLSiFCuOsNcBx1FKUgCh3Q
OP0JugH6yOJuCwyT1DCoipGbq/sjhaUmb+sqwN+IhcQ6vCw4Qnp2emvfAoGBANy0
zdG7PIL2i9Rub38+cR0/rFdZJFqWQXv/j1c+o+cAQfzsk6jPV5uR+St+HXyafMNu
I7po1mvp7v1UJceFrynZGqdS0FNALmDAnySaPoyhqSjk+nB88MkSEcACr64Smumw
5DHkU3hO+hhayCzPIsILiU67jXvIP3F5GabxNGUXAoGAGN+JZxJbkKyGDyIf5wXx
puq66O1Bb3bkW2bCxP4QENJ3+qRaJx1HtnkbMdYNLG15GOgNezN0R6UK52VIXa+X
lC7rqXs7VyGgi9IluIxA9OiYp6yctr2aZ2So9vfJVDuW6ayILFNEhS5ku6KkCJTf
0loWtIv0cWE8ZOVBawggXFsCgYBWw612Ggl74rit1ox2lXGacgGqhRzJj/BGcv1C
6xk8ItnOOKMD5h8mxYgTFQ06gvExUwcwrS4+VkThykbf3SozZWoZBXFoiP6ygocN
uKWGW7dIoFvkBPoT0cqwlV3DLQVBgYz0IDLARSwEjwvKsdOUPTzJeunwE5T9YwI2
ovRaGQKBgF0zysxImSvnC8jQFB5+jw5dCnP8gXDljZPDJhxwlkbNc85ikA27vSkQ
+ql0YOsgka3DH4ISOpMGJXaCTkI86OXtLlG0HBjLgiiyYQnFieQ1xPB3PfCPKWHl
G84TmLUw9uxC08jV0r7UfIDBxv7kNGDLJqyTmsCYON9EXbbt03ac
-----END RSA PRIVATE KEY-----

第二步,生成自签名得到根证书(ca.crt),CA的基本信息,生成证书时填入的信息,包含国家,地区,公司,域名等信息。
使用命令:openssl req -new -x509 -key ca.key -out ca.crt -days 365
参数说明:

  • -new :说明生成证书请求文件
  • -x509 :说明生成自签名证书
  • -key :指定已有的秘钥文件生成秘钥请求,只与生成证书请求选项-new配合。
  • -days:证书有效天数
  • -out :-out 指定生成的证书请求或者自签名证书名称

# 自签名得到根证书(.crt)
dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$  openssl req -new -x509 -key ca.key -out ca.crt -days 365
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]:ZH
State or Province Name (full name) [Some-State]:BJ
Locality Name (eg, city) []:BEIJING
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:myca.com
Email Address []:bsspirit@gmail.com

2.2 生成用户证书的步骤
接下来,生成我自己的博客的 fens.me 网站的秘钥,然后生成证书请求,用CA的证书进行签名,最后自己网站的证书。。

生成私钥(.key)–>生成证书请求(.csr)–>用CA根证书签名得到证书(.crt)

第一步,生成私钥(fens.me.key)


dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$ openssl genrsa -out fens.me.key 2048
Generating RSA private key, 2048 bit long modulus (2 primes)
...............................................................................................................+++++
.................................+++++
e is 65537 (0x010001)

第二步,生成CSR(Certificate Signing Request)证书请求(fens.me.csr)


dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$ openssl req -new -key fens.me.key -out fens.me.csr
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]:ZH
State or Province Name (full name) [Some-State]:BJ
Locality Name (eg, city) []:BEIJING
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:fens.me
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:bsspirit@gmail.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:fens.me
An optional company name []:

查看CSR文件中的信息,需要用到openssl命令工具来查看


dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$ openssl req -text -noout -verify -in fens.me.csr
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = ZH, ST = BJ, L = BEIJING, O = Internet Widgits Pty Ltd, OU = fens.me, emailAddress = bsspirit@gmail.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:b5:d3:98:e5:0f:57:19:a0:dd:c1:aa:7d:de:55:
                    c7:e7:ae:dc:03:30:68:f8:71:32:8b:c2:14:d8:b3:
                    31:ec:55:ca:70:fb:e2:17:8b:86:8f:c6:10:4e:fb:
                    4f:25:80:cf:9b:cf:6d:46:b6:ba:90:8f:a1:68:ba:
                    c2:ef:c4:99:e2:8a:0b:7f:af:7b:37:d7:5c:5a:5f:
                    0c:10:b3:3a:ab:b1:a6:c0:9d:8a:42:42:68:0c:c3:
                    07:52:b3:39:c1:8e:9f:dc:7b:c5:88:11:9d:7f:cd:
                    ad:95:27:42:47:a8:af:74:dc:66:df:d7:29:0b:ae:
                    3c:f6:4a:d2:dc:6b:20:60:5d:59:37:55:c0:48:68:
                    40:31:67:b5:d6:68:13:ed:f1:b2:70:00:7f:b0:c5:
                    1a:ba:e3:f5:c4:42:97:ef:1b:68:3c:43:ff:dc:81:
                    0d:7a:6f:f4:88:60:ce:47:32:7b:64:27:a0:e7:89:
                    e7:fa:60:73:6a:64:77:34:d9:6b:2a:f5:9b:c4:6f:
                    49:82:84:43:01:8a:ea:90:d6:0b:5c:f1:19:9f:56:
                    c2:c1:df:22:29:28:79:40:5f:72:13:ac:9b:b0:6f:
                    0b:b6:09:9f:c4:a8:12:5d:51:16:f0:81:e2:41:6a:
                    2a:74:56:bf:48:77:26:84:f9:57:f3:f9:f4:af:94:
                    68:bd
                Exponent: 65537 (0x10001)
        Attributes:
            challengePassword        :fens.me
    Signature Algorithm: sha256WithRSAEncryption
         10:cf:2f:82:24:a5:48:b0:c9:c2:79:00:eb:ba:70:8f:02:f4:
         cb:7a:b8:9b:d9:eb:9c:dd:34:03:9e:d7:3a:bd:7d:08:af:11:
         2c:a5:3e:0b:40:8c:3e:64:bb:c6:8b:b0:b0:6a:cb:59:b9:9b:
         e1:a1:d9:bc:ec:10:ed:d8:29:f2:a3:fd:f1:94:c6:4e:26:95:
         23:25:28:b7:01:fa:3d:bd:96:8b:b5:e8:f4:6b:c3:b9:67:a9:
         29:27:96:18:51:98:3a:45:87:31:35:6c:16:51:be:60:40:6e:
         2d:ea:0d:d6:0c:36:6f:5d:24:4e:c1:20:f7:42:7b:9e:62:81:
         e6:38:88:03:0a:7e:15:01:8e:ab:ac:9b:30:93:21:72:19:bf:
         9d:4d:51:ba:30:74:34:4f:1b:30:4d:c8:74:ca:e8:3e:15:a5:
         ec:2c:0e:79:4f:d1:11:56:0f:da:38:d0:6b:1e:54:ac:50:28:
         04:d2:8d:b4:6b:f3:d1:ee:a5:f2:43:6f:4c:73:42:13:df:66:
         30:4f:6c:16:6f:99:91:9f:56:ec:c2:99:aa:e1:82:28:2a:ce:
         e2:4e:9e:95:c4:52:fa:be:7a:8e:1e:32:6f:f9:83:92:c1:8a:
         da:a8:a1:ef:e6:df:14:bb:16:c0:b9:67:8d:71:43:69:d4:2e:
         2d:6a:9d:4c
verify OK

第三步,用CA根证书签名得到证书。实际过程中,会把上面生成的fens.me.csrR文件发给CA,请求CA签名,从而生成网站证书 fens.me.crt。


dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$ openssl x509 -req -in fens.me.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out fens.me.crt -days 365
Signature ok
subject=C = ZH, ST = BJ, L = BEIJING, O = Internet Widgits Pty Ltd, OU = fens.me, emailAddress = bsspirit@gmail.com
Getting CA Private Key

# 查看证书中的内容
$ openssl x509 -in fens.me.crt -text -noout
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 1 (0x1)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = ZH, ST = BJ, L = BEIJING, O = Internet Widgits Pty Ltd, CN = myca.com, emailAddress = bsspirit@gmail.com
        Validity
            Not Before: Nov 30 09:47:58 2022 GMT
            Not After : Nov 30 09:47:58 2023 GMT
        Subject: C = ZH, ST = BJ, L = BEIJING, O = Internet Widgits Pty Ltd, OU = fens.me, emailAddress = bsspirit@gmail.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:b5:d3:98:e5:0f:57:19:a0:dd:c1:aa:7d:de:55:
                    c7:e7:ae:dc:03:30:68:f8:71:32:8b:c2:14:d8:b3:
                    31:ec:55:ca:70:fb:e2:17:8b:86:8f:c6:10:4e:fb:
                    4f:25:80:cf:9b:cf:6d:46:b6:ba:90:8f:a1:68:ba:
                    c2:ef:c4:99:e2:8a:0b:7f:af:7b:37:d7:5c:5a:5f:
                    0c:10:b3:3a:ab:b1:a6:c0:9d:8a:42:42:68:0c:c3:
                    07:52:b3:39:c1:8e:9f:dc:7b:c5:88:11:9d:7f:cd:
                    ad:95:27:42:47:a8:af:74:dc:66:df:d7:29:0b:ae:
                    3c:f6:4a:d2:dc:6b:20:60:5d:59:37:55:c0:48:68:
                    40:31:67:b5:d6:68:13:ed:f1:b2:70:00:7f:b0:c5:
                    1a:ba:e3:f5:c4:42:97:ef:1b:68:3c:43:ff:dc:81:
                    0d:7a:6f:f4:88:60:ce:47:32:7b:64:27:a0:e7:89:
                    e7:fa:60:73:6a:64:77:34:d9:6b:2a:f5:9b:c4:6f:
                    49:82:84:43:01:8a:ea:90:d6:0b:5c:f1:19:9f:56:
                    c2:c1:df:22:29:28:79:40:5f:72:13:ac:9b:b0:6f:
                    0b:b6:09:9f:c4:a8:12:5d:51:16:f0:81:e2:41:6a:
                    2a:74:56:bf:48:77:26:84:f9:57:f3:f9:f4:af:94:
                    68:bd
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         7d:5e:a2:f1:c4:91:19:4f:c3:6a:88:7c:33:4b:d8:fc:20:e3:
         e4:ad:38:23:5e:90:95:46:fe:4a:45:4d:70:58:2a:90:c2:b9:
         53:a4:80:90:b4:6d:a5:b9:c0:b0:73:8c:ff:9d:f2:b4:06:93:
         da:66:67:e2:85:0e:9b:11:64:70:c5:b6:a2:93:8b:3e:00:cb:
         1c:34:0c:3a:34:86:16:8b:7a:c6:e4:6a:8e:4f:cb:86:ea:25:
         1d:dc:18:1b:c4:74:b0:a5:d1:a4:e7:f6:f4:e8:44:c4:13:e0:
         a5:b5:3f:c5:54:ae:f3:e4:ee:9a:53:6a:53:73:e3:84:df:65:
         ac:93:de:94:46:9a:4b:2f:7f:d4:cb:b9:de:ac:9b:72:9b:68:
         f4:bf:59:e8:79:b0:c5:cd:2a:7c:70:d1:ff:2b:3c:10:0a:8b:
         d4:36:33:68:d4:d7:1f:0e:16:98:f6:10:eb:a5:23:a8:22:9f:
         66:f5:0a:b0:1b:05:54:ab:4c:08:cf:55:c2:46:13:de:b0:e7:
         2c:76:6a:3d:5f:ec:44:f0:9c:75:d6:13:ca:d1:20:02:7c:9c:
         ea:48:58:aa:e2:15:02:37:a3:07:f9:40:89:4e:ba:2a:02:19:
         a3:e7:9f:65:00:d5:e1:27:dd:63:50:74:30:b8:7b:8b:4b:95:
         93:f2:ec:b1

查看当前目录中的文件,共5个


dan@DanZhang MINGW64 /c/work/R/encrypt/n1
$ ll
total 20
-rw-r--r-- 1 dan 197121 1448 Nov 30 17:40 ca.crt
-rw-r--r-- 1 dan 197121 1702 Nov 30 15:26 ca.key
-rw-r--r-- 1 dan 197121 1294 Nov 30 17:47 fens.me.crt
-rw-r--r-- 1 dan 197121 1096 Nov 30 17:45 fens.me.csr
-rw-r--r-- 1 dan 197121 1702 Nov 30 17:44 fens.me.key

2.3 查看证书
用windows程序打开证书,左边是  fens.me.crt证书,右边为ca.crt根证书。

  • ca.crt根证书,是我们自己的生成的,未获得电脑的信任。通过安装ca.crt根证书,让本机电脑获得信任。
  • fens.me.crt证书,用ca.crt根证书颁发的。

这样我们就完成了,就openssl命令行工具生成自制证书的过程,一路下来还是挺顺利的。

3. 用 R语言进行证书管理

本来我是打算用R语言openssl包,重现上面的证书生成的过程,但查了很多资料发现在R中,并没有用于证书生成的函数支持,只能做证书的一些管理和验证。那么,我就把openssl包中,能有关证书支持的函数,做一个介绍。

证书相关的函数:

  • read_cert,读取证书文件
  • read_cert_bundle,读取证书包
  • read_pem,以pem格式,读证书
  • read_pubkey,读取公钥
  • write_pem,以pem格式,输出秘钥和证书
  • write_pkcs1,以pkcs格式,输出秘钥和证书
  • write_ssh,以ssh格式,输出秘钥和证书
  • ca_bundle,X509证书集合
  • certificates,证书检查
  • cert_verify,证书验证

我们用R语言API来读取刚才生成ca.key秘钥查看秘钥的信息,并比较确认生成key的秘钥格式。

3.1 取读ca.key的私钥
使用read_key() 以私钥方式进行读取。


# 以私钥方式进行读取                             
> cakey<-read_key("./ca.key");cakey
[2048-bit rsa private key]
md5: 87be025db1f33785aafa00aa83388a8b
sha256: c03bc0d35b4593ff81cde4545b1aa62f80e754545297f30669bdcce29a910a45

# 查看cakey结构
> str(cakey)
List of 4
 $ type  : chr "rsa"
 $ size  : int 2048
 $ pubkey:List of 5
  ..$ type       : chr "rsa"
  ..$ size       : int 2048
  ..$ ssh        : chr "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWIuYl4kdh5 ..."
  ..$ fingerprint: 'hash' raw [1:32] c0 3b c0 d3 ...
  ..$ data       :List of 2
  .. ..$ e: 'bignum' raw [1:3] 01 00 01
  .. ..$ n: 'bignum' raw [1:257] 00 d6 22 e6 ...
 $ data  :List of 8
  ..$ e : 'bignum' raw [1:3] 01 00 01
  ..$ n : 'bignum' raw [1:257] 00 d6 22 e6 ...
  ..$ p : 'bignum' raw [1:129] 00 f8 61 23 ...
  ..$ q : 'bignum' raw [1:129] 00 dc b4 cd ...
  ..$ d : 'bignum' raw [1:257] 00 ca 23 6d ...
  ..$ dp: 'bignum' raw [1:128] 18 df 89 67 ...
  ..$ dq: 'bignum' raw [1:128] 56 c3 ad 76 ...
  ..$ qi: 'bignum' raw [1:128] 5d 33 ca cc ...

# 查看ssh格式
> ca.ssh<-write_ssh(cakey);ca.ssh
[1] "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDWIuYl4kdh5W63DpSL5X+1mzZJLgYXNNZGShGaa3hn7mNAy392/3hZuOYrK802teI2IbPWNlCzDYGP7batIqMYSHhCKmOlxz4A5+X31Xd2Ifxgz9zCPp00Uqqqakke3MNJuE6509vAOwW6Crd3JWxXWEwquUQF60jtCP/yRlKJ7Uov5BLTQb4hIIdR6zx/94UeB3uwxek5vrk0mEApkZJ5Pj1X4h4XUzzZUkTJX7HWn8LemkAv09O3dEV+1NAmNdbjy0h9LdHzkTSWJvJ5CeXEzeyUR/ZSc7XrCr/uPcqZaI8P2+a0+eE2MpsGvo9u0BH4ICxKnFpUsykDU8PJ2qwJ"

# 查看der格式
> ca.der<-write_der(cakey);ca.der
   [1] 30 82 04 a3 02 01 00 02 82 01 01 00 d6 22 e6 25 e2 47 61 e5 6e b7 0e 94
  [25] 8b e5 7f b5 9b 36 49 2e 06 17 34 d6 46 4a 11 9a 6b 78 67 ee 63 40 cb 7f
  [49] 76 ff 78 59 b8 e6 2b 2b cd 36 b5 e2 36 21 b3 d6 36 50 b3 0d 81 8f ed b6
  [73] ad 22 a3 18 48 78 42 2a 63 a5 c7 3e 00 e7 e5 f7 d5 77 76 21 fc 60 cf dc
  [97] c2 3e 9d 34 52 aa aa 6a 49 1e dc c3 49 b8 4e b9 d3 db c0 3b 05 ba 0a b7
 [121] 77 25 6c 57 58 4c 2a b9 44 05 eb 48 ed 08 ff f2 46 52 89 ed 4a 2f e4 12
 [145] d3 41 be 21 20 87 51 eb 3c 7f f7 85 1e 07 7b b0 c5 e9 39 be b9 34 98 40
 [169] 29 91 92 79 3e 3d 57 e2 1e 17 53 3c d9 52 44 c9 5f b1 d6 9f c2 de 9a 40
 [193] 2f d3 d3 b7 74 45 7e d4 d0 26 35 d6 e3 cb 48 7d 2d d1 f3 91 34 96 26 f2
 [217] 79 09 e5 c4 cd ec 94 47 f6 52 73 b5 eb 0a bf ee 3d ca 99 68 8f 0f db e6
 [241] b4 f9 e1 36 32 9b 06 be 8f 6e d0 11 f8 20 2c 4a 9c 5a 54 b3 29 03 53 c3
 [265] c9 da ac 09 02 03 01 00 01 02 82 01 01 00 ca 23 6d 90 2b 77 68 d1 34 df
 [289] 36 b3 41 10 26 ab 1f 56 72 64 30 b9 ad 55 ec 4a 35 2d c8 ef 83 64 84 6b
 [313] 00 ee 55 3d b9 b5 30 96 8e 00 2e 1a cc 3d 3b 0e 3b 7e 26 0d ad c8 3d 1a
 [337] c9 b2 1c e3 ca 83 bc bd 50 f0 44 da 94 27 52 a1 95 f3 8a 9d d7 75 23 91
 [361] 9c 06 6f 92 bd c1 4b 9b f1 7c 9d 83 b5 af df a2 4d 26 9b f8 20 11 57 49
 [385] 94 9e 35 a9 ad 09 12 bf eb c0 b1 96 c8 d1 31 78 c2 6b 48 b8 d7 9a de 12
 [409] dd f9 19 34 9d 76 89 c0 91 29 d7 5e 17 24 cb d7 b2 c4 7f a9 44 35 7a 25
 [433] 7b f4 96 ba f0 75 f9 18 59 6f bd de 25 53 0f 4c 97 28 45 7b a5 27 4f e9
 [457] 50 3d 90 c4 b3 27 fd 88 6f 31 51 fc 03 5d ef f5 38 a9 99 2e 75 9b 02 cf
 [481] 14 df 05 f4 13 66 d8 ae 90 5c 78 10 2e 08 63 61 ba 43 93 5a 51 4f 38 fe
 [505] e1 8a fa 75 b5 99 8a 91 d2 59 62 72 d5 62 8c 9b dd cc fb 95 4d b8 6e 9a
 [529] b4 a1 a3 fc 74 35 02 81 81 00 f8 61 23 17 33 15 9c 61 77 8a ac ea dd 3d
 [553] 3a 45 36 68 4a 46 61 c7 4c 18 46 05 77 b3 94 62 18 d4 3a 15 c7 50 c3 02
 [577] b9 45 80 ab 6e 79 e5 56 94 ac a8 65 aa 8b e9 fa e4 dd 30 44 a8 a7 b0 1c
 [601] ea 07 d2 1f 4c 86 d6 b4 cb 4a 21 42 b8 eb 0d 70 1c 75 14 a5 20 0a 1d d0
 [625] 38 fd 09 ba 01 fa c8 e2 6e 0b 0c 93 d4 30 a8 8a 91 9b ab fb 23 85 a5 26
 [649] 6f eb 2a c0 df 88 85 c4 3a bc 2c 38 42 7a 76 7a 6b df 02 81 81 00 dc b4
 [673] cd d1 bb 3c 82 f6 8b d4 6e 6f 7f 3e 71 1d 3f ac 57 59 24 5a 96 41 7b ff
 [697] 8f 57 3e a3 e7 00 41 fc ec 93 a8 cf 57 9b 91 f9 2b 7e 1d 7c 9a 7c c3 6e
 [721] 23 ba 68 d6 6b e9 ee fd 54 25 c7 85 af 29 d9 1a a7 52 d0 53 40 2e 60 c0
 [745] 9f 24 9a 3e 8c a1 a9 28 e4 fa 70 7c f0 c9 12 11 c0 02 af ae 12 9a e9 b0
 [769] e4 31 e4 53 78 4e fa 18 5a c8 2c cf 22 c2 0b 89 4e bb 8d 7b c8 3f 71 79
 [793] 19 a6 f1 34 65 17 02 81 80 18 df 89 67 12 5b 90 ac 86 0f 22 1f e7 05 f1
 [817] a6 ea ba e8 ed 41 6f 76 e4 5b 66 c2 c4 fe 10 10 d2 77 fa a4 5a 27 1d 47
 [841] b6 79 1b 31 d6 0d 2c 6d 79 18 e8 0d 7b 33 74 47 a5 0a e7 65 48 5d af 97
 [865] 94 2e eb a9 7b 3b 57 21 a0 8b d2 25 b8 8c 40 f4 e8 98 a7 ac 9c b6 bd 9a
 [889] 67 64 a8 f6 f7 c9 54 3b 96 e9 ac 88 2c 53 44 85 2e 64 bb a2 a4 08 94 df
 [913] d2 5a 16 b4 8b f4 71 61 3c 64 e5 41 6b 08 20 5c 5b 02 81 80 56 c3 ad 76
 [937] 1a 09 7b e2 b8 ad d6 8c 76 95 71 9a 72 01 aa 85 1c c9 8f f0 46 72 fd 42
 [961] eb 19 3c 22 d9 ce 38 a3 03 e6 1f 26 c5 88 13 15 0d 3a 82 f1 31 53 07 30
 [985] ad 2e 3e 56 44 e1 ca 46 df dd 2a 33 65 6a 19 05
 [ reached getOption("max.print") -- omitted 191 entries ]

# 查看pkcs1格式
> ca.pkcs1<-write_pkcs1(cakey);ca.pkcs1
[1] "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA1iLmJeJHYeVutw6Ui+V/tZs2SS4GFzTWRkoRmmt4Z+5jQMt/\ndv94WbjmKyvNNrXiNiGz1jZQsw2Bj+22rSKjGEh4Qipjpcc+AOfl99V3diH8YM/c\nwj6dNFKqqmpJHtzDSbhOudPbwDsFugq3dyVsV1hMKrlEBetI7Qj/8kZSie1KL+QS\n00G+ISCHUes8f/eFHgd7sMXpOb65NJhAKZGSeT49V+IeF1M82VJEyV+x1p/C3ppA\nL9PTt3RFftTQJjXW48tIfS3R85E0libyeQnlxM3slEf2UnO16wq/7j3KmWiPD9vm\ntPnhNjKbBr6PbtAR+CAsSpxaVLMpA1PDydqsCQIDAQABAoIBAQDKI22QK3do0TTf\nNrNBECarH1ZyZDC5rVXsSjUtyO+DZIRrAO5VPbm1MJaOAC4azD07Djt+Jg2tyD0a\nybIc48qDvL1Q8ETalCdSoZXzip3XdSORnAZvkr3BS5vxfJ2Dta/fok0mm/ggEVdJ\nlJ41qa0JEr/rwLGWyNExeMJrSLjXmt4S3fkZNJ12icCRKddeFyTL17LEf6lENXol\ne/SWuvB1+RhZb73eJVMPTJcoRXulJ0/pUD2QxLMn/YhvMVH8A13v9TipmS51mwLP\nFN8F9BNm2K6QXHgQLghjYbpDk1pRTzj+4Yr6dbWZipHSWWJy1WKMm93M+5VNuG6a\ntKGj/HQ1AoGBAPhhIxczFZxhd4qs6t09OkU2aEpGYcdMGEYFd7OUYhjUOhXHUMMC\nuUWAq2555VaUrKhlqovp+uTdMESop7Ac6gfSH0yG1rTLSiFCuOsNcBx1FKUgCh3Q\nOP0JugH6yOJuCwyT1DCoipGbq/sjhaUmb+sqwN+IhcQ6vCw4Qnp2emvfAoGBANy0\nzdG7PIL2i9Rub38+cR0/rFdZJFqWQXv/j1c+o+cAQfzsk6jPV5uR+St+HXyafMNu\nI7po1mvp7v1UJceFrynZGqdS0FNALmDAnySaPoyhqSjk+nB88MkSEcACr64Smumw\n5DHkU3hO+hhayCzPIsILiU67jXvIP3F5GabxNGUXAoGAGN+JZxJbkKyGDyIf5wXx\npuq66O1Bb3bkW2bCxP4QENJ3+qRaJx1HtnkbMdYNLG15GOgNezN0R6UK52VIXa+X\nlC7rqXs7VyGgi9IluIxA9OiYp6yctr2aZ2So9vfJVDuW6ayILFNEhS5ku6KkCJTf\n0loWtIv0cWE8ZOVBawggXFsCgYBWw612Ggl74rit1ox2lXGacgGqhRzJj/BGcv1C\n6xk8ItnOOKMD5h8mxYgTFQ06gvExUwcwrS4+VkThykbf3SozZWoZBXFoiP6ygocN\nuKWGW7dIoFvkBPoT0cqwlV3DLQVBgYz0IDLARSwEjwvKsdOUPTzJeunwE5T9YwI2\novRaGQKBgF0zysxImSvnC8jQFB5+jw5dCnP8gXDljZPDJhxwlkbNc85ikA27vSkQ\n+ql0YOsgka3DH4ISOpMGJXaCTkI86OXtLlG0HBjLgiiyYQnFieQ1xPB3PfCPKWHl\nG84TmLUw9uxC08jV0r7UfIDBxv7kNGDLJqyTmsCYON9EXbbt03ac\n-----END RSA PRIVATE KEY-----\n"

# 查看PEM格式
> ca.pem<-write_pem(cakey);ca.pem
[1] "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDWIuYl4kdh5W63\nDpSL5X+1mzZJLgYXNNZGShGaa3hn7mNAy392/3hZuOYrK802teI2IbPWNlCzDYGP\n7batIqMYSHhCKmOlxz4A5+X31Xd2Ifxgz9zCPp00Uqqqakke3MNJuE6509vAOwW6\nCrd3JWxXWEwquUQF60jtCP/yRlKJ7Uov5BLTQb4hIIdR6zx/94UeB3uwxek5vrk0\nmEApkZJ5Pj1X4h4XUzzZUkTJX7HWn8LemkAv09O3dEV+1NAmNdbjy0h9LdHzkTSW\nJvJ5CeXEzeyUR/ZSc7XrCr/uPcqZaI8P2+a0+eE2MpsGvo9u0BH4ICxKnFpUsykD\nU8PJ2qwJAgMBAAECggEBAMojbZArd2jRNN82s0EQJqsfVnJkMLmtVexKNS3I74Nk\nhGsA7lU9ubUwlo4ALhrMPTsOO34mDa3IPRrJshzjyoO8vVDwRNqUJ1KhlfOKndd1\nI5GcBm+SvcFLm/F8nYO1r9+iTSab+CARV0mUnjWprQkSv+vAsZbI0TF4wmtIuNea\n3hLd+Rk0nXaJwJEp114XJMvXssR/qUQ1eiV79Ja68HX5GFlvvd4lUw9MlyhFe6Un\nT+lQPZDEsyf9iG8xUfwDXe/1OKmZLnWbAs8U3wX0E2bYrpBceBAuCGNhukOTWlFP\nOP7hivp1tZmKkdJZYnLVYoyb3cz7lU24bpq0oaP8dDUCgYEA+GEjFzMVnGF3iqzq\n3T06RTZoSkZhx0wYRgV3s5RiGNQ6FcdQwwK5RYCrbnnlVpSsqGWqi+n65N0wRKin\nsBzqB9IfTIbWtMtKIUK46w1wHHUUpSAKHdA4/Qm6AfrI4m4LDJPUMKiKkZur+yOF\npSZv6yrA34iFxDq8LDhCenZ6a98CgYEA3LTN0bs8gvaL1G5vfz5xHT+sV1kkWpZB\ne/+PVz6j5wBB/OyTqM9Xm5H5K34dfJp8w24jumjWa+nu/VQlx4WvKdkap1LQU0Au\nYMCfJJo+jKGpKOT6cHzwyRIRwAKvrhKa6bDkMeRTeE76GFrILM8iwguJTruNe8g/\ncXkZpvE0ZRcCgYAY34lnEluQrIYPIh/nBfGm6rro7UFvduRbZsLE/hAQ0nf6pFon\nHUe2eRsx1g0sbXkY6A17M3RHpQrnZUhdr5eULuupeztXIaCL0iW4jED06JinrJy2\nvZpnZKj298lUO5bprIgsU0SFLmS7oqQIlN/SWha0i/RxYTxk5UFrCCBcWwKBgFbD\nrXYaCXviuK3WjHaVcZpyAaqFHMmP8EZy/ULrGTwi2c44owPmHybFiBMVDTqC8TFT\nBzCtLj5WROHKRt/dKjNlahkFcWiI/rKChw24pYZbt0igW+QE+hPRyrCVXcMtBUGB\njPQgMsBFLASPC8qx05Q9PMl66fATlP1jAjai9FoZAoGAXTPKzEiZK+cLyNAUHn6P\nDl0Kc/yBcOWNk8MmHHCWRs1zzmKQDbu9KRD6qXRg6yCRrcMfghI6kwYldoJOQjzo\n5e0uUbQcGMuCKLJhCcWJ5DXE8Hc98I8pYeUbzhOYtTD27ELTyNXSvtR8gMHG/uQ0\nYMsmrJOawJg430Rdtu3Tdpw=\n-----END PRIVATE KEY-----\n"

再以纯文本方式进行读取ca.key文件,并与PEM和pkcs1格式进行对比,发现key与pkcs1格式项目,这样就明确了ca.key是用pkcs1格式进行存储的。


# 以纯文本方式进行读取
> ca.txt<-readLines("./ca.key");ca.txt
 [1] "-----BEGIN RSA PRIVATE KEY-----"                                 
 [2] "MIIEowIBAAKCAQEA1iLmJeJHYeVutw6Ui+V/tZs2SS4GFzTWRkoRmmt4Z+5jQMt/"
 [3] "dv94WbjmKyvNNrXiNiGz1jZQsw2Bj+22rSKjGEh4Qipjpcc+AOfl99V3diH8YM/c"
 [4] "wj6dNFKqqmpJHtzDSbhOudPbwDsFugq3dyVsV1hMKrlEBetI7Qj/8kZSie1KL+QS"
 [5] "00G+ISCHUes8f/eFHgd7sMXpOb65NJhAKZGSeT49V+IeF1M82VJEyV+x1p/C3ppA"
 [6] "L9PTt3RFftTQJjXW48tIfS3R85E0libyeQnlxM3slEf2UnO16wq/7j3KmWiPD9vm"
 [7] "tPnhNjKbBr6PbtAR+CAsSpxaVLMpA1PDydqsCQIDAQABAoIBAQDKI22QK3do0TTf"
 [8] "NrNBECarH1ZyZDC5rVXsSjUtyO+DZIRrAO5VPbm1MJaOAC4azD07Djt+Jg2tyD0a"
 [9] "ybIc48qDvL1Q8ETalCdSoZXzip3XdSORnAZvkr3BS5vxfJ2Dta/fok0mm/ggEVdJ"
[10] "lJ41qa0JEr/rwLGWyNExeMJrSLjXmt4S3fkZNJ12icCRKddeFyTL17LEf6lENXol"
[11] "e/SWuvB1+RhZb73eJVMPTJcoRXulJ0/pUD2QxLMn/YhvMVH8A13v9TipmS51mwLP"
[12] "FN8F9BNm2K6QXHgQLghjYbpDk1pRTzj+4Yr6dbWZipHSWWJy1WKMm93M+5VNuG6a"
[13] "tKGj/HQ1AoGBAPhhIxczFZxhd4qs6t09OkU2aEpGYcdMGEYFd7OUYhjUOhXHUMMC"
[14] "uUWAq2555VaUrKhlqovp+uTdMESop7Ac6gfSH0yG1rTLSiFCuOsNcBx1FKUgCh3Q"
[15] "OP0JugH6yOJuCwyT1DCoipGbq/sjhaUmb+sqwN+IhcQ6vCw4Qnp2emvfAoGBANy0"
[16] "zdG7PIL2i9Rub38+cR0/rFdZJFqWQXv/j1c+o+cAQfzsk6jPV5uR+St+HXyafMNu"
[17] "I7po1mvp7v1UJceFrynZGqdS0FNALmDAnySaPoyhqSjk+nB88MkSEcACr64Smumw"
[18] "5DHkU3hO+hhayCzPIsILiU67jXvIP3F5GabxNGUXAoGAGN+JZxJbkKyGDyIf5wXx"
[19] "puq66O1Bb3bkW2bCxP4QENJ3+qRaJx1HtnkbMdYNLG15GOgNezN0R6UK52VIXa+X"
[20] "lC7rqXs7VyGgi9IluIxA9OiYp6yctr2aZ2So9vfJVDuW6ayILFNEhS5ku6KkCJTf"
[21] "0loWtIv0cWE8ZOVBawggXFsCgYBWw612Ggl74rit1ox2lXGacgGqhRzJj/BGcv1C"
[22] "6xk8ItnOOKMD5h8mxYgTFQ06gvExUwcwrS4+VkThykbf3SozZWoZBXFoiP6ygocN"
[23] "uKWGW7dIoFvkBPoT0cqwlV3DLQVBgYz0IDLARSwEjwvKsdOUPTzJeunwE5T9YwI2"
[24] "ovRaGQKBgF0zysxImSvnC8jQFB5+jw5dCnP8gXDljZPDJhxwlkbNc85ikA27vSkQ"
[25] "+ql0YOsgka3DH4ISOpMGJXaCTkI86OXtLlG0HBjLgiiyYQnFieQ1xPB3PfCPKWHl"
[26] "G84TmLUw9uxC08jV0r7UfIDBxv7kNGDLJqyTmsCYON9EXbbt03ac"            
[27] "-----END RSA PRIVATE KEY-----"      

# 比较纯文本格式和PEM格式,不相同
> identical(paste0(ca.txt,"\n",collapse = ""),ca.pem)
[1] FALSE

# 比较纯文本格式和pkcs1格式,相同
> identical(paste0(ca.txt,"\n",collapse = ""),ca.pkcs1)
[1] TRUE

3.2 取读ca.key的公钥并输出
在ca.key文件中,不仅包括了私钥信息还包括了公钥信息。有2个种方法,可以获取公钥信息。一是使用上面的私钥对象,取出公钥,二是使用read_pubkey()函数进行读取。

首先使用刚刚生成的cakey对象,来获取公钥。


> cakey$pubkey
[2048-bit rsa public key]
md5: 87be025db1f33785aafa00aa83388a8b
sha256: c03bc0d35b4593ff81cde4545b1aa62f80e754545297f30669bdcce29a910a45

再使用read_pubkey()函数,来获取公钥。


> cakey_pub<-read_pubkey("./ca.key");cakey_pub # public
[2048-bit rsa public key]
md5: 87be025db1f33785aafa00aa83388a8b
sha256: c03bc0d35b4593ff81cde4545b1aa62f80e754545297f30669bdcce29a910a45

# 比较2个结果,相同
> identical(cakey_pub,cakey$pubkey)
[1] TRUE

还没有公钥文件,我们可以输出这个文件,用pkcs1格式


# 输出公钥文件
> write_pkcs1(cakey_pub,"./ca_pub.key")

# 读取输出的公钥文件
> read_pubkey("./ca_pub.key")
[2048-bit rsa public key]
md5: 87be025db1f33785aafa00aa83388a8b
sha256: c03bc0d35b4593ff81cde4545b1aa62f80e754545297f30669bdcce29a910a45

3.3 取读ca.crt证书文件
除了能读取秘钥,还可以进行证书的读取。


# 读取证书文件
> file<-"ca.crt"
> cacrt<-read_cert(file, der = is.raw(file))

# 查看证书对象
> cacrt
[x509 certificate] myca.com
md5: 916c490f78b0f068a9ea6f7b0ded5bd5
sha1: be2eed543b4188fdc08bee8dfb0adf403e7437bf

# 查看证书链式对象 
> read_cert_bundle(file)
[[1]]
[x509 certificate] myca.com
md5: 916c490f78b0f068a9ea6f7b0ded5bd5
sha1: be2eed543b4188fdc08bee8dfb0adf403e7437bf

接下来,查看证书的文件存储格式,证书的文件格式是以PEM格式进行存储的。


# 以PEM格式查看证书内容
> cacrt.pem<-write_pem(cacrt);cacrt.pem
[1] "-----BEGIN CERTIFICATE-----\nMIID7TCCAtWgAwIBAgIUcV2+3ipz02Qccfqe5rT1sSY43LYwDQYJKoZIhvcNAQEL\nBQAwgYUxCzAJBgNVBAYTAlpIMQswCQYDVQQIDAJCSjEQMA4GA1UEBwwHQkVJSklO\nRzEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDDAht\neWNhLmNvbTEhMB8GCSqGSIb3DQEJARYSYnNzcGlyaXRAZ21haWwuY29tMB4XDTIy\nMTEzMDA5NDA0M1oXDTIzMTEzMDA5NDA0M1owgYUxCzAJBgNVBAYTAlpIMQswCQYD\nVQQIDAJCSjEQMA4GA1UEBwwHQkVJSklORzEhMB8GA1UECgwYSW50ZXJuZXQgV2lk\nZ2l0cyBQdHkgTHRkMREwDwYDVQQDDAhteWNhLmNvbTEhMB8GCSqGSIb3DQEJARYS\nYnNzcGlyaXRAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC\nAQEA1iLmJeJHYeVutw6Ui+V/tZs2SS4GFzTWRkoRmmt4Z+5jQMt/dv94WbjmKyvN\nNrXiNiGz1jZQsw2Bj+22rSKjGEh4Qipjpcc+AOfl99V3diH8YM/cwj6dNFKqqmpJ\nHtzDSbhOudPbwDsFugq3dyVsV1hMKrlEBetI7Qj/8kZSie1KL+QS00G+ISCHUes8\nf/eFHgd7sMXpOb65NJhAKZGSeT49V+IeF1M82VJEyV+x1p/C3ppAL9PTt3RFftTQ\nJjXW48tIfS3R85E0libyeQnlxM3slEf2UnO16wq/7j3KmWiPD9vmtPnhNjKbBr6P\nbtAR+CAsSpxaVLMpA1PDydqsCQIDAQABo1MwUTAdBgNVHQ4EFgQU55Mc/d8s7WlF\n0HaorBq64rcFJXIwHwYDVR0jBBgwFoAU55Mc/d8s7WlF0HaorBq64rcFJXIwDwYD\nVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAP6Vfs0SX8035Qo3nccOf\niemkfsMoH8Jme7TNT8sv19hxdVhtHw5pgPxsJfb5Upwp5RjQogdruPBXW86MVgSh\nxYptVH94L4QIPZFLvuPzTE1j03/44jm1mwvOGu7jf27Bb683Ltp6MoL9yoJylEYl\nFwYaowelmZnkAFArdy/QG/tgMuW3wh1ThACg1ZKDcnw+ncX5oAzJIa7ydAIUjyJO\nQpJvtrPfV23bGds3swICxZIPNLE7iAN5z9br1DZNJ+GoMrgCHlrvPilagY/LKzWu\nq9+IwP6mVY7egc+os7FQKQ3nFbGBBAE41m9HKP6Tw8kUhK8HcaViuFHbUOeftv8F\nhQ==\n-----END CERTIFICATE-----\n"

# 以pkcs1格式查看证书内容,报错。
> cacrt.pkcs1<-write_pkcs1(cacrt);cacrt.pkcs1
Error in write_pkcs1(cacrt) : PKCS1 pubkey format only supports RSA keys

# 以纯文件的方式读取证书文件
> cacrt.txt<-readLines(file);cacrt.txt
 [1] "-----BEGIN CERTIFICATE-----"                                     
 [2] "MIID7TCCAtWgAwIBAgIUcV2+3ipz02Qccfqe5rT1sSY43LYwDQYJKoZIhvcNAQEL"
 [3] "BQAwgYUxCzAJBgNVBAYTAlpIMQswCQYDVQQIDAJCSjEQMA4GA1UEBwwHQkVJSklO"
 [4] "RzEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDDAht"
 [5] "eWNhLmNvbTEhMB8GCSqGSIb3DQEJARYSYnNzcGlyaXRAZ21haWwuY29tMB4XDTIy"
 [6] "MTEzMDA5NDA0M1oXDTIzMTEzMDA5NDA0M1owgYUxCzAJBgNVBAYTAlpIMQswCQYD"
 [7] "VQQIDAJCSjEQMA4GA1UEBwwHQkVJSklORzEhMB8GA1UECgwYSW50ZXJuZXQgV2lk"
 [8] "Z2l0cyBQdHkgTHRkMREwDwYDVQQDDAhteWNhLmNvbTEhMB8GCSqGSIb3DQEJARYS"
 [9] "YnNzcGlyaXRAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC"
[10] "AQEA1iLmJeJHYeVutw6Ui+V/tZs2SS4GFzTWRkoRmmt4Z+5jQMt/dv94WbjmKyvN"
[11] "NrXiNiGz1jZQsw2Bj+22rSKjGEh4Qipjpcc+AOfl99V3diH8YM/cwj6dNFKqqmpJ"
[12] "HtzDSbhOudPbwDsFugq3dyVsV1hMKrlEBetI7Qj/8kZSie1KL+QS00G+ISCHUes8"
[13] "f/eFHgd7sMXpOb65NJhAKZGSeT49V+IeF1M82VJEyV+x1p/C3ppAL9PTt3RFftTQ"
[14] "JjXW48tIfS3R85E0libyeQnlxM3slEf2UnO16wq/7j3KmWiPD9vmtPnhNjKbBr6P"
[15] "btAR+CAsSpxaVLMpA1PDydqsCQIDAQABo1MwUTAdBgNVHQ4EFgQU55Mc/d8s7WlF"
[16] "0HaorBq64rcFJXIwHwYDVR0jBBgwFoAU55Mc/d8s7WlF0HaorBq64rcFJXIwDwYD"
[17] "VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAP6Vfs0SX8035Qo3nccOf"
[18] "iemkfsMoH8Jme7TNT8sv19hxdVhtHw5pgPxsJfb5Upwp5RjQogdruPBXW86MVgSh"
[19] "xYptVH94L4QIPZFLvuPzTE1j03/44jm1mwvOGu7jf27Bb683Ltp6MoL9yoJylEYl"
[20] "FwYaowelmZnkAFArdy/QG/tgMuW3wh1ThACg1ZKDcnw+ncX5oAzJIa7ydAIUjyJO"
[21] "QpJvtrPfV23bGds3swICxZIPNLE7iAN5z9br1DZNJ+GoMrgCHlrvPilagY/LKzWu"
[22] "q9+IwP6mVY7egc+os7FQKQ3nFbGBBAE41m9HKP6Tw8kUhK8HcaViuFHbUOeftv8F"
[23] "hQ=="                                                            
[24] "-----END CERTIFICATE-----"                                       

# 验证纯文本的证书与PEM格式,相同。
> identical(paste0(cacrt.txt,"\n",collapse = ""),cacrt.pem)
[1] TRUE

最后,可以把证书重写到一个新文件中,再读取,可得到完成一样的证书数据。


> write_pem(cacrt,"cacrt.crt.bak")
> read_cert("cacrt.crt.bak")
[x509 certificate] myca.com
md5: 916c490f78b0f068a9ea6f7b0ded5bd5
sha1: be2eed543b4188fdc08bee8dfb0adf403e7437bf

4. 获取网站证书并验证

如果我们想让自己的网站支持HTTPS,实现SSL/TLS协议时就需要用到证书;或者当我们想配置github的git的自动权限访问时,也需要使用用到证书;当我们在使用Linux,想进行无密码登陆时,这时也可以用证书。

利用openssl工具,我们可以获得各种网站的证书,查看网站的证书的相关信息,数字签名的信息,通过HTTPS传输的加密算法等信息,最后可以验证证书是否是合法的。

4.1 在浏览器网站证书
我们访问R的官方网站,https://www.r-project.org/,网站是HTTPS的,我们可以手动查看网站的证书。在证书中的,证书颁发者,SHA1,SHA256,有效期,公钥等的信息。

我们可以手动下载证书文件,在本地进行打开。

4.2 用R语言来下载证书
接下来,我们可以用R语言的程序,来获取证书信息,


# 证书下载
> cert <- download_ssl_cert("www.r-project.org")

查看当前证书,上级证书,一直到root证书。


> cert
[[1]]
[x509 certificate] *.r-project.org
md5: 11a9fee5a83206c063a203c97f71481b
sha1: 03c310e4971981154ada3a5df46206dadfb82172

[[2]]
[x509 certificate] Sectigo RSA Domain Validation Secure Server CA
md5: adab5c4df031fb9299f71ada7e18f613
sha1: 33e4e80807204c2b6182a3a14b591acd25b5f0db

[[3]]
[x509 certificate] USERTrust RSA Certification Authority
md5: 285ec909c4ab0d2d57f5086b225799aa
sha1: d89e3bd43d5d909b47a18977aa9d5ce36cee184c

4.3 查看当前证书的详细

取当前证书,看证书的信息。当前证书允许的域名。


# 取当前证书
> cert_data<-cert[[1]]

# 允许域名
> cert_data$alt_names
[1] "*.r-project.org" "r-project.org"  

# 查看cert_data的数据结构
> str(cert_data)
List of 8
 $ subject    : chr "CN=*.r-project.org"
 $ issuer     : chr "CN=Sectigo RSA Domain Validation Secure Server CA,O=Sectigo Limited,L=Salford,ST=Greater Manchester,C=GB"
 $ algorithm  : chr "sha256WithRSAEncryption"
 $ signature  : raw [1:256] b1 2f f9 a5 ...
 $ validity   : chr [1:2] "Oct 23 00:00:00 2022 GMT" "Nov 23 23:59:59 2023 GMT"
 $ self_signed: logi FALSE
 $ alt_names  : chr [1:2] "*.r-project.org" "r-project.org"
 $ pubkey     :List of 5
  ..$ type       : chr "rsa"
  ..$ size       : int 2048
  ..$ ssh        : chr "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQ/cx8HyMW9SCLJsC/UKaTh/RskQ33Leicy0prlBc2Dm3Tk/m1Dn7cpwGZ ..."
  ..$ fingerprint: 'hash' raw [1:32] a7 68 11 fb ...
  ..$ data       :List of 2
  .. ..$ e: 'bignum' raw [1:3] 01 00 01
  .. ..$ n: 'bignum' raw [1:257] 00 d0 fd cc ...

4.4 查看公钥信息
查看公钥信息,使用了RSA的2048位加密,查看公钥的二进制格式,就与网上在看到的公钥内容是一致的。


# 查看公钥为2048位的RSA加密
> cert_data$pubkey
[2048-bit rsa public key]
md5: 6b372d9b5c5759fbb28baf03c609730a
sha256: a76811fbe1608aeeb74f077b63e07c8fea82fdf4789c01c7b5b975a8958eb12b

# 公钥的ssh码
> cert_data$pubkey$ssh
[1] "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQ/cx8HyMW9SCLJsC/UKaTh/RskQ33Leicy0prlBc2Dm3Tk/m1Dn7cpwGZQrsS4ak1IaV5WroeJjWUzEiaJH5Ky4vJOGWJYXoXw5THDFxraaCk4CPd/cQuFkezY6iuEYORuNYoukV9xgXhfaGJN4K8Gan6Hi8syY6HqnOiSt887GUZckO7TQF3RishPlbLzx0G17inlhigDFtoFenG/x043EwkIFJ6pz1jA8h/Us29Rw1+853AwNZ/nOptaUFku6W3uXgf4TLYUn6NzPSKY6lJ34gpSoBVQIIU8hjz2Y3cwlIzgQnrw2qlzDmFmiZtjPTaMrvBzs5QHfsgOBHvc5j/"

# 查看公钥的二进制格式
> write_der(cert_data$pubkey)
  [1] 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a
 [29] 02 82 01 01 00 d0 fd cc 7c 1f 23 16 f5 20 8b 26 c0 bf 50 a6 93 87 f4 6c 91 0d f7 2d
 [57] e8 9c cb 4a 6b 94 17 36 0e 6d d3 93 f9 b5 0e 7e dc a7 01 99 42 bb 12 e1 a9 35 21 a5
 [85] 79 5a ba 1e 26 35 94 cc 48 9a 24 7e 4a cb 8b c9 38 65 89 61 7a 17 c3 94 c7 0c 5c 6b
[113] 69 a0 a4 e0 23 dd fd c4 2e 16 47 b3 63 a8 ae 11 83 91 b8 d6 28 ba 45 7d c6 05 e1 7d
[141] a1 89 37 82 bc 19 a9 fa 1e 2f 2c c9 8e 87 aa 73 a2 4a df 3c ec 65 19 72 43 bb 4d 01
[169] 77 46 2b 21 3e 56 cb cf 1d 06 d7 b8 a7 96 18 a0 0c 5b 68 15 e9 c6 ff 1d 38 dc 4c 24
[197] 20 52 7a a7 3d 63 03 c8 7f 52 cd bd 47 0d 7e f3 9d c0 c0 d6 7f 9c ea 6d 69 41 64 bb
[225] a5 b7 b9 78 1f e1 32 d8 52 7e 8d cc f4 8a 63 a9 49 df 88 29 4a 80 55 40 82 14 f2 18
[253] f3 d9 8d dc c2 52 33 81 09 eb c3 6a a5 cc 39 85 9a 26 6d 8c f4 da 32 bb c1 ce ce 50
[281] 1d fb 20 38 11 ef 73 98 ff 02 03 01 00 01

# 查看公钥数据
> cert_data$pubkey$data
$e
[b] 65537
$n
[b] 26382720270417477837617835499791330099049934775392664891485281047561411831161618531639849698257940022133596595793360566082157334663831597661390020187366330377887609444158710396573956877965613421020593308923784748020878445065836239835939589641288489663966645774920702533024654777130568392213769053338143063817255841711707909827551784702236553682744092755623610745991474611454202317512454804265930433899882101055065400009265523412830694180236095124621448935253985091450512509725554745453130660854770715808981111924477167623114187044856057832860089329518157485613424693402698364749754164433810264444699764245619172153599

4.5 验证证书是否真实有效
验证证书是否真实有效,ca_bundle()函数可以获得所有root证书的机构,通过比对当前证书的root机构与登记的机构比对,从而判断当前证书的真实性。


# 验证证书是否真实有效
> cert_verify(cert, ca_bundle())
[1] TRUE

# 查看root证书的机构的前6条
> head(ca_bundle())
[[1]]
[x509 certificate] GlobalSign Root CA
md5: 3e455215095192e1b75d379fb187298a
sha1: b1bc968bd4f49d622aa89a81f2150152a41d829c

[[2]]
[x509 certificate] GlobalSign
md5: 9414777e3e5efd8f30bd41b0cfe7d030
sha1: 75e0abb6138512271c04f85fddde38e4b7242efe

[[3]]
[x509 certificate] VeriSign Class 3 Public Primary Certification Authority - G3
md5: cd68b6a7c7c4ce75e01d4f5744619209
sha1: 132d0d45534b6997cdb2d5c339e25576609b5cc6

[[4]]
[x509 certificate] Entrust.net Certification Authority (2048)
md5: ee2931bc327e9ae6e8b5f751b4347190
sha1: 503006091d97d4f5ae39f7cbe7927d7d652d3431

[[5]]
[x509 certificate] Baltimore CyberTrust Root
md5: acb694a59c17e0d791529bb19706a6e4
sha1: d4de20d05e66fc53fe1a50882c78db2852cae474

[[6]]
[x509 certificate] AddTrust External CA Root
md5: 1d3554048578b03f42424dbf20730a3f
sha1: 02faf3e291435468607857694df5e45b68851868

4.6 导出密钥或证书
使用write_pem() 函数将钥匙或证书导出为标准的base64 PEM格式。对于私钥,可以设置一个密码。


# 查看证书的密文
> write_pem(cert_data)
[1] "-----BEGIN CERTIFICATE-----\nMIIGNzCCBR+gAwIBAgIQVDDlPXe+N3mjxP1zcGBexzANBgkqhkiG9w0BAQsFADCB\njzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\nA1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD\nEy5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB\nMB4XDTIyMTAyMzAwMDAwMFoXDTIzMTEyMzIzNTk1OVowGjEYMBYGA1UEAwwPKi5y\nLXByb2plY3Qub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0P3M\nfB8jFvUgiybAv1Cmk4f0bJEN9y3onMtKa5QXNg5t05P5tQ5+3KcBmUK7EuGpNSGl\neVq6HiY1lMxImiR+SsuLyThliWF6F8OUxwxca2mgpOAj3f3ELhZHs2OorhGDkbjW\nKLpFfcYF4X2hiTeCvBmp+h4vLMmOh6pzokrfPOxlGXJDu00Bd0YrIT5Wy88dBte4\np5YYoAxbaBXpxv8dONxMJCBSeqc9YwPIf1LNvUcNfvOdwMDWf5zqbWlBZLult7l4\nH+Ey2FJ+jcz0imOpSd+IKUqAVUCCFPIY89mN3MJSM4EJ68Nqpcw5hZombYz02jK7\nwc7OUB37IDgR73OY/wIDAQABo4IDATCCAv0wHwYDVR0jBBgwFoAUjYxexFStiuF3\n6Zv5mwXhuAGNYeEwHQYDVR0OBBYEFAnIMghgCgq5FuZgZW2gz333Rr0yMA4GA1Ud\nDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr\nBgEFBQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEW\nF2h0dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEE\neDB2ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29S\nU0FEb21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzAB\nhhdodHRwOi8vb2NzcC5zZWN0aWdvLmNvbTApBgNVHREEIjAggg8qLnItcHJvamVj\ndC5vcmeCDXItcHJvamVjdC5vcmcwggF/BgorBgEEAdZ5AgQCBIIBbwSCAWsBaQB3\nAK33vvp8/xDIi509nB4+GGq0Zyldz7EMJMqFhjTr3IKKAAABhAKZWJgAAAQDAEgw\nRgIhAJdfvjFXbRU053wGkFTCa6M/FZ9AQXpIWKw3XGmtclmPAiEA6FRxqIJ2qam2\naLj5lvj/YuiD+ohMvaeRL78TlAt1dSoAdgB6MoxU2LcttiDqOOBSHumEFnAyE4VN\nO9IrwTpXo1LrUgAAAYQCmViyAAAEAwBHMEUCIQCWAMENLlPBh73qMoqix/AIwr/g\niLrKwc4yKkcXNY49bQIgQWjhgFm/o/QL3NuoyaY5Gs0J7BWjJr4AHQn14cFmKAAA\ndgDoPtDaPvUGNTLnVyi8iWvJA9PL0RFr7Otp4Xd9bQa9bgAAAYQCmVh7AAAEAwBH\nMEUCIQCV0vEGrO3f/pH9ew1fNwFE40nrB17r/8XAwyiWI5cg7gIgZ3RQ3PKI1zFq\npiW86XgKgU9EnW1GjNzKJfxb4SMPuJkwDQYJKoZIhvcNAQELBQADggEBALEv+aVn\ndC0VLCw+CoBYt5cpWkwPfKpWewAC9fw2Q9womhmrN9+C51kX0JZpEE2V1oG5lahp\n5fUi5XwZRT+/1wNIz5GDLMbumY+m7dQp6NE+XApYSd/J+HnLoKAuVI+YosOtHhF/\n07cMNogiVhEldeOweo8Z8YxCmb/EzSY30wdHNNASf5s2TQ0tlZ1NzKTzt/gpOXDp\njgQJE4zXamym3qNGDumhbEWKozQqLVo2OOYgak8OkeDzar48MQdc3vvtO0B1heRh\nvZ2FezAFWDfHcldXJK2W/DOpprE+oIRjlYKztXG3r/aYpu59vMsAIhGWuC6Vg2Yx\nk8Umv/fhAjEe/xc=\n-----END CERTIFICATE-----\n"

本文尝试了使用openssl 的命令行工具,和R语言中的openssl包的秘钥和x509证书管理的函数,进行了秘钥生成,证书生成,证书管理,证书下载,证书验证等的功能尝试。里面有不少的密码学的内容,是需要我们先理解后再进行操作的。

唯一遗憾的就是不能直接用R语言进行证书的生成。openssl博大精深,一个包支持非常多的密码学的内容。

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

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

打赏作者

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/

打赏作者