文档链接

文档链接2

视频链接


一、密码

1.1 发送者、接收者和窃听者

请想象一个Alice向Bob发送电子邮件的场景。在这个场景中,发出邮件的Alice称为 发送者(sender) ,而收到邮件的Bob则称为 接收者(receiver)

在讲解发送者、接收者的概念时,用邮件这个例子会比较便于理解,但实际上发送者和接收者这两个术语的使用范围并不仅仅局限于邮件。当某个人向另一个人发送信息时,发出信息的人称为发送者,而收到信息的人称为接收者。另外,被发送的信息有时也统称为 消息(message)


Alice向Bob发送邮件:

邮件是通过互联网从Alice的计算机发送到Bob的计算机的。在发送邮件时,邮件会经过许多台计算机和通信设备进行中转,在这个过程中,就存在被恶意窃听者(eavesdropper)偷看到的可能性。


Eve(窃听者)看到邮件的内容

窃听者Eve并不一定是人类,有可能是安装在通信设备上的某种窃听器,也可能是安装在邮件软件和邮件服务器上的某些程序。

尽管邮件内容原本应该只有发送者和接收者两个人知道,但如果不采取相应的对策,就存在被第三方知道的风险。





1.2 加密和解密

Alice不想让别人看到邮件的内容,于是她决定将邮件进行加密(encrypt)后再发送出去。

加密之前的消息称为明文(plaintext),加密之后的消息称为密文(cipher-text)。

我们看到明文可以理解其中的含义,而看到密文则无法理解其中的含义。


明文加密之后就会变成看不懂的密文

Bob收到了来自Alice的加密邮件,但作为接收者的Bob也是无法直接阅读密文的,于是
Bob需要对密文进行解密(decrypt)之后再阅读。解密就是将密文恢复成明文的过程。


密文解密之后就变成了原来的明文


将消息加密后发送, 窃听者只能得到密文

将消息加密后发送的话,即使消息被窃听,窃听者得到的也只是密文,而无法得知加密前的明文内容。

在上述场景中,Alice将邮件进行加密,而Bob则进行解密,这样做的目的,是为了不让窃听者Eve读取邮件的内容Alice和Bob通过运用密码(cryptography)技术,保证了邮件的机密性(confidentiality)。





1.3 密钥

密码算法

用于解决复杂问题的步骤,通常称为算法(algorithm)。从明文生成密文的步骤,也就是加密的步骤,称为“加密算法”,而解密的步骤则称为“解密算法”。加密、解密的算法合在一起统称为密码算法。


密钥

密码算法中需要密钥(key)。现实世界中的“钥’’,是像 :key: 这样的形状微妙而复杂的小金属片。然而,密码算法中的密钥,则是像203554728568477650354673080689430768 这样的一串非常大的数字。


无论是在加密时还是在解密时,都需要知道密钥。

正如保险柜的钥匙可以保护保险柜中存放的贵重物品一样,密码中的密钥可以保护你的重要数据。即使保险箱再坚固,如果钥匙被盗, 里面的贵重物品也会被盗。同样地我们也必须注意不要让密码的密钥被他人窃取。





1.4 凯撒密码

恺撒密码(Caesar cipher)是一种相传尤利乌斯·恺撒曾使用过的密码。恺撒于公元前100年左右诞生于古罗马,是一位著名的军事统帅。

恺撤密码是通过将明文中所使用的字母表按照一定的字数“平移”来进行加密的。比如在日语(例如平假名)或者汉语(例如汉语拼音)或者英文字母表中都可以用同样的思路来实现恺撒密码。

为了讲解方便,我们用小写字母(a,b,c,…)来表小明文,用大写字母(A,B,C,…)来表示密文。

现在我们将字母表平移3个字母,于是,明文中的a在加密后就变成了与其相隔3个字母的D,以此类推。b变成E,c变成F,d变成G……v变成Y,w变成Z,而x则会回到字母表的开头而变成A,相应地,y变成B,z变成C。通过下图我们可以很容易地理解“平移”的具体工作方式。


凯撒密码的加密

这里,我们假设要保密的信息为monkey d luffy这个男孩的名字。我们暂且不管这个名字到底代表一位真实的男性,还是只是一种暗号,只考虑将它在保密的状态下发送给接收者。

此时,明文包含下列12个字母:monkey d luffy, 接下来我们对明文中的字母逐一加密:

1
2
3
4
5
6
7
8
9
10
11
12
m    --->    P                
o ---> R
n ---> Q
k ---> N
e ---> H
y ---> B
d ---> G
l ---> O
u ---> X
f ---> I
f ---> I
y ---> B

这样,明文 monkey d luffy 就被转换成了密文PRQNHB G OXIIB,monkey d luffy这个词我们能够看懂,但

PRQNHB G OXIIB就看不懂了。

恺撒密码中,将字母表中的字母平移这个操作就是密码的算法,而平移的字母数量则相当于密钥。在上面的例子中,密钥为3(如下图)。


凯撒密码的解密

现在,假设接收者已经收到了密文PRQNHB G OXIIB,由于密文本身是看不懂的,因此必须将它解密成明文。

恺撒密码的解密过程是使用与加密时相同的密钥进行反向的平移操作。用刚才的例子来说,只要反向平移3个字母就可以解密了

1
2
3
4
5
6
7
8
9
10
11
12
P    --->    m                
R ---> o
Q ---> n
N ---> k
H ---> e
B ---> y
G ---> d
O ---> l
X ---> u
I ---> f
I ---> f
B ---> y

这样我们就得到了明文monkey d luffy

在这个场景中, 秘钥3必须由发送者和接收者事先约定好。





1.5 密码信息安全常识与威胁

1.5.1 密码信息安全常识

在继续下面的内容之前,我们先来介绍一些关于密码的常识。刚刚开始学习密码的人常常会对以下这几条感到不可思议,因为它们有悖于我们的一般性常识。

  • 不要使用保密的密码算法
  • 使用低强度的密码比不进行任何加密更危险
  • 任何密码总有一天都会被破解
  • 密码只是信息安全的一部分

不要使用保密的密码算法

很多企业都有下面这样的想法:

“由公司自己开发一种密码算法,并将这种算法保密,这样就能保证安全。然而,这样的想法却是大错特错,使用保密的密码算法是无法获得高安全性的。我们不应该制作或使用任何保密的密码算法,而是应该使用那些已经公开的、被公认为强度较高的密码算法。

这样做的原因主要有以下两点:

  • 密码算法的秘密早晚会公诸于世

    从历史上看,密码算法的秘密最终无一例外地都会被暴露出来。例如: RSA公司开发的RC4密码算法曾经也是保密的,但最终还是有一位匿名人士开发并公开了与其等效的程序。

    一旦密码算法的详细信息被暴露,依靠对密码算法本身进行保密来确保机密性的密码系统也就土崩瓦解了。反之,那些公开的算法从一开始就没有设想过要保密,因此算法的暴露丝毫不会削弱它们的强度。

  • 开发高强度的密码算法是非常困难的

    • 要比较密码算法的强弱是极其困难的,因为密码算法的强度并不像数学那样可以进行严密的证明。密码算法的强度只能通过事实来证明,如果专业密码破译者经过数年的尝试仍然没有破解某个密码算法,则说明这种算法的强度较高。
    • 稍微聪明一点的程序员很容易就能够编写出“自己的密码系统”。这样的密码在外行看来貌似牢不可破,但在专业密码破译者的眼里,要破解这样的密码几乎是手到擒来。
    • 现在世界上公开的被认为强度较高的密码算法,几乎都是经过密码破译者长期尝试破解未果而存活下来的。因此,如果认为“公司自己开发的密码系统比那些公开的密码系统更强”,那只能说是过于高估自己公司的能力了。
    • 试图通过对密码算法本身进行保密来确保安全性的行为,一般称为隐蔽式安全性(securitybyobscurity),这种行为是危险且愚蠢的。
    • 反过来说,将密码算法的详细信息以及程序源代码全部交给专业密码破译者,并且为其提供大量的明文和密文样本,如果在这样的情况下破译一段新的密文依然需要花上相当长的时间,就说明这是高强度的密码。

使用低强度的密码比不进行任何加密更危险

一般人们会认为.就算密码的强度再低,也比完全不加密要强吧?其实这样的想法是非常危险的。

正确的想法应该是:与其使用低强度的密码,还不如从一开始就不使用任何密码这主要是由于用户容易通过“密码”这个词获得一种“错误的安全感”。对于用户来说,安全感与密码的强度无关,而只是由“信息已经被加密了”这一事实产生的,而这通常会导致用户在处理一些机密信息的时候麻痹大意。


任何密码总有一天会被破译

如果某种密码产品宣称“本产品使用了绝对不会被破解的密码算法”,那么你就要对这个产品的安全性打个问号了,这是因为绝对不会被破解的密码是不存在的。

无论使用任何密码算法所生成的密文,只要将所有可能的密钥全部尝试一遍,就总有一天可以破译出来。因此,破译密文所需要花费的时间,与要保密的明文的价值之间的权衡就显得非常重要。


密码只是信息安全的一部分

我们还是回到Alice给Bob发送加密邮件的例子。即便不去破解密码算法,也依然有很多方法能够知道Alice所发送的邮件内容, 例如:

攻击者可以不去试图破译经过加密的邮件,而是转而攻击Alice的电脑以获取加密之前的邮件明文。

上面提到的攻击手段,都与密码的强度毫无关系。要保证良好的安全性,就需要理解“系统”这一概念本身的性质复杂的系统就像一根由无数个环节相连组成的链条,如果用力拉,链条就会从其中最脆弱的环节处断开。因此,系统的强度取决于其中最脆弱的环节的强度。

最脆弱的环节并不是密码,而是人类自己。





1.5.2 密码信息威胁

我们将信息安全所面临的威胁与用来用对这些威胁的密码技术直接的关系用一张图标来表示出来。





二、对称加密

“对称加密: 也称为对称密码, 是指在加密和解码时使用同一秘钥的加密方式”

现代的密码都是建立在计算机的基础之上的,这是因为现代的密码所处理的数据量非常大,而且密码算法也非常复杂,不借助计算机的力量就无法完成加密和解密的操作。

2.1 DES

2.1.1 什么是DES

DES(Data Encryption Standard)是1977年美国联邦信息处理标准(FIPS)中所采用的一种对称密码(FIPS46.3)。DES一直以来被美国以及其他国家的政府和银行等广泛使用。然而,随着计算机的进步,现在DES已经能够被暴力破解,强度大不如前了。


RSA公司举办过破泽DES密钥的比赛(DESChallenge),我们可以看一看RSA公司官方公布的比赛结果:

1997年的DES Challenge1中用了96天破译密钥
1998年的DES ChallengeIl-I中用了41天破译密钥
1998年的DES ChallengeII-2中用了56小时破译密钥
1999年的DES ChallengeIll中只用了22小时15分钟破译密钥

由于DES的密文可以在短时间内被破译,因此除了用它来解密以前的密文以外,现在我们不应该再使用DES了。





2.1.2 加密和解密





2.1.3 Go中对DES的操作





2.2 三重DES

2.2.1 三重DES的加密





2.2.2 Go中对3DES的操作





2.3 AES

2.3.1 什么是AES

AES(Advanced Encryption Standard)是取代其前任标准(DES)而成为新标准的一种对称密码算法。全世界的企业和密码学家提交了多个对称密码算法作为AES的候选,最终在2000年从这些候选算法中选出了一种名为 Rijndael 的对称密码算法,并将其确定为了AES。

Rijndael是由比利时密码学家Joan Daemen和Vincent Rijmen设汁的分组密码算法,今后会有越来越多的密码软件支持这种算法。

Rijndael的分组长度为128比特,密钥长度可以以32比特为单位在128比特到256比特的范围内进行选择(不过在AES的规格中,密钥长度只有128、192和256比特三种)。

128bit = 16字节

192bit = 24字节

256bit = 32字节

在go提供的接口中,如果你提供的密钥长度不是 16、24 或 32 字节(例如 18 字节、20 字节等),Go 将会抛出错误。





2.3.2 AES的加密和解密

和DES—样,AES算法也是由多个轮所构成的,下图展示了每一轮的大致计算步骤。DES使用Feistel网络作为其基本结构,而AES没有使用Feistel网络,而是使用了SPN,Rijndael的输入分组为128比特,也就是16字节。首先,需要逐个字节地对16字节的输入数据进行SubBytes处理。所谓SubBytes,就是以每个字节的值(0~255中的任意值)为索引,从一张拥有256个值的替换表(S-Box)中查找出对应值的处理,也是说,将一个1字节的值替换成另一个1字节的值。

SubBytes之后需要进行ShiftRows处理,即将SubBytes的输出以字节为单位进行打乱处理。从下图的线我们可以看出,这种打乱处理是有规律的。

  • SubBytes – 字节代换
  • ShiftRows – 行移位代换
  • MixColumns – 列混淆
  • AddRoundKey – 轮密钥加

ShiftRows之后需要进行MixColumns处理,即对一个4字节的值进行比特运算,将其变为另外一个4字节值。

最后,需要将MixColumns的输出与轮密钥进行XOR,即进行AddRoundKey处理。到这里,AES的一轮就结東了。实际上,在AES中需要重复进行10 ~ 14轮计算。

通过上面的结构我们可以发现输入的所有比特在一轮中都会被加密。和每一轮都只加密一半输入的比特的Feistel网络相比,这种方式的优势在于加密所需要的轮数更少。此外,这种方式还有一个优势,即SubBytes,ShiftRows和MixColumns可以分别按字节、行和列为单位进行并行计算。


下图展示了AES中一轮的解密过程。从图中我们可以看出,SubBytes、ShiftRows、MixColumns分别存在反向运算InvSubBytes、InvShiftRows、InvMixColumns,这是因为AES不像Feistel网络一样能够用同一种结构实现加密和解密。

  • InvSubBytes – 逆字节替代
  • InvShiftRows – 逆行移位
  • InvMixColumns – 逆列混淆




2.3.3 Go中对AES的使用

加解密实现思路

  • 加密 - CBC分组模式
    1. 创建并返回一个使用AES算法的cipher.Block接口
      • 秘钥长度为128bit, 即 128/8 = 16字节(byte)
    2. 对最后一个明文分组进行数据填充
      • AES是以128比特的明文(比特序列)为一个单位来进行加密的
      • 最后一组不够128bit, 则需要进行数据填充
    3. 创建一个密码分组为链接模式的, 底层使用AES加密的BlockMode接口
    4. 加密连续的数据块
  • 解密
    1. 创建并返回一个使用AES算法的cipher.Block接口
    2. 创建一个密码分组为链接模式的, 底层使用AES解密的BlockMode接口
    3. 数据块解密
    4. 去掉最后一组的填充数据

加解密的代码实现

加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// AES加密
func AESEncrypt(src, key []byte) []byte{
// 1. 创建一个使用AES加密的块对象
block, err := aes.NewCipher(key)
if err != nil{
panic(err)
}
// 2. 最后一个分组进行数据填充
src = PKCS5Padding(src, block.BlockSize())
// 3. 创建一个分组为链接模式, 底层使用AES加密的块模型对象
blockMode := cipher.NewCBCEncrypter(block, key[:block.BlockSize()])
// 4. 加密
dst := src
blockMode.CryptBlocks(dst, src)
return dst
}

解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// AES解密
func AESDecrypt(src, key []byte) []byte{
// 1. 创建一个使用AES解密的块对象
block, err := aes.NewCipher(key)
if err != nil{
panic(err)
}
// 2. 创建分组为链接模式, 底层使用AES的解密模型对象
blockMode := cipher.NewCBCDecrypter(block, key[:block.BlockSize()])
// 3. 解密
dst := src
blockMode.CryptBlocks(dst, src)
// 4. 去掉尾部填充的字
dst = PKCS5UnPadding(dst)
return dst
}

重要的函数说明

  1. 生成一个底层使用AES加/解密的Block接口对象
1
2
3
4
函数对应的包: import "crypto/aes"
func NewCipher(key []byte) (cipher.Block, error)
- 参数 key: aes对称加密使用的密码, 密码长度为128bit, 即16byte
- 返回值 cipher.Block: 创建出的使用AES加/解密的Block接口对象
  1. 创建一个密码分组为CBC模式, 底层使用b加密的BlockMode接口对象
1
2
3
4
5
函数对应的包: import "crypto/cipher"
func NewCBCEncrypter(b Block, iv []byte) BlockMode
- 参数 b: 使用aes.NewCipher函数创建出的Block接口对象
- 参数 iv: 事先准备好的一个长度为一个分组长度的比特序列, 每个分组为64bit, 即8byte
- 返回值: 得到的BlockMode接口对象
  1. 使用cipher包的BlockMode接口对象对数据进行加/解密
1
2
3
4
5
6
7
8
9
10
接口对应的包: import "crypto/cipher"
type BlockMode interface {
// 返回加密字节块的大小
BlockSize() int
// 加密或解密连续的数据块,src的尺寸必须是块大小的整数倍,src和dst可指向同一内存地址
CryptBlocks(dst, src []byte)
}
接口中的 CryptBlocks(dst, src []byte) 方法:
- 参数 dst: 传出参数, 存储加密或解密运算之后的结果
- 参数 src: 传入参数, 需要进行加密或解密的数据切片(字符串)
  1. 创建一个密码分组为CBC模式, 底层使用b解密的BlockMode接口对象
1
2
3
4
5
6
函数对应的包: import "crypto/cipher"
func NewCBCDecrypter(b Block, iv []byte) BlockMode
- 参数 b: 使用des.NewCipher函数创建出的Block接口对象
- 参数 iv: 事先准备好的一个长度为一个分组长度的比特序列, 每个分组为128bit, 即16byte,
该序列的值需要和NewCBCEncrypter函数的第二个参数iv值相同
- 返回值: 得到的BlockMode接口对象




2.5 应选择哪种对称加密

前面我们介绍了DES、三重DES和AES等对称密码,那么我们到底应该使用哪一种对称密码算法呢?

  1. 今后最好不要将DES用于新的用途,因为随着计算机技术的进步,现在用暴力破解法已经能够在现实的时间内完成对DES的破译。但是,在某些情况下也需要保持与旧版本软件的兼容性。
  2. 出于兼容性的因素三重DES在今后还会使用一段时间,但会逐渐被AES所取代。
  3. 今后大家应该使用的算法是AES(Rijndael),因为它安全、快速,而且能够在各种平台上工作。此外,由于全世界的密码学家都在对AES进行不断的验证,因此即便万一发现它有什么缺陷,也会立刻告知全世界并修复这些缺陷。

一般来说,我们不应该使用任何自制的密码算法,而是应该使用AES。因为AES在其选定过程中,经过了全世界密码学家所进行的高品质的验证工作,而对于自制的密码算法则很难进行这样的验证。


本章小结

本章中我们介绍了对称密码,以及DES、三重DES、AES和其他一些密码算法。

使用一种密钥空间巨大,且在算法上没有弱点的对称密码,就可以通过密文来确保明文的机密性。巨大的密钥空间能够抵御暴力破解,算法上没有弱点可以抵御其他类型的攻击。

然而,用对称密码进行通信时,还会出现密钥的配送问题,即如何将密钥安全地发送给接收者。为了解决密钥配送问题,我们需要非对称加密技术。非对称加密,我们将在第四章进行讲解。

本章所介绍的几乎所有的密码算法,都只能将一个固定长度的分组进行加密当需要加密的明文长度超过分组长度时,就需要对密码算法进行迭代下一章我们将探讨对分组密码进行迭代的方法。





三、分组密码的模式

“分组密码的模式 – 分组密码是如何迭代的”

本章中我们将探讨一下分组密码的模式

我们在上一章中介绍的DES和AES都属于分组密码,它们只能加密固定长度的明文。如果需要加密任意长度的明文,就需要对分组密码进行迭代,而分组密码的迭代方法就称为分组密码的“模式”。

分组密码有很多种模式,如果模式的选择不恰当,就无法保证机密性。例如,如果使用ECB模式,明文中的一些规律就可以通过密文被识别出来。

分组密码的主要模式(ECB、CBC、CFB、OFB、CTR),最后再来考察一下到底应该使用哪一种模式。

3.1 分组密码

分组密码(blockcipher)是每次只能处理特定长度的一块数据的一类密码算法,这里的一块”就称为分组(block)。此外,一个分组的比特数就称为分组长度(blocklength)。

例如,DES和三重DES的分组长度都是64比特。这些密码算法一次只能加密64比特的明文.并生成64比特的密文。

AES的分组长度可以从128比特、192比特和256比特中进行选择。当选择128比特的分组长度时,AES一次可加密128比特的明文,并生成128比特的密文





四、非对称加密

“非对称加密也叫公钥密码: 使用公钥加密, 使用私钥解密”

在对称密码中,由于加密和解密的密钥是相同的,因此必须向接收者配送密钥。用于解密的密钥必须被配送给接收者,这一问题称为密钥配送问题。如果使用非对称加密也可以称为公钥密码,则无需向接收者配送用于解密的密钥,这样就解决了密钥配送问题。可以说非对称加密是密码学历史上最伟大的发明。

非对称加密中,密钥分为加密密钥和解密密钥两种。发送者用加密密钥对消息进行加密,接收者用解密密钥对密文进行解密。要理解公钥密码,清楚地区分加密密钥和解密密钥是非常重要的。加密密钥是发送者加密时使用的,而解密密钥则是接收者解密时使用的。

仔细思考一下加密密钥和解密密钥的区别,我们可以发现:

  • 发送者只需要加密密钥
  • 接收者只需要解密密钥
  • 解密密钥不可以被窃听者获取
  • 加密密钥被窃听者获取也没问题

也就是说,解密密钥从一开始就是由接收者自己保管的,因此只要将加密密钥发给发送者就可以解决密钥配送问题了,而根本不需要配送解密密钥。

非对称加密中,加密密钥一般是公开的。正是由于加密密钥可以任意公开,因此该密钥被称为公钥(publickey) 。公钥可以通过邮件直接发送给接收者,也可以刊登在报纸的广告栏上,做成看板放在街上,或者做成网页公开给世界上任何人,而完全不必担心被窃听者窃取。

当然,我们也没有必要非要将公钥公开给全世界所有的人,但至少我们需要将公钥发送给需要使用公钥进行加密的通信对象(也就是给自己发送密文的发送者)。

相对地,解密密钥是绝对不能公开的,这个密钥只能由你自己来使用,因此称为私钥(privatekey) 。私钥不可以被别人知道,也不可以将它发送给别人,甚至也不能发送给自己的通信对象。

公钥和私钥是一一对应的,一对公钥和私钥统称为密钥对(keypair) 。由公钥进行加密的密文,必须使用与该公钥配对的私钥才能够解密。密钥对中的两个密钥之间具有非常密切的关系(数学上的关系)一一因此公钥和私钥是不能分别单独生成的。

公钥密码的使用者需要生成一个包括公钥和私钥的密钥对,其中公钥会被发送给别人,而私钥则仅供自己使用。稍后我们将具体尝试生成一个密钥对。

4.1 非对称加密的通信流程

下面我们来看一看使用公钥密码的通信流程。和以前一样、我们还是假设Alice要给Bob发送一条消息,Alice是发送者,Bob是接收者,而这一次窃听者Eve依然能够窃听到他们之间的通信内容。

在非对称加密通信中,通信过程是由接收者Bob来启动的。

  1. Bob生成一个包含公钥和私钥的密钥对。

    • 私钥由Bob自行妥善保管。
  2. Bob将自己的公钥发送给Alicea

    • Bob的公钥被窃听者Eve截获也没关系。
    • 将公钥发送给Alice,表示Bob请Alice用这个公钥对消息进行加密并发送给他。
  3. Alice用Bob的公钥对消息进行加密。

    • 加密后的消息只有用Bob的私钥才能够解密。
    • 虽然Alice拥有Bob的公钥,但用Bob的公钥是无法对密文进行解密的。
  4. Alice将密文发送给Bobo

    • 密文被窃听者Eve截获也没关系。Eve可能拥有Bob的公钥,但是用Bob的公钥是无法进行解密的。
  5. Bob用自己的私钥对密文进行解密。

请参考下图, 看一看在Alice和Bob之间到底传输了哪些信息。其实它们之间所传输的信息只有两个:Bob的公钥以及用Bob的公钥加密的密文。由于Bob的私钥没有出现在通信内容中,因此窃听者Eve无法对密文进行解密。

窃听者Eve可能拥有Bob的公钥,但是Bob的公钥只是加密密钥,而不是解密密钥,因此窃听者Eve就无法完成解密操作。





4.2 RSA

非对称加密的密钥分为加密密钥和解密密钥,但这到底是怎样做到的呢?本节中我们来讲解现在使用最广泛的公钥密码算法一一RSA。

RSA是一种非对称加密算法,它的名字是由它的三位开发者,即RonRivest、AdiShamir和LeonardAdleman 的姓氏的首字母组成的(Rivest-Shamir-Leonard)。

RSA可以被用于非对称加密和数字签名,关于数字签名我们将在后面章节进行讲解。

1983年,RSA公司为RSA算法在美国取得了专利,但现在该专利已经过期。

4.2.1 RSA加密

下面我们终于可以讲一讲非对称加密的代表—RSA的加密过程了。在RSA中,明文、密钥和密文都是数字。RSA的加密过程可以用下列公式来表达,如下。

也就是说,RSA的密文是对代表明文的数字的E次方求 modN的结果。换句话说,就是将明文自己做E次乘法,然后将其结果除以N求余数,这个余数就是密文。

咦,就这么简单?

对,就这么简单。仅仅对明文进行乘方运算并求mod即可,这就是整个加密的过程。在对称密码中,出现了很多复杂的函数和操作,就像做炒鸡蛋一样将比特序列挪来挪去,还要进行XOR(按位异或)等运算才能完成,但RSA却不同,它非常简洁。

对了,加密公式中出现的两个数一一一E和N,到底都是什么数呢?RSA的加密是求明文的E次方modN,因此只要知道E和N这两个数,任何人都可以完成加密的运算。所以说,E和N是RSA加密的密钥,也就是说,E和N的组合就是公钥

不过,E和N并不是随便什么数都可以的,它们是经过严密计算得出的。顺便说一句,E是加密(Encryption)的首字母,N是数字(Number)的首字母

有一个很容易引起误解的地方需要大家注意一一E和N这两个数并不是密钥对(公钥和私钥的密钥对)。E和N两个数才组成了一个公钥,因此我们一般会写成 “公钥是(E,N)” 或者 “公钥是{E, N}” 这样的形式,将E和N用括号括起来。

现在大家应该已经知道,RSA的加密就是 “求E次方的modN” ,接下来我们来看看RSA的解密。





4.2.2 RSA解密

RSA的解密和加密一样简单,可以用下面的公式来表达:

也就是说,对表示密文的数字的D次方求modN就可以得到明文。换句话说,将密文自己做D次乘法,再对其结果除以N求余数,就可以得到明文。

这里所使用的数字N和加密时使用的数字N是相同的。数D和数N组合起来就是RSA的解密密钥,因此D和N的组合就是私钥。只有知道D和N两个数的人才能够完成解密的运算。

大家应该已经注意到,在RSA中,加密和解密的形式是相同的。加密是求 “E次方的mod N”,而解密则是求 “D次方的modN”

当然,D也并不是随便什么数都可以的,作为解密密钥的D,和数字E有着相当紧密的联系。否则,用E加密的结果可以用D来解密这样的机制是无法实现的。

顺便说一句,D是解密〈Decryption)的首字母,N是数字(Number)的首字母

我们将上面讲过的内容整理一下,如下表所示。





4.2.3 Go中生成公钥和私钥

需要引入的包

1
2
3
4
5
6
7
import (
"crypto/rsa"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"os"
)

生成私钥操作流程概述

  1. 使用rsa中的GenerateKey方法生成私钥
  2. 通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
  3. 将私钥字符串设置到pem格式块中
  4. 通过pem将设置好的数据进行编码, 并写入磁盘文件中

生成公钥操作流程

  1. 从得到的私钥对象中将公钥信息取出
  2. 通过x509标准将得到的rsa公钥序列化为字符串
  3. 将公钥字符串设置到pem格式块中
  4. 通过pem将设置好的数据进行编码, 并写入磁盘文件

生成公钥和私钥的源代码

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
// 参数bits: 指定生成的秘钥的长度, 单位: bit
func RsaGenKey(bits int) error{
// 1. 生成私钥文件
// GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
// 参数1: Reader是一个全局、共享的密码用强随机数生成器
// 参数2: 秘钥的位数 - bit
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil{
return err
}
// 2. MarshalPKCS1PrivateKey将rsa私钥序列化为ASN.1 PKCS#1 DER编码
derStream := x509.MarshalPKCS1PrivateKey(privateKey)
// 3. Block代表PEM编码的结构, 对其进行设置
block := pem.Block{
Type: "RSA PRIVATE KEY",//"RSA PRIVATE KEY",
Bytes: derStream,
}
// 4. 创建文件
privFile, err := os.Create("private.pem")
if err != nil{
return err
}
// 5. 使用pem编码, 并将数据写入文件中
err = pem.Encode(privFile, &block)
if err != nil{
return err
}
// 6. 最后的时候关闭文件
defer privFile.Close()

// 7. 生成公钥文件
publicKey := privateKey.PublicKey
derPkix, err := x509.MarshalPKIXPublicKey(&publicKey)
if err != nil{
return err
}
block = pem.Block{
Type: "RSA PUBLIC KEY",//"PUBLIC KEY",
Bytes: derPkix,
}
pubFile, err := os.Create("public.pem")
if err != nil{
return err
}
// 8. 编码公钥, 写入文件
err = pem.Encode(pubFile, &block)
if err != nil{
panic(err)
return err
}
defer pubFile.Close()

return nil

}

重要的函数介绍:

  1. GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥。
1
2
3
4
5
6
7
8
"crypto/rsa" 包中的函数
func GenerateKey(random io.Reader, bits int) (priv *PrivateKey, err error)
- 参数1: io.Reader: 赋值为: rand.Reader
-- rand包实现了用于加解密的更安全的随机数生成器。
-- var Reader io.Reader (rand包中的变量)
- 参数2: bits: 秘钥长度
- 返回值1: 代表一个RSA私钥。
- 返回值2: 错误信息
  1. 通过x509 将rsa私钥序列化为ASN.1 PKCS#1 DER编码
1
2
3
4
"crypto/x509" 包中的函数 (x509包解析X.509编码的证书和密钥)。
func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte
- 参数1: 通过rsa.GenerateKey得到的私钥
- 返回值: 将私钥通过ASN.1序列化之后得到的私钥编码数据
  1. 设置Pem编码结构
1
2
3
4
5
6
Block代表PEM编码的结构。
type Block struct {
Type string // 得自前言的类型(如"RSA PRIVATE KEY")
Headers map[string]string // 可选的头项,Headers是可为空的多行键值对。
Bytes []byte // 内容解码后的数据,一般是DER编码的ASN.1结构
}
  1. 将得到的Pem格式私钥通过文件指针写入磁盘中
1
2
3
4
"encoding/pem" 包中的函数
func Encode(out io.Writer, b *Block) error
- 参数1: 可进行写操作的IO对象, 此处需要指定一个文件指针
- 参数2: 初始化完成的Pem块对象, 即Block对象
  1. 通过RSA私钥得到公钥
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 私钥
type PrivateKey struct {
PublicKey // 公钥
D *big.Int // 私有的指数
Primes []*big.Int // N的素因子,至少有两个
// 包含预先计算好的值,可在某些情况下加速私钥的操作
Precomputed PrecomputedValues
}
// 公钥
type PublicKey struct {
N *big.Int // 模
E int // 公开的指数
}
通过私钥获取公钥
publicKey := privateKey.PublicKey // privateKey为私钥对象
  1. 通过x509将公钥序列化为PKIX格式DER编码。
1
2
3
4
5
"crypto/x509" 包中的函数
func MarshalPKIXPublicKey(pub interface{}) ([]byte, error)
- 参数1: 通过私钥对象得到的公钥
- 返回值1:将公钥通过ASN.1序列化之后得到的编码数据
- 返回值2: 错误信息
  1. 将公钥编码之后的数据格式化为Pem结构, 参考私钥的操作

  2. 将得到的Pem格式公钥通过文件指针写入磁盘中

  3. 生成的私钥和公钥文件数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 私钥文件数据
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQC5bm0DCEV+EFeiLUqSshziqhSB30jXy5BWbPV5SlMq4aWiEknM
i+Mw1aXic4bEsM3YyT73eWsifqZNSc/4fRaV4qz5OL8IIe9AZoGDSLX/Ar9AQMJf
OHbAtdIlCGQ4d80KjpDpPs2wZkTqllWCg31d7U3DVEm5kqTGtSYIu9e7JQIDAQAB
AoGARGdn72ZtvENrEHiEufjajwMO7Zng1TpS1I79PvEcHQWAhHkaoEo6VRl7SD41
yPkv9njGsaQo0WDHGFvSTGhYm/EWGrBWRPc5xXbSBg7ty9Iza9B1ekAj8VfWryen
Wje3xDOCVCDUiCcYdaSfPiJPYuWMSnNMNa+0cR921zBQg0ECQQDpCMljuH7LrpbC
NDF5q+LbUWMAE2KLDPX4WmDSdZdIO3mPux3MdwOUEfrcvSBGZNB7gyaEG7goZL8G
BqL22MJHAkEAy7SqbVPoPbMPHuLI52VQ2FDp6xxSWLhjmv1ePCHGo28MDCaHeVzZ
QaxyuIbnY8A6NHfu/QGwz/eB941IjYNBMwJBAI9XEEl+mr++zIz4fdZRnGE7VqId
SmgtuL7jGNtb6YpMyyFV/6ZdLp5N0PkmfEvQh0zyBycLxeNS1Q1n16Xu/tECQQCZ
dF42wdDgOfWYFMu31VETw9CTtuApya3vYhMNRXx4Pf1bYeMIf/OCT8CUVbwWHwc5
42d73TwvTorvy9TuFgSVAkEA6F69THlTn5oIP8IWHcHuqS01fIR/vGfEwQ4cFZGR
ketfieyeeF8rjn4qzwT/ugwRNjkhfKmoILnIC8UhEEJdjA==
-----END RSA PRIVATE KEY-----
1
2
3
4
5
6
7
// 公钥文件数据
-----BEGIN RSA PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5bm0DCEV+EFeiLUqSshziqhSB
30jXy5BWbPV5SlMq4aWiEknMi+Mw1aXic4bEsM3YyT73eWsifqZNSc/4fRaV4qz5
OL8IIe9AZoGDSLX/Ar9AQMJfOHbAtdIlCGQ4d80KjpDpPs2wZkTqllWCg31d7U3D
VEm5kqTGtSYIu9e7JQIDAQAB
-----END RSA PUBLIC KEY-----




4.2.4 Go中使用RSA

操作步骤

  • 使用公钥加密

    1. 将公钥文件中的公钥读出, 得到使用pem编码的字符串
    2. 将得到的字符串解码
    3. 使用x509将编码之后的公钥解析出来
    4. 使用得到的公钥通过rsa进行数据加密
  • 使用私钥解密

    1. 将私钥文件中的私钥读出, 得到使用pem编码的字符串
    2. 将得到的字符串解码
    3. 使用x509将编码之后的私钥解析出来
    4. 使用得到的私钥通过rsa进行数据解密

代码实现

src是需要加密的明文,filename是密钥文件

  • 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
func RSAEncrypt(src, filename []byte) []byte {
// 1. 根据文件名将文件内容从文件中读出
file, err := os.Open(string(filename))
if err != nil {
return nil
}
// 2. 读文件
info, _ := file.Stat()
allText := make([]byte, info.Size())
file.Read(allText)
// 3. 关闭文件
file.Close()

// 4. 从数据中查找到下一个PEM格式的块
block, _ := pem.Decode(allText)
if block == nil {
return nil
}
// 5. 解析一个DER编码的公钥
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil
}
pubKey := pubInterface.(*rsa.PublicKey)

// 6. 公钥加密
result, _ := rsa.EncryptPKCS1v15(rand.Reader, pubKey, src)
return result
}
  • RSA私钥解密
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func RSADecrypt(src, filename []byte) []byte {
// 1. 根据文件名将文件内容从文件中读出
file, err := os.Open(string(filename))
if err != nil {
return nil
}
// 2. 读文件
info, _ := file.Stat()
allText := make([]byte, info.Size())
file.Read(allText)
// 3. 关闭文件
file.Close()
// 4. 从数据中查找到下一个PEM格式的块
block, _ := pem.Decode(allText)
// 5. 解析一个pem格式的私钥
privateKey , err := x509.ParsePKCS1PrivateKey(block.Bytes)
// 6. 私钥解密
result, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, src)

return result
}




4.3 ECC椭圆曲线

概念

椭圆曲线密码学(英语:Elliptic curve cryptography,缩写为 ECC),一种建立公开密钥加密的算法,基于椭圆曲线数学。椭圆曲线在密码学中的使用是在1985年由Neal Koblitz和Victor Miller分别独立提出的。

ECC的主要优势是在某些情况下它比其他的方法使用更小的密钥——比如RSA加密算法——提供相当的或更高等级的安全。

椭圆曲线密码学的许多形式有稍微的不同,所有的都依赖于被广泛承认的解决椭圆曲线离散对数问题的困难性上。与传统的基于大质数因子分解困难性的加密方法不同,ECC通过椭圆曲线方程式的性质产生密钥。

ECC 164位的密钥产生的一个安全级相当于RSA 1024位密钥提供的保密强度,而且计算量较小,处理速度更快,存储空间和传输带宽占用较少。目前我国居民二代身份证正在使用 256 位的椭圆曲线密码,虚拟货币比特币也选择ECC作为加密算法。

具体算法详解参考:

https://www.cnblogs.com/Kalafinaian/p/7392505.html

https://blog.csdn.net/taifei/article/details/73277247


数学原理

不管是RSA还是ECC或者其它,公钥加密算法都是依赖于某个正向计算很简单(多项式时间复杂度),而逆向计算很难(指数级时间复杂度)的数学问题。

椭圆曲线依赖的数学难题是:

k为正整数,P是椭圆曲线上的点(称为基点), k*P=Q , 已知Q和P,很难计算出k





4.4 非对称加密解惑

  • 非对称加密比对称加密机密性更高吗?

这个问题无法回答, 以为机密性高低是根据秘钥长度而变化的


  • 采用1024bit 秘钥长度的非对称加密, 和采用128bit秘钥长度的对称加密中, 是秘钥更长的非对称加密更安全吗?

不是。非对称加密的密钥长度不能与对称加密的密钥长度进行直接比较。下表是一张密钥长度的比较表(本表摘自《应用密码学》),根据这张表我们可以看出,1024比特的公钥密码与128比特的对称密码相比,反而是128比特的对称密码抵御暴力破解的能力更强。

对称加密密钥长度 非对称加密密钥长度
128 比特 2304 比特
112 比特 1792 比特
80 比特 768 比特
64 比特 512 比特
56 比特 384 比特

  • 有了非对称加密, 以后对称加密会被替代吗?

不会。一般来说,在采用具备同等机密性的密钥长度的情况下,非对称加密的处理速度只有对称加密的几百分之一。因此,非对称加密并不适合用来对很长的消息内容进行加密。根据目的的不同,还可能会配合使用对称加密和非对称加密,例如,混合密码系统就是将这两种密码组合而成的。





五、单向散列函数

“单向散列函数 — 获取消息的指纹”

在刑事侦查中,侦查员会用到指纹。通过将某个特定人物的指纹与犯罪现场遗留的指纹进行对比,就能够知道该人物与案件是否存在关联。

针对计算机所处理的消息,有时候我们也需要用到“指纹”。当需要比较两条消息是否一致时,我们不必直接对比消息本身的内容,只要对比它们的“指纹”就可以了。

本章中,我们将学习单向散列函数的相关知识。使用单向散列函数就可以获取消息的“指纹”,通过对比 “指纹”,就能够知道两条消息是否一致。

下面,我们会先简单介绍一下单向散列函数,并给大家展示具体的例子。然后我们将详细介绍现在使用非常广泛的SHA-I单向散列函数。

5.1 什么是单向散列函数

单向散列函数(one-wayftnction)有一个输入和一个输出,其中输入称为消息(message),输出称为散列值(hashvalue)。单向散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性。

这里的消息不一定是人类能够读懂的文字,也可以是图像文件或者声音文件。单向散列函数不需要知道消息实际代表的含义。无论任何消息,单向散列函数都会将它作为单纯的比特序列来处理,即根据比特序列计算出散列值。

散列值的长度和消息的长度无关。无论消息是1比特,还是100MB,甚至是IOOGB,单向散列函数都会计算出固定长度的散列值。以SHA-I单向散列函数为例,它所计算出的散列值的长度永远是160比特(20字节)。





5.2 关于术语

单向散列函数的相关术语有很多变体,不同参考资料中所使用的术语也不同,下面我们就介绍其中的儿个。

单向散列函数也称为消息摘要函数(message digest function)哈希函数或者杂凑函数

输入单向散列函数的消息也称为原像(pre-image)

单向散列函数输出的散列值也称为消息摘要(message digest) 或者指纹(fingerprint)

完整性也称为一致性。

顺便说一句,单向散列函数中的“散列”的英文”hash一词,原意是古法语中的“斧子”,后来被引申为“剁碎的肉末”,也许是用斧子一通乱剁再搅在一起的那种感觉吧。单向散列函数的作用,实际上就是将很长的消息剁碎,然后再混合成固定长度的散列值。





5.3 单向散列函数的性质

通过使用单向散列函数,即便是确认几百MB大小的文件的完整性,也只要对比很短的散列值就可以了。那么,单向散列函数必须具备怎样的性质呢?我们来整理一下。


根据任意长度的消息计算出固定长度的散列值

首先,单向散列函数的输入必须能够是任意长度的消息。

其次,无论输入多长的消息,单向散列函数必须都能够生成长度很短的散列值,如果消息越长生成的散列值也越长的话就不好用了。从使用方便的角度来看,散列值的长度最好是短且固定的。


能够快速计算出散列值

计算散列值所花费的时间必须要短。尽管消息越长,计算散列值的时间也会越长,但如果不能在现实的时间内完成计算就没有意义了。


消息不同散列值也不同

为了能够确认完整性,消息中哪怕只有1比特的改变,也必须有很高的概率产生不同的散列值。

如果单向散列函数计算出的散列值没有发生变化,那么消息很容易就会被篡改,这个单向散列函数也就无法被用于完整性的检查。两个不同的消息产生同一个散列值的情况称为碰撞(collision) 。如果要将单向散列函数用于完整性的检查,则需要确保在事实上不可能被人为地发现碰撞。

难以发现碰撞的性质称为抗碰撞性(collisionresistance) 。密码技术中所使用的单向散列函数,都需要具备抗碰撞性。

强抗碰撞性,是指要找到散列值相同的两条不同的消息是非常困难的这一性质。在这里,散列值可以是任意值。密码技术中的单向散列函数必须具备强抗碰撞性。


具备单向性

单向散列函数必须具备单向性(one-way)。单向性指的是无法通过散列值反算出消息的性质。根据消息计算散列值可以很容易,但这条单行路是无法反过来走的。

正如同将玻璃砸得粉碎很容易,但却无法将碎片还原成完整的玻璃一样,根据消息计算出散列值很容易,但根据散列值却无法反算出消息。

在这里需要注意的一点是,尽管单向散列函数所产生的散列值是和原来的消息完全不同的比特序列,但是单向散列函数并不是一种加密,因此无法通过解密将散列值还原为原来的消息





5.4 单向散列函数的实际应用

下面我们来看一下实际应用单向散列函数的例子。


检测软件是否被篡改

我们可以使用单向散列函数来确认自己下载的软件是否被篡改。

很多软件,尤其是安全相关的软件都会把通过单向散列函数计算出的散列值公布在自己的官方网站上。用户在下载到软件之后,可以自行计算散列值,然后与官方网站上公布的散列值进行对比。通过散列值,用户可以确认自己所下载到的文件与软件作者所提供的文件是否一致。

这样的方法,在可以通过多种途径得到软件的情况下非常有用。为了减轻服务器的压力,很多软件作者都会借助多个网站(镜像站点)来发布软件,在这种情况下,单向散列函数就会在检测软件是否被篡改方面发挥重要作用。


消息认证码

使用单向散列函数可以构造消息认证码。

消息认证码是将“发送者和接收者之间的共享密钥”和“消息”,进行混合后计算出的散列值。使用消息认证码可以检测并防止通信过程中的错误、篡改以及伪装。

消息认证码在SSL/TLS中也得到了运用,关于SSL/TLS我们将后边章节中介绍。


数字签名

在进行数字签名时也会使用单向散列函数。

数字签名是现实社会中的签名(sign)和盖章这样的行为在数字世界中的实现。数字签名的处理过程非常耗时,因此一般不会对整个消息内容直接施加数字签名,而是先通过单向散列函数计算出消息的散列值,然后再对这个散列值施加数字签名。


伪随机数生成器

使用单向散列函数可以构造伪随机数生成器。

密码技术中所使用的随机数需要具备“事实上不可能根据过去的随机数列预测未来的随机数列”这样的性质。为了保证不可预测性,可以利用单向散列函数的单向性。


一次性口令

使用单向散列函数可以构造一次性口令(one-time password)。

一次性口令经常被用于服务器对客户端的合法性认证。在这种方式中,通过使用单向散列函数可以保证口令只在通信链路上传送一次(one-time),因此即使窃听者窃取了口令,也无法使用。





5.5 常用的单向散列函数

5.5.1 MD4、MD5

MD4是由Rivest于1990年设计的单向散列函数,能够产生128比特的散列值(RFC1186,修订版RFC1320)。不过,随着Dobbertin提出寻找MD4散列碰撞的方法,因此现在它已经不安全了。

MD5是由Rwest于1991年设计的单项散列函数,能够产生128比特的散列值(RFC1321)。

MD5的强抗碰撞性已经被攻破,也就是说,现在已经能够产生具备相同散列值的两条不同的消息,因此它也已经不安全了。

MD4和MD5中的MD是消息摘要(Message Digest)的缩写。





5.5.3 SHA-1、SHA-224、SHA-256、SHA-384、SHA-512

SHA-1是由NIST(NationalInstituteOfStandardsandTechnology,美国国家标准技术研究所)设计的一种能够产生160比特的散列值的单向散列函数。1993年被作为美国联邦信息处理标准规格(FIPS PUB 180)发布的是SHA,1995年发布的修订版FIPS PUB 180-1称为SHA-1。

SHA-1的消息长度存在上限,但这个值接近于2^64^比特,是个非常巨大的数值,因此在实际应用中没有问题。

SHA-256、SHA-384和SHA-512都是由NIST设计的单向散列函数,它们的散列值长度分别为256比特、384比特和512比特。这些单向散列函数合起来统称SHA-2,它们的消息长度也存在上限(SHA-256的上限接近于 2^64^ 比特,SHA-384 和 SHA-512的上限接近于 2^128^ 比特)。这些单向散列函数是于2002年和 SHA-1 一起作为 FIPS PUB 180-2发布的 SHA-1 的强抗碰撞性已于2005年被攻破, 也就是说,现在已经能够产生具备相同散列值的两条不同的消息。不过,SHA-2还尚未被攻破。

算法 比特数 字节数
MD4 128bit 16byte
MD5 128bit 16byte
SHA-1 160bit 20byte
SHA-224 224bit 28byte
SHA-256 256bit 32byte
SHA-384 384bit 48byte
SHA-512 512bit 64byte




5.5.4 Go中对SHA-1、SHA-2的使用

需要导入的包

1
2
3
4
5
6
import (
"crypto/sha1"
"encoding/hex"
"crypto/sha256"
"crypto/sha512"
)

使用sha1计算文件指纹

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
package main

import (
"crypto/sha1"
"fmt"
"io"
"os"
)

func main() {
// 打开文件
file, err := os.Open("example.txt")
if err != nil {
panic(err)
}
defer file.Close()

// 创建 SHA-1 哈希对象
hash := sha1.New()

// 将文件内容写入哈希对象
if _, err := io.Copy(hash, file); err != nil {
panic(err)
}

// 计算哈希值并输出
hashInBytes := hash.Sum(nil) // 返回 []byte
hashInHex := fmt.Sprintf("%x", hashInBytes) // 将哈希值格式化为十六进制字符串

fmt.Println("SHA-1 文件指纹:", hashInHex)
}




六、消息认证码

“消息认证码 — 消息被正确传送了吗?”

6.1 什么是消息认证码

Alice 和 Bob 的故事

像以前一样,我们还是从一个Alice和Bob的故事开始讲起。不过,这一次Alice和Bob分别是两家银行,Alice银行通过网络向Bob银行发送了一条汇款请求,Bob银行收到的请求内容是:

从 账户A(5374) 向 账户B(6671) 汇款1000万元

当然,Bob银行所收到的汇款请求内容必须与Alice银行所发送的内容是完全一致的。如果主动攻击者Mallory在中途将Alice银行发送的汇款请求进行了篡改,那么Bob银行就必须要能够识别出这种篡改,否则如果Mallory将收款账户改成了自己的账户,那么1000万元就会被盗走。

话说回来,这条汇款请求到底是不是Alice银行发送的呢?有可能Alice银行根本就没有发送过汇款请求,而是由主动攻击者Mallory伪装成Alice银行发送的。如果汇款请求不是来自Alice银行,那么就绝对不能执行汇款。

现在我们需要关注的问题是汇款请求(消息)的 “完整性” 和 “认证” 这两个性质。

消息的完整性(integrity), 指的是“消息没有被篡改”这一性质,完整性也叫一致性。如果能够确认汇款请求的内容与Alice银行所发出的内容完全一致,就相当于是确认了消息的完整性,也就意味着消息没有被篡改。

消息的认证(authentication)指的是“消息来自正确的发送者”这一性质。如果能够确认汇款请求确实来自Alice银行,就相当于对消息进行了认证,也就意味着消息不是其他人伪装成发送者所发出的。

通过使用本章中要介绍的消息认证码,我们就可以同时识别出篡改和伪装,也就是既可以确认消息的完整性,也可以进行认证。


什么是消息认证码

消息认证码(message authentication code)是一种确认完整性并进行认证的技术,取三个单词的首字母,简称为MAC。

消息认证码的输入包括任意长度的消息和一个发送者与接收者之间共享的密钥,它可以输出固定长度的数据,这个数据称为MAC值。

根据任意长度的消息输出固定长度的数据,这一点和单向散列函数很类似。但是单向散列函数中计算散列值时不需要密钥,而消息认证码中则需要使用发送者与接收者之间共享的密钥。

要计算MAC必须持有共享密钥,没有共享密钥的人就无法计算MAC值,消息认证码正是利用这一性质来完成认证的。此外,和单向散列函数的散列值一样,哪怕消息中发生1比特的变化,MAC值也会产生变化,消息认证码正是利用这一性质来确认完整性的。

消息认证码有很多种实现方法,大家可以暂且这样理解:消息认证码是一种与密钥相关联的单向散列函数。


单向散列函数与消息认证码的比较





6.2 消息认证码的使用步骤

我们还是以Alice银行和Bob银行的故事为例,来讲解一下消息认证码的使用步骤:

  • 发送者Alice与接收者Bob事先共享密钥。
  • 发送者Alice根据汇款请求消息计算MAC值(使用共享密钥)。
  • 发送者Alice将汇款请求消息和MAC值两者发送给接收者Bob。
  • 接收者Bob根据接收到的汇款请求消息计算MAC值(使用共享密钥)。
  • 接收者Bob将自己计算的MAC值与从Alice处收到的MAC值进行对比。
  • 如果两个MAC值一致,则接收者Bob就可以断定汇款请求的确来自Alice(认证成功);如果不一致,则可以断定消息不是来自Alice(认证失败)。




6.3 HMAC

HMAC介绍

HMAC是一种使用单向散列函数来构造消息认证码的方法(RFC2104),其中HMAC的H就是Hash的意思。

HMAC中所使用的单向散列函数并不仅限于一种,任何高强度的单向散列函数都可以被用于HMAC,如果将来设计出新的单向散列函数,也同样可以使用。

使用SHA-I、MD5、RIPEMD-160所构造的HMAC,分别称为HMAC-SHA-1、HMAC-MD5和HMAC-RlPEMD。


使用HMAC通过秘钥将消息生成消息认证码的内部实现:

通过上述流程我们可以看出,最后得到的MAC值,一定是一个和输入的消息以及密钥都相关的长度固定的比特序列。

Go中对HMAC的使用

需要使用的包

1
2
3
4
5
```

使用的函数

```go




6.4 消息认证码的密钥配送问题

在消息认证码中,需要发送者和接收者之间共享密钥,而这个密钥不能被主动攻击者Mallory获取。如果这个密钥落入Mallory手中,则Mallory也可以计算出MAC值,从而就能够自由地进行篡改和伪装攻击,这样一来消息认证码就无法发挥作用了。

发送者和接收者需要共享密钥,这一点和我们介绍的对称加密很相似。实际上,对称加密的密钥配送问题在消息认证码中也同样会发生。关于秘钥的配送后边章节会介绍如何使用非对称加密的方式进行解决。





6.5 消息认证码无法解决的问题

假设发送者Alice要向接收者Bob发送消息,如果使用了消息认证码,接收者Bob就能够断定自己收到的消息与发送者Alice所发出的消息是一致的,这是因为消息中的MAC值只有用Alice和Bob之间共享的密钥才能够计算出来,即便主动攻击者Mallory篡改消息,或者伪装成Alice发送消息,Bob也能够识别出消息的篡改和伪装。

但是,消息认证码也不能解决所有的问题,例如“对第三方证明”和“防止否认”,这两个问题就无法通过消息认证码来解决。下面我们来逐一解释一下。


对第三方证明

假设Bob在接收了来自Alice的消息之后,想要向第三方验证者Victor证明这条消息的确是Alice发送的,但是用消息认证码无法进行这样的证明,这是为什么呢?

首先,Victor要校验MAC值,就需要知道Alice和Bob之间共享的密钥。

假设Bob相信Victor, 同意将密钥告诉Victor,即便如此,Victor也无法判断这条消息是由Alice发送的,因为Victor可以认为:“即使MAC值是正确的,发送这条消息的人也不一定是Alice,还有可能是Bob。”

能够计算出正确MAC值的人只有Alice和Bob,在他们两个人之间进行通信时,可以断定是对方计算了MAC值,这是因为共享这个密钥的双方之中,有一方就是自己。然而,对于第三方Victor、Alice或Bob却无法证明是对方计算了MAC值,而不是自己。

使用第7章中将要介绍的数字签名就可以实现对第三方的证明。


防止否认

假设Bob收到了包含MAC值的消息,这个MAC值是用Alice和Bob共享的密钥计算出来的,因此Bob能够判断这条消息的确来自Alice。

但是,上面我们讲过,Bob无法向验证者Victor证明这一点,也就是说,发送者Alice可以向Victor声称:“我没有向Bob发送过这条消息。”这样的行为就称为否认(repudiation)。

Alice可以说“这条消息是Bob自己编的吧”,“说不定Bob的密钥被主动攻击者Mallory给盗取了,我的密钥可是妥善保管着呢” 等。说白了,就是Alice和Bob吵起来了。

即便Bob拿MAC值来举证,Victor也无法判断Alice和Bob谁的主张才是正确的,也就是说,用消息认证码无法防止否认(nonrepudiatlon)。





6.5 总结

消息认证码是对消息进行认证并确认其完整性的技术。通过使用发送者和接收者之间共享的密钥,就可以识别出是否存在伪装和篡改行为。

消息认证码可以使用单向散列函数HMAC, 对称加密也可以实现, 这里不再进行介绍。

消息认证码中,由于发送者和接收者共享相同的密钥,因此会产生无法对第三方证明以及无法防止否认等问题。在下一章中,我们将介绍能够解决这些问题的数字签名。





七、数字签名

“数字签名 — 消息到底是谁写的”

本章中我们将学习数字签名的相关知识。数字签名是一种将相当于现实世界中的盖章、签字的功能在计算机世界中进行实现的技术。使用数字签名可以识别篡改和伪装,还可以防止否认。

7.1 从消息认证到数字签名

消息认证码的局限性

通过使用第6章中介绍的消息认证码,我们可以识别消息是否被篡改或者发送者身份是否被伪装,也就是可以校验消息的完整性,还可以对消息进行认证。然而,比如在出具借条的场景中却无法使用消息认证码,因为消息认证码无法防止否认。

消息认证码之所以无法防止否认,是因为消息认证码需要在发送者Alice和接收者Bob两者之间共享同一个密钥。正是因为密钥是共享的,所以能够使用消息认证码计算出正确MAC值的并不只有发送者Alice,接收者Bob也可以计算出正确的MAC值。由于Alice和Bob双方都能够计算出正确的MAC值,因此对于第三方来说,我们无法证明这条消息的确是由Alice生成的。


通过数字签名解决问题

下面请大家开动一下脑筋。假设发送者Alice和接收者Bob不需要共享一个密钥,也就是说,Alice和Bob各自使用不同的密钥。

我们假设Alice使用的密钥是一个只有Alice自己才知道的私钥。当Alice发送消息时,她用私钥生成一个“签名”。相对地,接收者Bob则使用一个和Alice不同的密钥对签名进行验证。使用Bob的密钥无法根据消息生成签名,但是用Bob的密钥却可以对Alice所计算的签名进行验证,也就是说可以知道这个签名是否是通过Alice的密钥计算出来的。如果真有这么一种方法的话,那么不管是识别篡改、伪装还是防止否认就都可以实现了吧 ?

实际上,这种看似很神奇的技术早就已经问世了,这就是数字签名(digital signat.ure)。





7.2 签名的生成和验证

让我们来稍微整理一下。

在数字签名技术中,出现了下面两种行为:

  • 生成消息签名的行为
  • 验证消息签名的行为

生成消息签名这一行为是由消息的发送者Alice来完成的,也称为“对消息签名”。生成签名就是根据消息内容计算数字签名的值,这个行为意味着 “我认可该消息的内容”。

验证数字签名这一行为一般是由消息的接收者Bob来完成的,但也可以由需要验证消息的第三方来完成,这里的第三方我们暂且将其命名为验证者Victor。验证签名就是检查该消息的签名是否真的属于Alice,验证的结果可以是成功或者失败,成功就意味着这个签名是属于Alice的,失败则意味着这个签名不是属于Alice的。

在数字签名中,生成签名和验证签名这两个行为需要使用各自专用的密钥来完成。

Alice使用“签名密钥”来生成消息的签名,而Bob和Victor则使用“验证密钥”来验证消息的签名。数字签名对签名密钥和验证密钥进行了区分,使用验证密钥是无法生成签名的。这一点非常重要。此外,签名密钥只能由签名的人持有,而验证密钥则是任何需要验证签名的人都可以持有。

刚才讲的这部分内容,是不是觉得似曾相识呢?

没错,这就是我们讲过的非对称加密。公钥密码和上面讲的数字签名的结构非常相似。在非对称加密中,密钥分为加密密钥和解密密钥,用加密密钥无法进行解密。此外,解密密钥只能由需要解密的人持有,而加密密钥则是任何需要加密的人都可以持有。你看,数字签名和非对称加密是不是很像呢?

实际上,数字签名和非对称加密有着非常紧密的联系,简而言之,数字签名就是通过将非对称加密 “反过来用” 而实现的。下面我们来将密钥的使用方式总结成一张表:

私钥 公钥
非对称加密 接收者解密时使用 发送者加密时使用
数字签名 签名者生成签名时使用 验证者验证签名时使用
谁持有密钥? 个人持有 只要需要,任何人都可以持有




7.3





7.4





7.5





八、证书

“证书 – 为公钥加上数字签名”

要开车得先考驾照.驾照上面记有本人的照片、姓名、出生日期等个人信息.以及有效期、准驾车辆的类型等信息,并由公安局在上面盖章。我们只要看到驾照,就可以知道公安局认定此人具有驾驶车辆的资格。

公钥证书(Public-Key Certificate,PKC)其实和驾照很相似,里面记有姓名、组织、邮箱地址等个人信息,以及属于此人的公钥,并由认证机构(Certification Authority、Certifying Authority, CA)施加数字签名。只要看到公钥证书,我们就可以知道认证机构认定该公钥的确属于此人。公钥证书也简称为证书(certificate)。

可能很多人都没听说过认证机构,认证机构就是能够认定 “公钥确实属于此人”,并能够生成数字签名的个人或者组织。认证机构中有国际性组织和政府所设立的组织,也有通过提供认证服务来盈利的一般企业,此外个人也可以成立认证机构。

8.1 证书的应用场景

下面我们来通过证书的代表性应用场景来理解证书的作用。

下图展示了Alice向Bob发送密文的场景,在生成密文时所使用的Bob的公钥是通过认证机构获取的。

认证机构必须是可信的,对于“可信的第三方”,下图中会使用Trent这个名字,这个词是从trust(信任)一词演变而来的。





九、SSL/TLS

“SSL/TLS — 为了更安全的通信”

本章中我们将学习SSL/TLS的相关知识。

SSL/TLS是世界上应用最广泛的密码通信方法。比如说,当在网上商城中输人信用卡号时,我们的Web浏览器就会使用SSL/TLS进行密码通信。使用SSL/TLS可以对通信对象进行认证,还可以确保通信内容的机密性。

SSL/TLS中综合运用了之前所学习的对称密码、消息认证码、公钥密码、数字签名、伪随机数生成器等密码技术,大家可以在阅读本章内容的同时对这些技术进行复习。严格来说,SSL(Secure Socket Layer)与TLS(Transport Layer Security)是不同的,TLS相当于是SSL的后续版本。不过,本章中所介绍的内容,大多是SSL和TLS两者兼备的,因此除具体介绍通信协议的部分以外,都统一写作SSL/TLS。

9.1 客户端与服务器

Bob书店是Alice经常光顾的一家网店,因为在Bob书店她可以搜索到新出版的图书,还可以通过信用卡快速完成支付,购买的书还能快递到家,真的很方便。

有一天,Alice 读了一本关于网络信息安全的书,书上说“互联网上传输的数据都是可以被窃听的”。Alice感到非常担心,自己在购买新书的时候输人的信用卡号会不会被窃听呢?

Alice看到Bob书店的网站下面写着一行字:“在以https://开头的网页中输人的信息将通过SSL/TLS发送以确保安全”。

的确,输人信用卡号的网页的URL是以 https:// 开头的,而不是一般的 http://。此外.在浏览这个网页时,Alice的web浏览器上还会显示一个小锁头的图标,看上去好像挺安全的。

但Alice心想,就算写着“通过SSL/TLS发送”我也不放心啊,到底在我的Web浏览器和Bob书店的网站之间都发生了哪些事呢?

本章将要介绍的技术一一SSL/TLS就可以解答Alice的疑问。当进行SSL/TLS通信时,Web浏览器上就会显示一个小锁头的图标。


Alice的Web浏览器(客户端)和Bob书店的网站(服务器)进行HTTP通信

Alice和Bob书店之间的通信,实际上是Alice所使用的Web浏览器和Bob书店的Web服务器之间的通信。Web浏览器是Alice的计算机上运行的一个程序,而web服务器则是在Bob书店的计算机上运行的一个程序,它们都遵循一种叫作HTTP(Hyper Text Transfer Protocol, 超文本传输协议)的协议(protocol)来进行通信。其中,Web浏览器称为HTTP客户端,Web服务器称为HTTP服务器。

当Alice点击网页上的链接或者输人URL时,Web浏览器就会通过网络向Web服务器发送一个 “我要浏览这个网页“,的请求(request)。

Web服务器则将请求的网页内容发送给Web浏览器,以便对请求作出响应(response)。服务器和客户端之间所进行的处理就是请求和响应的往复。HTTP可以认为是在HTTP客户端与HTTP服务器之间进行请求和响应的规范。


Alice向Bob书店发送信用卡号也是使用HTTP来完成的(下图)。Alice输人信用卡号之后按下提交按钮,这时客户端(Web浏览器)就会将信用卡号作为HTTP请求发送给服务器。服务器则会将“生成订单”的网页作为HTTP响应返回给客户端。

不过,如果直接发送请求的话,信用卡号就很可能被窃听。下一节我们将探讨针对这种风险的对策。





9.2 用SSL/TLS承载HTTP

什么是SSL,什么是TLS呢?官话说SSL是安全套接层(secure sockets layer),TLS是SSL的继任者,叫传输层安全(transport layer security)。说白点,就是在明文的上层和TCP层之间加上一层加密,这样就保证上层信息传输的安全。如HTTP协议是明文传输,加上SSL层之后,就有了雅称HTTPS。它存在的唯一目的就是保证上层通讯安全的一套机制。

当Web浏览器发送信用卡号时,信用卡号的数据会作为客户端请求发送给服务器。如果通信内容被窃听者Eve所窃取,Eve就会得到信用卡号。

于是,我们可以用SSL(Secure Socket Layer)或者TLS(Transport Layer Security)作为对通信进行加密的协议,然后在此之上承載HTTP(下图)。通过将两种协议进行叠加,我们就可以对HTTP的通信(请求和响应)进行加密,从而防止窃听。通过SSL/TLS进行通信时,URL不是以http://开头,而是以https://开头。

以上就是SSL/TLS的简单介绍。


在大致了解了SSL/TLS之后,我们来整理一下SSL/TLS到底负责哪些工作。我们想要实现的是,通过本地的浏览器访问网络上的web服务器,并进行安全的通信。用上边的例子来说就是,Alice希望通过web浏览器向Bob书店发送信用卡号。在这里,我们有几个必须要解决的问题。

  • Alice的信用卡号和地址在发送到Bob书店的过程中不能被窃听。
  • Alice的信用卡号和地址在发送到Bob书店的过程中不能被篡改。
  • 确认通信对方的Web服务器是真正的Bob书店。

在这里,(1)是机密性的问题;(2)是完整性的问题;而(3)则是认证的问题。

要确保机密性,可以使用对称加密。由于对称加密算法的密钥不能被攻击者预测,因此我们使用伪随机数生成器来生成密钥。若要将对称加密的密钥发送给通信对象,可以使用非对称加密算法完成密钥交换。要识别篡改,对数据进行认证,可以使用消息认证码。消息认证码是使用单向散列函数来实现的。

要对通信对象进行认证,可以使用对公钥加上数字签名所生成的证书。

好,工具已经找齐了,下面只要用一个“框架”(framework)将这些工具组合起来就可以了。SSL/TIS协议其实就扮演了这样一种框架的角色。


SSL/TLS也可以保护其他的协议

刚才我们提到用SSL/TLS承载HTTP通信,这是因为HTTP是一种很常用的协议。其实SSL/TLS上面不仅可以承载HTTP,还可以承载其他很多协议。例如,发送邮件时使用的SMTP(Simple Mail Transfer Protocol, 简单邮件传输协议)和接收邮件时使用的POP3(Post Office Protocol,邮局协议)都可以用SSL/TLS进行承载。在这样的情况下,SSL/TLS就可以对收发的邮件进行保护。

用SSL/TLS承载HTTP、SMTP和POP3的结构如下图所示。一般的电子邮件软件都可以完成发送和接收邮件这两种操作,其实是同时扮演了SMTP客户端和POP3客户端这两种角色。





9.3 https

http和https

HTTP协议:是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(TCP),用于从WWW服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少。

HTTPS:是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL/TLS层,HTTPS的安全基础是SSL/TLS,因此加密的详细内容就需要SSL/TLS。

HTTPS协议的主要作用可以分为两种:

  • 建立一个信息安全通道,来保证数据传输的安全;
  • 确认网站的真实性。

HTTPS和HTTP的区别主要如下:

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。

2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。

3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

4、http的连接很简单,是无状态的;HTTPS协议是由SSL/TLS+HTTP协议构建的可进行加密传输、


https优点

尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:

  1. 使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器;
  2. HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
  3. HTTPS是现行架构下最安全的解决方案,虽然不是绝对安全,但它大幅增加了中间人攻击的成本。
  4. 谷歌曾在2014年8月份调整搜索引擎算法,并称 “比起同等HTTP网站,采用HTTPS加密的网站在搜索结果中的排名将会更高”。

https的缺点

虽然说HTTPS有很大的优势,但其相对来说,还是存在不足之处的:

  • HTTPS协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;
  • HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;
  • SSL/TLS证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
  • SSL/TLS证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
  • HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。




9.4 SSL/TLS 工作原理和详细握手过程

https://segmentfault.com/a/1190000021559557





十、其他

10.1 SSH

10.1.1 SSH 介绍

SSH(Secure Shell) 是一种加密的网络协议,用于在不安全的网络上安全地执行网络服务,例如远程登录到计算机系统。SSH 主要用于 Linux/Unix 系统下的远程管理和操作,它可以实现加密的数据传输,从而保证通信的安全性。


SSH 的主要特点

  1. 安全性
    • 加密通信:SSH 使用加密技术来保护数据在网络上的传输,防止数据被窃听或篡改。所有的通信(包括认证、命令传输和文件传输)都经过加密,通常使用对称加密(如 AES)和非对称加密(如 RSA)组合来提供安全性。
    • 身份验证:SSH 支持多种身份验证方式,包括密码验证和基于公钥的身份验证。公钥验证方式更为安全,是推荐的做法。
    • 完整性检查:SSH 使用消息验证码(MAC)来保证数据的完整性,防止数据在传输过程中被修改。
  2. 远程登录: SSH 允许用户通过网络远程登录到另一台机器。这是 SSH 最广泛使用的功能,特别是在服务器管理、云计算环境和 DevOps 中,用来管理远程系统。
  3. 端口转发: SSH 支持端口转发,即通过加密的 SSH 通道转发其他协议的数据。这可以用于安全地访问防火墙后面的服务,也可以用于建立 VPN 隧道等。
  4. 文件传输: SSH 还可以用于文件传输,支持SCP(Secure Copy)和 SFTP(SSH File Transfer Protocol)。这两种工具都基于 SSH 协议,可以在远程计算机之间安全地传输文件。
  5. 隧道功能(Tunneling): SSH 允许将通信数据通过安全的 SSH 隧道进行转发。这通常用于安全地传输不安全协议的数据,或者在中间跳板服务器的情况下,实现端口转发。




10.1.2 SSH 连接的详细过程

下面是 SSH 连接的详细过程,解释为什么是先密钥协商,后身份认证

1︎⃣ 建立连接(TCP 连接)

客户端首先与服务器通过 TCP 建立一个连接,默认使用 端口 22


2︎⃣ 密钥协商和加密通信

在连接建立后,SSH 会进行密钥协商,生成一个共享的会话密钥,用于加密接下来的通信数据。密钥协商确保了接下来所有数据的机密性,包括身份验证数据。

密钥协商的过程:

  1. 算法协商:客户端和服务器首先协商要使用的加密算法、消息认证码(MAC)算法、压缩算法等。
  2. Diffie-Hellman 密钥交换:SSH 通常使用 Diffie-HellmanElliptic Curve Diffie-Hellman (ECDH) 算法来进行密钥交换。这个过程可以确保客户端和服务器生成一个共享的对称密钥(会话密钥),即使攻击者能够监听通信,攻击者也无法获取这个密钥。
    • 客户端和服务器通过交换一些公开信息,协商出一个共享的对称密钥
    • 这个对称密钥用于加密后续的所有通信,包括身份验证。

为什么先进行密钥协商?

  • 在密钥协商之后,通信内容会被加密,确保数据的机密性和完整性。这样可以防止身份认证信息(例如密码、私钥签名等)在未加密的情况下被截获或篡改。
  • SSH 使用对称加密(如 AES)来加密实际的通信流量,对称加密的密钥是在密钥协商阶段生成的。

3︎⃣ 身份认证

在加密通道建立之后,SSH 开始进行身份认证。由于已经通过密钥协商建立了加密通道,身份认证的过程也是安全的,不会被第三方窃听。

常见的身份认证方式包括:

  1. 密码认证:用户输入密码,服务器对密码进行验证。因为此时通信是加密的,密码可以安全地在网络上传输。

  2. 基于公钥的认证:这是 SSH 推荐的身份认证方式。客户端使用私钥对一个消息进行签名,服务器验证这个签名是否与客户端的公钥匹配。如果匹配,身份验证通过。

    公钥认证的过程:

    • 服务器向客户端发送一个随机消息(挑战)。
    • 客户端使用自己的私钥对这个消息进行签名,并将签名发送回服务器。
    • 服务器使用客户端的公钥验证签名的正确性。如果验证通过,证明客户端拥有对应的私钥,认证通过。

4︎⃣ 会话建立与通信

一旦身份认证成功,SSH 会建立一个加密的会话,客户端可以向服务器发送命令、传输文件、执行远程操作等。所有的通信都在前面协商好的加密通道内进行,确保通信的安全性。