数据加密汇总

随机数

先从简单的随机算法说起,真正的随机数应该是不可预测的。

伪随机数

  rand(),我们用的最多伪随机算法。在做负载均衡时,我们经常会random到某台机器上进行服务,这无伤大雅,但是当要对敏感信息加密时,我们random出一个秘钥因子时,这就会有很大风险被破解。因为rand是通过编程算法得出的,是可预测的,如果将random出的数字转换为黑白像素,可以看出他是有规律的。

伪随机

我们用下面的一段小程序可以发现,伪随机确实是有规律的。这段程序不管执行多少次,每次执行得到的10个随机数是一模一样。

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(void)
{
int i = 0;
for(i = 0; i < 10; i++){
srand(i);
printf("%d\n",rand());
}
return 0;
}

无随机种子
rand的随机序列完全依赖于随机种子,就是说如果我知道了你的随机种子,使用rand函数就可以得到和你一模一样的随机序列。因此,如果A同学使用当前时间srand作为随机种子,生成随机数,加密了一段数据,B同学截获了这段数据,B同学只需要根据当前时间取前后n分钟的时间作为随机种子,经过(n*60)次碰撞就解密了A的数据,解出来不要太快。

真随机数

  真随机数,就是无法预测的随机数,它是利用当前系统的熵池来计算出固定一定数量的随机比特,然后将这些比特作为字节流返回。(熵池就是当前系统的环境噪音,熵指的是一个系统的混乱程度,系统噪音可以通过很多参数来评估,如内存的使用,文件的使用量,不同类型的进程数量等等),真随机数可用于密码学。
  /dev/random和/dev/urandom是Linux系统中提供的随机设备,两者有啥区别:
/dev/random依赖于系统中断,系统的中断数不足时,尝试读取的进程就会进入等待状态
/dev/urandom不依赖系统的中断,也就不会造成进程忙等待,数据的随机性不如/dev/random高,但是它不会阻塞,用它就足够

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>

int main(void)
{
int i = 0;
unsigned long seed;
int fd = open("/dev/urandom", 0);
for(i = 0; i < 10; i++)
{
if (fd < 0 || read(fd, &seed, sizeof(seed)) < 0)
return -1;
srand(seed);
printf("%d\n",rand());
}
close(fd);
return 0;
}

再使用上面的代码进行测试,每次运行都会获得不一样的随机序列,随机序列无法预测
真随机种子
不同语言下的真随机数:

消息摘要(单向加密)

消息摘要又有数字摘要、单向散列、哈希函数等名称,典型的有md5,sha系列算法。他有以下算法特征:

  • 输入一样,输出必然相同;
  • 雪崩效应,输入的微小改变,将会引起结果的巨大变化;
  • 定长输出,无论原始数据多大,结果大小都是相同的;
  • 不可逆,无法根据特征码还原原来的数据;

  因此,它可用作单向加密,又称为不可逆加密算法。这种算法一般用作数据库中用户密码的加密,因为他的加密是不可逆的,你很难根据密码的哈希值推算出密码。

  用户一般设置密码一般都很懒,所有的应用可能使用的是同一个用户名和密码,如果数据库中存放的是单向加密后的数据,那么泄露的影响将会大大降低。最为典型的案例,就是csdn和12306的用户密码的泄露,如果他的密码经过单向加密,无论是网站的数据库管理员还是获取了密码数据的黑客,能看到的只是一长串毫无意义的字符,很难将它还原成真正的密码。

单向加密破解

虽然单向加密是不可逆的,但是花费一定的代价仍然有破解的方法。

  1. 暴力破解
    用穷举法组合出所有的密码可能,然后经哈希机密算法计算,将结果与哈希串进行比对它需要大量的计算,因此破解速度非常慢,以14位字母和数字的组合密码为例,共有1.24×10^25种可能,即使电脑每秒钟能进行10亿次运算,也需要4亿年才能破解;
  2. 数据字典
    提前生成可能密码与对应哈希串的对照表,密码攻击时直接根据哈希串从对照表中查询对应的密码。需要海量的磁盘空间来储存数据,以14位字母和数字的组合密码为例,生成的密码32位哈希串的对照表将占用5.7×10^14 TB的存储空间。

彩虹表

  上面的两种破解方法都很蠢,第一种要花费大量的时间,第二种要花费大量的空间,而彩虹表则是两者的折中。它将耗时和占用空间控制在可接受的范围内。可以在一定的空间内存储更多的哈希值,从而使攻击更加有效。
  下图中“7位密码、全部字符集、哈希链长度为2万”的彩虹表大小为32G,本地生成大约需要332天,而从网上下载只需要2个小时左右,破解要48分钟,主流的彩虹表的大小普遍在100G以上。就是说根据你的单向加密的密文,我只需要从网上下载彩虹表,查表48分钟就可以破解了。

  彩虹表是如何实现的呢?(https://baijiahao.baidu.com/s?id=1611935212672798027&wfr=spider&for=pc!)
  彩虹表的原理和数据字典的原理有些相像,数据字典中存放的是“明文,密文”这样的数据对,而彩虹表就是将这些数据对用H和R两个函数串起来,我只需要将哈希链的首尾元素p0和pn做为一个数对存入表中,中间的其它元素全部都可以通过计算获取这样就大大的减少存储空间。

  这个哈希链中p是明文,q是哈希串,H是加密使用的hash函数(md5,sha),R是规约函数。比较难理解的就是R函数了,它其实并没有特殊的含义,他的意义只是将上一个数对的末尾字符串(密文)和下一个数对的首字符串(明文)串起来,尽可能多的覆盖数据字典中的数对。
  知道了彩虹表中存放的是(P0,Pn)这样的数对,怎么破解?比如一个密文A,要得到他的明文,则先和表中的Pn进行匹配,匹配成功了,则明文是就是P(n-1),我们将P0经过H,R进行多次计算就可以得到P(n-1)。如果没有匹配成功,计算B=R(H(A)),再用B和表中的Pn进行匹配,匹配成功则A的明文就是P(n-2)。如果还没匹配成功,则继续计算C=R(H(B)),继续匹配,以此类推。
  彩虹表的关键是构造R函数,优秀的R函数要保证计算结果均匀分布,即避免出现相同的明文密码。然而想构造优秀的R函数是件非常困难的事,不同的哈希链中可能会出现大量的重复数据,严重影响了密码攻击的效率。改良后的彩虹表在哈希链的计算过程中引入不同的R函数,有效减少不同哈希链中的重复节点,进一步提高了攻击效率。如果将不同的R函数用不同的颜色表示,众多的哈希链就会像彩虹一样,从里到外呈现出颜色变化,这就是彩虹表名称的由来。

加盐

  通过彩虹表可以看出,一个7位的字母密码的md5,通过彩虹表7秒钟就破解出你的明文。可见在数据库中存放密码的md5,还不够安全。
  防御彩虹表最有效的方法就是“加盐”,即在密码的特定位置插入特定的字符串,这个特定字符串就是“盐”,加盐后的密码经过哈希加密得到的哈希串与加盐前的哈希串完全不同,黑客用彩虹表得到的密码根本就不是真正的密码。即使黑客知道了“盐”的内容、加盐的位置,还需要对H函数和R函数进行修改,彩虹表也需要重新生成,因此加盐能大大增加利用彩虹表攻击的难度。
  可以为每个密码随机生成盐值,在进行加密,就算黑客要破解,他也要为每个盐值重新生成彩虹表,代价很高。

1
2
MD5('123' + '1ck12b13k1jmjxrg1h0129h2lj') = '6c22ef52be70e11b6f3bcf0f672c96ce'
MD5('456' + '1h029kh2lj11jmjxrg13k1c12b') = '7128f587d88d6686974d6ef57c193628'

PBKDF2算法(Password-Based Key Derivation Function 2)

  这个算法是专门用来做密码散列和秘钥导出的。高端的显卡(GPU)和定制的硬件可以每秒进行数十亿次哈希计算,因此字典攻击或暴力攻击依然可以很高效。为了降低攻击者的效率,我们可以使加密变慢。美国政府机构已经将这个方法标准化,并且用于一些政府和军方的系统,这个方案最大的优点是标准化,实现容易同时采用了久经考验的SHA算法。
  该算法原理大致相当于在Hash算法基础上增加盐值,并进行多次Hash运算,随机盐和多次Hash使得建表和破解的难度都大幅增加,多次加密的目的就是让加密变慢。比如,一次hash时间是10us,使用PBKDF2算法迭代1万次,那么单次加密的时间将扩大1w倍,建彩虹表的时间扩为原来的1w倍,代价很高。
  这个是详细的算法流程(https://segmentfault.com/a/1190000004261009)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"golang.org/x/crypto/pbkdf2"
)

func main(){
pwd := []byte("tencent")
salt := []byte("1h029kh2lj11jmjxrg13k1c12b")

dk := pbkdf2.Key(pwd, salt, 15000, 32, sha256.New)
str := base64.StdEncoding.EncodeToString(dk)
fmt.Println(str)
}

这是个golang版本的PBKDF2例子,单向加密的耗时和迭代次数相关,这个例子迭代了15000次。
  通过以上单向加密,了解到数据库中千万不要保存密码明文。现在很多系统中,已经没有“找回密码”这个功能了,因为服务端真的不知道你的密码,你只能选择“忘记密码”,然后重置。

对称加密

  单向加密最为明显的一个特征就是不可逆,如果数据有解密还原的需求,那就可以使用对称加密。采用秘钥加密密码的方法,同一个密钥可以同时用来加密和解密,这种加密方法称为对称加密,也是双向加密,双向加密又称为可逆加密,即生成密文后,在需要的时候可以反解为明文。

DES加密

  1977年1月,美国政府颁布:采纳IBM公司设计的方案作为非机密数据的正式数据加密标准(DESData Encryption Standard) 。在通信网络的两端,双方约定一致的Key,在通信的源点用Key对核心数据进行DES加密,然后以密码形式在公共通信网中传输到通信网络的终点,数据到达目的地后,用同样的Key对密码数据进行解密,便再现了明码形式的核心数据。这样,便保证了核心数据在公共通信网中传输的安全性和可靠性。
  自DES 算法公诸于世以来,学术界围绕它的安全性等方面进行了研究并展开了激烈的争论。在技术上,对DES的批评主要集中在以下几个方面:

1、作为分组密码,DES 的加密单位仅有64 位二进制,这对于数据传输来说太小,因为每个分组仅含8 个字符,而且其中某些位还要用于奇偶校验或其他通讯开销。

2、DES 的密钥的位数太短,只有56 比特,而且各次迭代中使用的密钥是递推产生的,这种相关必然降低密码体制的安全性,在现有技术下用穷举法寻找密钥已趋于可行。

3、DES 不能对抗差分和线性密码分析。

4、DES 用户实际使用的密钥长度为56bit,理论上最大加密强度为256。DES 算法要提高加密强度(例如增加密钥长度),则系统开销呈指数增长。除采用提高硬件功能和增加并行处理功能外,从算法本身和软件技术方面都无法提高DES 算法的加密强度。

  说白了,DES在使用特殊的硬件是可以被暴力破解的。最典型的是有个专门破解des的机器,EFF DES破解机(https://baike.baidu.com/item/EFF-DES%E7%A0%B4%E8%A7%A3%E6%9C%BA/22718143?fr=aladdin)。

AES加密

  2000年10月,NIST(美国国家标准和技术协会)宣布通过从15种候选算法中选出的一项新的密匙加密标准。AES(Advanced Encryption Standard),高级加密标准,速度快,安全级别高。
  加密模式:AES一共有四种加密模式,分别是ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB、OFB,我们一般使用的是CBC模式。四种模式中除了ECB相对不安全之外,其它三种模式的区别并没有那么大。AES加密算法采用分组密码体制,根据秘钥长度分为AES-128(16byte),AES-192(24byte)和AES-256(32byte),AES-128是16byte密钥10轮加密,AES-256是32byte密钥14轮加密,AES256安全程度更高。

ECB加密

  ECB加密是AES加密中的一种模式,它为啥不安全呢?ECB模式全称是电子密码本(Electronic CodeBook)模式。
  ECB加密过程是先将明文按秘钥长度(128,192,256)进行分组,不足一组的需要填充,每组使用AES算法用秘钥进行加密。


下面是go语言版本的ECB加密和解密

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
package main
import (
"bytes"
"crypto/aes"
"fmt"
)

//AES ECB模式的加密解密
func ECBEncrypt(src []byte, key []byte) ([]byte, error) {
//key只能是 16 24 32长度
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//padding
src = PKCS5Padding(src, block.BlockSize())
//返回加密结果
encryptData := make([]byte, len(src))
//存储每次加密的数据
tmpData := make([]byte, block.BlockSize())

//分组分块加密
for index := 0; index < len(src); index += block.BlockSize() {
block.Encrypt(tmpData, src[index:index+block.BlockSize()])
copy(encryptData[index:], tmpData)
}
return encryptData, nil
}
func ECBDecrypt(src []byte, key []byte) ([]byte, error) {
//key只能是 16 24 32长度
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//返回解密结果
decryptData := make([]byte, len(src))
//存储每次解密的数据
tmpData := make([]byte, block.BlockSize())

//分组分块解密
for index := 0; index < len(src); index += block.BlockSize() {
block.Decrypt(tmpData, src[index:index+block.BlockSize()])
copy(decryptData[index:], tmpData)
}
return PKCS5UnPadding(decryptData), nil
}

func PKCS5Padding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
//填充
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

简单提下这个地方的Padding,即数据填充。AES加密需要明文按一定长度对齐,叫做块大小(BlockSize),比如16字节,那么对于一段任意的数据,加密前需要对最后一个块填充到16 字节,解密后需要删除掉填充的数据。有三种填充方式PKCS7Padding,PKCS5Padding,ZeroPadding:

  • ZeroPadding,数据长度不对齐时使用0填充,否则不填充。
  • PKCS7Padding,假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。
  • PKCS5Padding,PKCS7Padding的子集,块大小固定为8字节。

  其实PKCS7Padding和PKCS5Padding区分并不是很细,他们最后一个字节肯定为填充数据的长度,所以在解密后可以准确删除填充的数据,而使用ZeroPadding填充时,没办法区分真实数据与填充数据,所以只适合以\0结尾的字符串加解密。
  使用ECB加密一张图片时,发现它并不能很好隐藏原图的信息。

主要原因是,ECB的明文分组与密文分组是一一对应的关系,而分组与分组之间的关系没有被很好的隐藏,比如A分组加密为A1,B分组加密为B1,但A与B的某种函数关系在A1和B1中任然被保留了,即如果A==B,那么A1必然==B1。所以色块与色块间的依赖关系被保留了。

由于这种关系,ECB模式中,攻击者无需破译密码就能操纵明文。比如这样一个场景:

从A-5374账号向B-6671账号转账1亿元

将上面数据用ECB加密,加密后

攻击者将密文分组1和2对调,分组对调数据仍然能正常解密

攻击者没有试图破译密码,但场景却发生了变化

CBC加密

  CBC加密也是AES加密的一种加密模式,全称是Cipher Block Chaining(密码分组链接模式)。他在ECB的基础之上引入了初始向量的概念,说白了就是一个加盐的过程,不过每个分组使用的盐值是上一个分组的密文而已。

  加密的时候,第一个明文块会首先和初始向量IV做异或操作,然后再经过密钥加密,然后第一个密文块又会作为第二个明文块的加密向量来异或,依次类推下去,这样相同的明文块加密出的密文块就是不同的,因此更加安全,我们常用的就是CBC加密模式。比如,第一个分组的明文是A,CBC加密后为A1,第二个分组的明文也是A,CBC加密后为A2,A1!=A2,因为第一个分组和第二个分组使用了不同的初始向量。因此,CBC加密很好的隐藏了色块与色块间的关系。
  再回到上面的攻击场景,黑客调整了明文分组的顺序,那么这个明文将无法被解密。
下面是一个go版本的cbc加密的例子

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
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/hex"
"fmt"
)

func main(){
key := []byte("2019201920192019") //16byte
iv := []byte("&~!@#$*|87654321") //加密时必须随机生成
pwd := "tencent0123456789" //32byte

en, _ := CBCEncrypt(pwd, key, iv)
ret := en + "." + hex.EncodeToString(iv)
fmt.Println(ret)

de, _ := CBCDecrypt(en, key, iv)
fmt.Println(string(de))

}

func CBCEncrypt(encodeStr string, key []byte, iv []byte) (string, error) {
encodeBytes := []byte(encodeStr)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}

//填充
encodeBytes = PKCS5Padding(encodeBytes, block.BlockSize())

//加密
blockMode := cipher.NewCBCEncrypter(block, iv)
crypted := make([]byte, len(encodeBytes))
blockMode.CryptBlocks(crypted, encodeBytes)

//base64
return base64.StdEncoding.EncodeToString(crypted), nil
}



func CBCDecrypt(decodeStr string, key []byte, iv []byte) ([]byte, error) {
//先base64反编码
decodeBytes, err := base64.StdEncoding.DecodeString(decodeStr)
if err != nil {
return nil, err
}

block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}

//解密
blockMode := cipher.NewCBCDecrypter(block, iv)
origData := make([]byte, len(decodeBytes))
blockMode.CryptBlocks(origData, decodeBytes)

return PKCS5UnPadding(origData), nil
}

func PKCS5Padding(cipherText []byte, blockSize int) []byte {
padding := blockSize - len(cipherText)%blockSize
//填充
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(cipherText, padText...)
}

func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}

初始向量在加密时必须真随机生成,它最后可以通过一个分隔符放在密文的末尾,作为密文的一部分,它是可以被暴露出去的。最终密文进行base64编码,初始向量进行16进制编码,通过“点”连接,可以得到一个这样的密文串(当然也可用其他的序列化方式)。

分层加密

  通常我们会把系统要使用的密码(mysql,ftp等)保存在配置文件中,明文保存往往是有风险的,一旦机器被入侵,明文密码就这样被泄露出去。最好是使用秘钥对密码进行加密,但是秘钥也保存在配置里,它还是会被泄露啊。此时秘钥可以再用新的秘钥再加密,这样一层层的加密秘钥,最后的秘钥,我们称之为根秘钥,他可以部分硬编码到程序中。

  密钥分层管理至少选择两层结构进行管理:根密钥和工作密钥。这个图中,根秘钥加密了存储秘钥,存储秘钥加密了mysql的密码,像这种多层加密,每个模块使用不同的秘钥加密,黑客就算拿到了mysql的密码密文也要先找到他的存储秘钥,找到存储秘钥,还要找到根秘钥。
  因此,这个分层加密的关键是根秘钥保存。而这又回到了PBKDF2这个函数。它不仅仅可以用来单向加密,还可以用于根秘钥导出。PBKDF2是一个基于口令的密钥导出函数,从公共秘密值导出一个或多个密钥。这样可以防止获得派生密钥的攻击者学习关于输入秘密值或任何其他导出密钥的有用信息;也可以用来确保派生密钥具有其他期望的属性,诸如在某些特定加密系统中避免“弱密钥”。

1
2
3
4
5
6
7
8
DK = PBKDF2(PRF, Password, Salt, c, dkLen)

PRF是一个hash函数,例如HASH_HMAC, sha函数
Password是用来生成密钥的原文密码。
Salt是一个加密用的盐值。
c是进行重复计算的次数。
dkLen是期望得到的密钥的长度。
DK是最后产生的密钥。

password就是所谓的公共秘密值,这个值可以保存在某个配置文件中,Salt硬编码到代码中。迭代次数设到1w次以上,以加大暴力攻击的难度。那么每次解密的流程就要先从配置中PBKDF2导出根秘钥,再解密存储秘钥,最后解密mysql的密码。

通过以上的操作,秘钥仍然是有泄露的风险的,我们需要正确的管理他的生命周期:

  • 生成: 生成算法随机性差,导致密钥可被预测,或攻击者可以自己生成密钥。
  • 分发: 密钥明文分发,导致密钥存在被攻击者截获的风险。
  • 更新: 密钥从不更新,导致攻击者更容易获取密钥,从而能够轻易获取敏感数据的明文。
  • 存储: 密钥明文存储在数据库中,导致攻击者容易读取出密钥,从而能够轻易获取敏感数据的明文。
  • 备份: 如果重要密钥从不备份,一旦密钥丢失,将导致原有加密的数据不能解密,大大降低了系统可靠性。
  • 销毁: 密钥仅被普通删除,导致攻击者有可能恢复出密钥。

非对称加密RSA

  AES对称加密的数据虽然很难破解,但是他有一个严重的弊端,就是秘钥的传输。换句话说,如何把密钥发送到需要解密你的消息的人的手里是一个问题。在发送密钥的过程中,密钥有很大的风险会被黑客们拦截。
  1976年,两位美国计算机学家Whitfield Diffie 和 Martin Hellman,提出了一种崭新构思,可以在不直接传递密钥的情况下,完成解密。这被称为”Diffie-Hellman密钥交换算法”。这个算法启发了其他科学家。人们认识到,加密和解密可以使用不同的规则,只要这两种规则之间存在某种对应关系即可,这样就避免了直接传递密钥。这种新的加密模式被称为”非对称加密算法”:
  (1)乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。
  (2)甲方获取乙方的公钥,然后用它对信息加密。
  (3)乙方得到加密后的信息,用私钥解密。

  非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥和私钥。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。
  基于这种想法,罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年设计了一种算法,可以实现非对称加密,即RSA。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。
  这种算法非常可靠,密钥越长,它就越难破解。根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。
  它的基本算法是这样的:

1
2
3
4
5
6
7
RSA加密算法为: 
(1) 取两个大素数p,q ;
(2) 计算 n=p*q , φ(n)=(p-1)*(q-1);
(3) 随机选取整数e,满足 gcd(e, φ(n))=1 (e与φ(n)互素);
(4) 计算 d 满足 d*e≡1 (mod φ(n)); (d为e的逆元,可通过扩展的欧几里得算法进行求解)
(5) {e,n}为公钥,{d,n}为私钥
(6) 加密时c = x^e mod n ;解密时 x = c^d mod n

  RSA这个算法流程看起来简单,但是要详细证明它的正确性,就有点复杂了,详细证明可以参考这篇文章(https://blog.csdn.net/flurry_rain/article/details/77970691)。
  这个算法从1977年提出到现在都没有被破解,并被广泛应用,可以说只要有网络的地方就有RSA,它是多么的牛叉!对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就肯定会极度下降。确实也有人研究如何做大数的因式分解,Pollard_rho 算法(https://blog.csdn.net/maxichu/article/details/45459533)

https

  https就是典型的对称加密和非对称加密共同使用的场景。

  1. client向server发送请求,连接到server的443端口。

  2. 服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。

  3. 传送证书。这个证书包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。

  4. 客户端解析证书。这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个对称加密的秘钥。然后用证书对该秘钥进行加密。

  5. 客户端传送公钥加密后的秘钥。这部分传送的是用证书加密后的秘钥,目的就是让服务端得到这个秘钥,以后客户端和服务端的通信就可以通过这个秘钥来进行对称加密解密了。

  6. 服务端解密秘钥。服务端用私钥解密秘密秘钥,得到了客户端传过来的秘钥,之后通信使用对称加密。

  7. 这样客户端和服务端都拥有相同的秘钥,之后的通信也转为使用对称加密。

  随机数,消息摘要,对称加密和非对称加密,广泛应用于我们的计算机系统中,比如https,token,防盗链等。加密无处不在,了解这些加密的一些基本原理,可以提高安全意识,应用于编码中,提高代码的安全性。


数据加密汇总
http://example.com/2024/04/03/数据加密汇总/
作者
Liu XinWei
发布于
2024年4月3日
许可协议