一、密码常识
1.1 信息安全面临的问题以及解决方案
记住:
- 不要过于依赖密码保证信息安全,因为任何密码都会有被破解的一天;
- 不要使用低强度的密码;
- 不要使用保密的密码算法;
- 信息安全是一个系统的问题,密码只是信息安全的一部分;
1.2 加密三要素
明文和密文:加密处理的数据。
秘钥(key):用于生成明文的一串数字。
算法(algorithm):用于解决复杂问题的步骤。从明文生成密文的步骤,也就是加密的步骤,称为“加密算法",而解密的步骤则称为“解密算法"。
1.3 凯撒密码
恺撒密码(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。通过下图我们可以很容易地理解“平移"的具体工作方式。
比如英文hello
,加密后的数据变为KHOOR
。
恺撒密码的解密过程是使用与加密时相同的密钥进行反向的平移操作。比如上面例子,只要反向平移3个字母就可以解密了。
二、对称加密
2.1 什么是对称加密
对称加密是指在加密和解密时使用相同的秘钥。
2.2 分组密码
一般来说,以分组为单位进行处理的密码算法称为分组密码(blockcipher)。一个分组的比特数就称为分组长度(blocklength)。例如,DES
和三重DES
的分组长度都是64比特。这些密码算法一次只能加密64比特的明文,并生成64比特的密文。
2.3 分组模式
分组密码算法只能加密固定长度的分组,但是我们需要加密的明文长度可能会超过分组密码的分组长度,这时就需要对分组密码算法进行迭代,以便将一段很长的明文全部加密。而迭代的方法就称为分组密码模式。
比较常见的分组模式有:ECB、CBC、CFB、OFB、CTR等模式。
2.3.1 ECB模式
ECB(Electronic Code Book, 电子密码本模式),是最简单的分组模式。明文消息被分成固定大小的块(分组),并且每个块被单独加密。如果最后一个明文分组的内容小于分组长度时,需要用一特定的数据进行填充(padding)。
特点: 每个块的加密和解密都是独立的,且使用相同的方法进行加密,所以可以进行并行计算,所以效率较高。但是,由于密文有规律,如果有一个块被破解,那么使用相同的方法可以解密其他块中的数据,所以安全性较低,容易被破解。
2.3.2 CBC模式
2.3.2.1 XOR
XOR的全称是exclusive or,在中文里叫作异或。由于XOR和加法运算很相似,因此一般用+和O组合而成的符号⊕来表示XOR。
如果两个比特序列执行异或运算,只要对其中每个相对应的比特进行XOR运算就可以了(如下图)。
由于两个相同的数进行XOR运算的结果一定为0,因此如果将A⊕B的结果再与B进行XOR运算,则结果会变回A。也就是说,两个公式中的B会相互抵消。
通过上面的运算公式与加密解密非常相似:
- 将明文A用密钥B进行加密,得到密文A⊕B
- 将密文A⊕B用密钥B进行解密,得到明文A
实际上,只要选择一个合适的B(相当于秘钥),仅仅使用XOR就可以实现一个高强度的密码。
2.3.2.2 CBC模式
CBC - Cipher Block Chaining, 密码块链模式,它是一种最常见的加密模式。CBC模式中的每一个分组要先和前一个分组加密后的数据进行XOR
异或操作,然后再进行加密。而第一个数据块需要用初始化向量IV
进行异或操作后再进行加密。
初始化向量:当加密第一个明文分组时,由于不存在前一个密文分组,因此需要事先准备一个长度为一个分组的比特序列来代替前一个密文分组,这个比特序列称为初始化向量(initialization vector),通常缩写为 IV。一般来说,每次加密时都会随机产生一个不同的比特序列来作为初始化向量。
特点:明文分组在加密之前一定会与前一个密文分组进行 XOR 运算,即便明文分组1和2的值是相等的,密文分组1和2的值也不一定是相等的。这样一来,ECB模式的缺陷在CBC模式中就不存在了。但是,由于CBC加密是连续的,不能并行处理,因此效率较低。
明文分组:是指分组密码算法中作为加密对象的明文。明文分组的长度与分组密码算法的分组长度是相等的。
密文分组:是指使用分组密码算法将明文分组加密之后所生成的密文。
如果将一个分组的加密过程分离出来,我们就可以很容易地比较出ECB模式和CBC模式的区别 。ECB模式只进行了加密,而CBC模式则在加密之前进行了一次XOR。
2.3.3 CFB模式
CFB - Cipher FeedBack, 密文反馈模式。在CFB模式中,前一个分组的密文被送回到密码算法
的输入端进行加密,然后再把加密后的秘钥流
和当前分组的明文进行异或操作生成当前分组的密文。
由密码算法所生成的比特序列称为
密钥流
。密码算法
就相当于用来生成密钥流的伪随机数生成器,而初始化向量就相当于伪随机数生成器的种子。
CFB模式的解密流程与加密流程是非常相似的。
通过对比CBC模式与CFB模式发现,在CBC模式中,明文分组和密文分组之间有XOR和密码算法两个步骤,而在CFB模式中,明文分组和密文分组之间则只有XOR。
在CFB模式中,由于密码算法的输出是通过计算得到的,并不是真正的随机数,因此CFB模式不可能具各理论上不可破译的性质。
2.3.4 OFB模式
OFB - Output-Feedback, 输出反馈模式。在OFB模式中,密码算法的输出会反馈到密码算法的输入中, 即上一个分组密码算法的输出是当前分组密码算法的输入。
和CBC模式、CFB模式一样,OFB模式中也需要使用初始化向量(IV)。一般来说,我们需要在每次加密时生成一个不同的随机比特序列用作初始化向量。
通过CFB和OFB模式对比发现,CFB和OFB模式的区别仅仅在于密码算法的输入。CFB式中,密码算法的输人是前一个密文分组,OFB模式中,密码算法的输入则是密码算法的前一个输出(如下图)。
相对地,在OFB模式中,XOR所需的密钥流可以事先通过密码算法生成,和明文分组无关。只要提前准备好所需的密钥流,就可以将明文与密钥流进行XOR操作即可,不需要动用密码算法。换个角度看,生成密钥流的操作和进行XOR运算的操作是可以并行的。
2.3.5 CTR模式
CTR - CounTeR, 计数器模式,它是一种通过将逐次累加的计数器进行加密来生成密钥流的流密码(如下图)。
CTR模式中,每个分组对应一个计数器,并通过对计数器进行加密来生成密钥流,然后再与明文分组进行XOR操作得到密文分组。
- 计数器生成方法:
每次加密时都会生成一个随机数(nonce)作为计数器的初始值。当分组长度为128比特(16字节)时,计数器的初始值的格式为:
其中,前面8个字节为nonce,每次加密都会生成一个新的none值。后面8位位分组序号,这部分会逐次累加。
由于计数器的值是不相同的,因此每个分组中将计数器进行加密所得到的密钥流也是不同的。也是说,这种方法就是用分组密码来模拟生成随机的比特序列。
如果我们将单个分组的加密过程拿出来,那么OFB模式和CTR模式之间的差异还是很容易理解的(下图)。
OFB模式是将加密的输出反愦到密码算法的输入,而CTR模式则是将计数器的值用作密码算法的输入。
由于CTR模式在加密和解密时所需要计数器的值可以有none和分组序号计算出来,因此它能够以任意顺序处理分组。这也就意味着在支持并行计算的系统中,CTR模式的速度是非常快的。
2.3.6 最后总结
模式 | 特点 | 备注 |
---|---|---|
ECB模式 |
|
不推荐使用 |
CBC模式 |
|
推荐使用 |
CFB模式 |
|
建议使用CTR模式代替 |
OFB模式 |
|
推荐使用CTR模式代替 |
CTR模式 |
|
推荐使用 |
2.4 对称加密在Go语言中的实现
2.4.1 加密实现思路
- 第一步:创建一个底层使用des/3des/aes的密码接口;
- 第二步:如果使用cbc/ecb分组模式,需要对明文分组进行填充;
- 第三步:创建一个密码分组模式的接口对象;
- 第四步:执行加密,得到密文;
2.4.2 解密实现思路
- 第一步:创建一个底层使用des/3des/aes的密码接口;
- 第二步:创建一个密码分组模式的接口对象;
- 第三步:执行解密操作;
- 第四步:去掉最后分组的填充数据;
2.5 对称加密技术
2.5.1 DES
DES(Data Encryption Standard)是1977年美国联邦信息处理标准(FIPS)中所采用的一种对称密码(FIPS46.3)。DES一直以来被美国以及其他国家的政府和银行等广泛使用。然而,随着计算机的进步,现在DES已经能够被暴力破解,因此在实际应用中不推荐使用。
特点:安全性低、对数据进行分组、分组长度为64bit,秘钥长度为56,其中有8位是错误检测标志位。
2.5.1.1 加密解密
DES是以64比特的明文为一组来进行加密的。尽管从规格上来说,DES的密钥长度是64比特,一般来说,以分组为单位进行处理的密码算法称为分组密码(blockcipher),DES就是分组密码的一种。DES每次只能加密64比特的数据,如果要加密的明文比较长,就需要对DES加密进行迭代。
由于DES每隔7比特会设置一个用于错误检查的比特位,因此,实质上其密钥长度为56比特。
2.5.1.2 CBC模式实现AES加密
// des加密
func desEncrypt(plainText, key []byte) []byte {
// 创建cipher.Block接口的实例
block, err := des.NewCipher(key)
if err != nil {
panic(err)
}
// 填充数据(每一块大小为8比特)
newText := paddingLastGroup(plainText, block.BlockSize())
// 初始化向量
iv := []byte("12345678")
// 创建BlockMode接口的实例
blockMode := cipher.NewCBCEncrypter(block, iv)
// 定义一个切片,用于存储密文数据
cipherText := make([]byte, len(newText))
// 将newText加密后存储到cipherText中
blockMode.CryptBlocks(cipherText, newText)
// 返回密文
return cipherText
}
// 填充数据
func paddingLastGroup(plainText []byte, blockSize int) []byte {
// 求出最后一个分组的字节数
padNum := blockSize - len(plainText) % blockSize
// padNum作为填充数据
data:= []byte{byte(padNum)}
// 重复产生padNum个填充数据data,并存储到切片中
newPlain := bytes.Repeat(data, padNum)
// 把填充数据与plainText进行合并
newText := append(plainText, newPlain...)
return newText
}
// 测试
func main() {
plainText := []byte("hello world hello go hello java")
key := []byte("87654321") // 秘钥
cipherText := desEncrypt(plainText, key)
fmt.Println("加密后:", cipherText)
}
2.5.1.3 CBC模式实现AES解密
// des解密
func desDecrypt(cipherText, key []byte) []byte {
// 1.创建cipher.Block接口的实例
block, err := des.NewCipher(key)
if err != nil {
panic(err)
}
// 2.创建BlockMode实例,
iv := []byte("12345678")
blockMode := cipher.NewCBCDecrypter(block, iv)
// 3.解密
plainText := make([]byte, len(cipherText))
blockMode.CryptBlocks(plainText, cipherText) // 该方法将plainText解密后保存在cipherText中
// 3.删除填充数据
newText := unPadddingLastGroup(plainText)
return newText
}
// 去掉最后一组的填充数据
func unPadddingLastGroup(plainText []byte) []byte {
// 获取明文的长度
length := len(plainText)
// 获取最后一个字节的值
lastChar := plainText[length - 1]
// 该值为填充数据的长度
num := int(lastChar)
// 从位置0开始一直截取到length - num位置
return plainText[:length - num]
}
// 测试
func main() {
fmt.Println("--------------cbc加密解密----------------")
plainText := []byte("hello world hello go hello java")
key := []byte("87654321") // 秘钥
cipherText := desEncrypt(plainText, key)
fmt.Println("加密后:", cipherText)
newText := desDecrypt(cipherText, key)
fmt.Println("解密后:", string(newText))
}
运行结果:
2.5.2 3DES
现在DES已经可以在现实的时间内被暴力破解,因此我们需要一种用来替代DES的分组密码,3DES就是出于这个目的被开发出来的。3DES是为了增加DES的强度,将DES重复3次所得到的一种密码算法(如下图)。
明文经过三次DES处理才能变成最后的密文,由于DES密钥的长度实质上是56比特,因此3DES的密钥长度就是56×3=168比特, 加上用于错误检测的标志位8x3, 共192
比特。
从上图我们可以发现,三重DES并不是进行三次DES加密(加密–>加密–>加密),而是加密–>解密–>加密的过程。在加密算法中加人解密操作让人感觉很不可思议,实际上这个方法是IBM公司设计出来的,目的是为了让三重DES能够兼容普通的DES。
当三重DES中所有的密钥都相同时,三重DES也就等同于普通的DES了。这是因为在前两步加密–>解密之后,得到的就是最初的明文。因此,以前用DES加密的密文,就可以通过这种方式用三重DES来进行解密。也就是说,三重DES对DES具备向下兼容性。
2.5.2.1 CBC模式实现3DES加密
// 3DES加密
func TripleDESEncrypt(plainText, key []byte) []byte {
// 创建并返回一个使用3DES算法的cipher.Block接口
block, err := des.NewTripleDESCipher(key)
if err != nil{
panic(err)
}
// 填充数据
plainText = paddingLastGroup(plainText, block.BlockSize())
// 定义初始化向量,可以随意指定
iv := []byte("12345678")
// 创建一个密码分组为链接模式, 底层使用3DES加密的BlockMode模型
blockMode := cipher.NewCBCEncrypter(block, iv)
// 加密数据
// 定义一个切片,用于存储密文数据
cipherText := make([]byte, len(plainText))
blockMode.CryptBlocks(cipherText, plainText)
return cipherText
}
2.5.2.2 CBC模式实现3DES解密
// 3DES解密
func TripleDESDecrypt(cipherText, key []byte) []byte {
// 创建3DES算法的Block接口对象
block, err := des.NewTripleDESCipher(key)
if err != nil{
panic(err)
}
// 初始化向量
iv := []byte("12345678")
// 创建密码分组为链接模式, 底层使用3DES解密的BlockMode模型
blockMode := cipher.NewCBCDecrypter(block, iv)
// 解密
plainText := make([]byte, len(cipherText))
blockMode.CryptBlocks(plainText, cipherText)
// 去掉尾部填充的数据
newText := unPadddingLastGroup(plainText)
return newText
}
// 测试
func main() {
plainText := []byte("hello world hello go hello java")
fmt.Println("--------------3DES加密解密----------------")
key := []byte("876543218765432187654321") // 秘钥长度为192比特位
cipherText := TripleDESEncrypt(plainText, key)
fmt.Println("加密后:", cipherText)
newText := TripleDESDecrypt(cipherText, key)
fmt.Println("解密后:", string(newText))
}
运行结果:
2.5.3 AES
AES(Advanced Encryption Standard)是取代其前任标准(DES)而成为新标准的一种对称密码算法。全世界的企业和密码学家提交了多个对称密码算法作为AES的候选,最终在2000年从这些候选算法中选出了一种名为Rijndae的对称密码算法,并将其确定为了AES。
AES密钥长度可以有128bit、192bit、256bit三种规格供用户选择。但是在Go语言提供的接口中秘钥长度只能是16个字节(128bit)。
2.5.3.1 加密解密
和DES—样,AES加密结果也是要经过多轮计算才能够得到的。
- 每一轮的大致计算步骤如下:
第一步:AES需要逐个字节地对16字节的输入数据进行SubBytes处理。所谓SubBytes,就是以每个字节的值(0~255中的任意值)为索引,从一张拥有256个值的替换表(S-Box)中查找出对应值的处理,也是说,将一个1字节的值替换成另一个1字节的值。
第二步:SubBytes之后需要进行ShiftRows处理,即将SubBytes的输出以字节为单位进行打乱处理。从下图的线我们可以看出,这种打乱处理是有规律的。
第三步:ShiftRows之后需要进行MixColumns处理,即对一个4字节的值进行比特运算,将其变为另外一个4字节值。
第四步:需要将MixColumns的输出与轮密钥进行XOR,即进行AddRoundKey处理。到这里为止,AES的一轮加密就已经结東了。
AES加密的SubBytes、ShiftRows、MixColumns操作都存在反向运算InvSubBytes、InvShiftRows、InvMixColumns。
- 每一轮解密的计算过程如下图:
2.5.3.2 CTR模式实现AES加密和解密
func aesEncryptAndDecrypt(data, key []byte) []byte {
// 创建cipher.Block接口的实例
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// 创建初始化向量,ctr每个分组占16个字节
iv := []byte("123456789abcdefg")
// 使用CTR模式创建Stream实例
stream := cipher.NewCTR(block, iv)
// 3.加密解密
newText := make([]byte, len(data))
stream.XORKeyStream(newText, data)
return newText
}
// 测试
func main() {
fmt.Println("--------------ctr加密解密----------------")
plainText := []byte("hello world hello go hello java")
key := []byte("1122334455667788")
cipherText := aesEncryptAndDecrypt(plainText, key)
fmt.Println("加密后:", cipherText)
newText := aesEncryptAndDecrypt(cipherText, key)
fmt.Println("解密后:", string(newText))
}
运行结果:
来源:CSDN
作者:伤心程序员
链接:https://blog.csdn.net/zhongliwen1981/article/details/104690541