非对称加密
对称加密有两个属性:
- 加密和解密使用相同的密钥。
- 加密函数和解密函数非常类似。
与此同时带来的缺点有:
- 密钥分配问题,发送方和接收方必须使用一个安全的信道建立密钥,因为所有的通信链路都是不安全的,所以密钥分配存在安全隐患。
- 密钥个数,在拥有n个用户的网络中,如果每个用户之间都需要一个单独的密钥对,则整个网络需要的密钥对数是: n(n-1)/2,且每个用户都需要安全的存储n-1个密钥。比如拥有2000名员工的公司,也需要生成近400万个密钥对,并且每个密钥对都必须通过安全信道进行传输。
- 对发送方或者接收方的欺骗没有防御机制。简而言之,就是不能防伪。
非对称加密是为了克服对称加密的这些缺点应运而生的。非对称加密就是发送方加密数据的密钥不需要保密,而接收方解密数据的密钥不能被公开,即双方使用两个不同的密钥。
如果使用非对称加密来加密对称加密的密钥,则对称加密的密钥即可实现安全传输。
非对称加密的特点
- 密钥建立,在不安全信道上建立密钥,如对称密钥传输
- 不可否认性,可以通过数字签名实现不可否认性和消息完整性
- 身份标示,使用质询-响应协议与数字签名相结合的方法识别实体
- 加密,对消息进行加密
- 执行加密操作时需要进行的计算量大,加密过程很慢(相比对称加密)
公钥同样存在问题,即公钥的可靠性,这通常通过证书解决。
非对称加密算法
1. 整数分解方案: RSA加密
2. 离散对数方案: Elgamal加密或DSA数字签名
3. 椭圆曲线(EC)方案: ECDSA数字签名
RSA加密
RSA的应用
- 数据小片段的传输,尤其用于密钥传输
- 数字签名
RSA通常与类似AES的对称密码一起使用,其中真正用来加密大量数据的是对称加密。RSA的原理就是整数因式分解问题:两个大素数相乘在计算上是非常简单的,但是对乘积结果进行因式分解却是非常困难的。
关于RSA加密原理的具体实现有很多书籍有很细致的讲解,本文参考《深入浅出密码学——常用加密技术原理与应用》。
算法实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104const (
PKCSLevel1 pKCSLevel = iota + 1 //PKCS#1
PKCSLevel8 //PKCS#8
)
const (
RSAEncryptTypeOAEP rSAEncryptType = iota + 1 //使用RSA-OAEP算法加密, 推荐使用
RSAEncryptTypePKCS1v15 //使用PKCS#1 v1.5规定的填充方案和RSA算法加密,加密的数据量有限
)
type (
pKCSLevel uint //PKCS标准类型, 用于生成密钥文件
rSAEncryptType uint //RSA加密算法类型, 用于加密、解密
)
//生成RSA密钥对
// 参数解析:
// bits: 密钥的长度
// saveDir: 密钥文件的保存目录
// pkcsLevel: 公钥规范: PKCS1(PKCS#1) 和PKCS8(PKCS#8)
func GenerateRSAKey(bits int, saveDir string, pkcsLevel pKCSLevel) (privateFile, publicFile string, err error) {
//1. 生成RSA密钥对
//2. 将私钥对象转换为DER编码形式
//3. 创建私钥(公钥)文件
//4. 对密钥信息进行pem编码并写入私钥文件中
privateData, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return
}
var privateBytes, publicBytes []byte
switch pkcsLevel {
case PKCSLevel1:
privateBytes = x509.MarshalPKCS1PrivateKey(privateData)
publicBytes = x509.MarshalPKCS1PublicKey(&privateData.PublicKey)
case PKCSLevel8:
privateBytes, err = x509.MarshalPKCS8PrivateKey(privateData)
if err != nil {
return
}
publicBytes, err = x509.MarshalPKIXPublicKey(&privateData.PublicKey)
if err != nil {
return
}
default:
err = pkg.ErrInvalidPKCSLevel
return
}
privatePath := path.Join(saveDir, "private.pem")
file, err := os.Create(privatePath)
if err != nil {
return
}
defer file.Close()
var block = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateBytes,
}
if err = pem.Encode(file, block); err != nil {
return
}
//创建公钥
publicPath := path.Join(saveDir, "public.pem")
file, err = os.Create(publicPath)
if err != nil {
return
}
block = &pem.Block{
Type: "PUBLIC KEY",
Bytes: publicBytes,
}
if err = pem.Encode(file, block); err != nil {
return
}
return privatePath, publicPath, nil
}
//公钥加密
func RSAEncrypt(originalData []byte, privateKey *rsa.PrivateKey, rsaType rSAEncryptType, label []byte) (encryptData []byte, err error) {
switch rsaType {
case RSAEncryptTypeOAEP:
encryptData, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, &privateKey.PublicKey, originalData, label)
case RSAEncryptTypePKCS1v15:
encryptData, err = rsa.EncryptPKCS1v15(rand.Reader, &privateKey.PublicKey, originalData)
}
return
}
//私钥解密
func RSADecryptBytes(encryptData []byte, privateKey *rsa.PrivateKey, rsaType rSAEncryptType, label []byte) (originalData []byte, err error) {
switch rsaType {
case RSAEncryptTypeOAEP:
originalData, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, encryptData, label)
case RSAEncryptTypePKCS1v15:
originalData, err = rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptData)
}
return
}
数字签名
在非对称加密中,通过公钥加密,私钥解密的方式叫做数据加密,即非对称加密的本职。无论公钥还是私钥,都是非对称加密的密钥,那么如果将两者的作用交换一下,私钥加密,公钥解密,同样也可以对数据进行加密,但在加密学中将这种方式称之为数字签名。
数字签名存在的目的是为了防止中间人篡改消息,因为加密数据的传输通道为通信链路,而通信链路并非绝对安全,甚至是不安全的,这样就会出现:在传输的过程中,可能会存在中间人篡改加密消息以伪造成发送方,从而达到窃取数据的目的。而数字签名则可以规避这一点,因为数字签名是通过非对称加密中的私钥来加密,公钥解密。
在数字签名过程中,通常有两部分部分组成:
- 私钥加密的密文数据(对明文摘要加密)
- 明文
但是通常我们加密的明文数据比较大(超过加密算法限定的长度),此时我们使用哈希算法(下一篇将介绍哈希)来对加密明文数据(因为哈希的结果为定长,且不可逆,所以哈希加密过程称之为摘要生成,加密结果即为摘要,也称消息指纹),然后通过加密算法对摘要进行加密得到发送给接收方的密文并发送出去。
接收方收到签名数据后,首先用公钥对密文进行解密,得到消息摘要,然后通过双方商定的Hash方式加密接收到的明文,再将结果与收到的摘要对比,如果一致则证明数据有效,否则数据无效。
签名生成和验证签名的整个过程都是作用于消息摘要(即Hash值),而不是消息本身。
RSA数字签名
1 | //签名 |
ECC(椭圆曲线)数字签名
1 | //生成密钥 |
DSA数字签名
1 | func GenerateDSAKey(size dsa.ParameterSizes) (privateKey *dsa.PrivateKey, err error) { |
相关资料
最后
如有错误,欢迎指正!
Thanks!