Go 实现加密算法系列之对称加密
加密算法简介
加密算法就是加密的方法,在密码学中,加密是将明文信息隐藏起来,使之缺少特殊信息时不可读。加密算法可以分为两类:对称加密和非对称加密。
1. 对称加密
将信息使用一个密钥进行加密,解密时使用同样的密钥,同样的算法进行解密。
2. 非对称加密
非对称加密又称公开密钥加密,密钥分为私密密钥和公开密钥,通过公钥加密,私钥解密的过程,并且加密和解密算法不同。
关于对称加密
对称加密是加密和解密双方持有相同的私钥,即加密方通过密钥执行加密过程,解密方通过相同的密钥执行解密过程,因为双方持有相同的密钥,所以叫做对称加密。同时,因为密钥理论上只有加解密双方持有,所以也叫做私钥加密。常见的对称加密算法有:DES,3DES,AES 以及 RC4 等。
对称加密分为序列密码和分组密码两类,如下图:
1. 序列密码
序列密码单独加密每个位,它是通过将密钥序列中的每个位 与每个明文位相加实现的。同步序列密码的密码序列仅仅取决于密钥,而异步序列密码序列则取决于密钥和密文,如 RC4。
2. 分组密码
分组密码每次使用相同的密钥加密整个明文位分组。这意味着对给定分组内任何明文位的加密都依赖于与它同在一个分组内的其他所有明文位。绝大多数分组密码的分组长度要么是 128 位(16 字节),比如 AES,要么是 64 位(8 字节),比如 DES 和 3DES。
对称加密特点
-
算法可逆,即加密后可以通过解密还原数据。 -
加密效率高,加密速度快 -
安全性没有非对称加密高,因为密钥分发时存在安全隐患
对称加密算法
在写对称加密算法之前,有必要先提一下分组加密的操作模式有哪些,因为分组加密是对称加密中应用最多的加密方式,并且可以有不同的操作模式。
分组加密模式
1. 电子密码本模式(ECB)
ECB 模式要求明文长度必须是所使用密码的分组大小的整数倍,比如在 AES(后面会提到)中,明文长度应该是 16 字节的整数倍,如果不满足此长度要求,则必须对其进行填充,填充的方式有很多种,比如:在明文后附加单个"1",然后再根据需要附加足够多的"0"位,直到明文的长度达到分组长度的整数倍,如果明文的长度刚好是分组长度的整数倍,则填充的所有位刚好形成了一个单独的额外分组。ECB 加密解密如下图:
ECB 优缺点:
-
加密和解密之间的分组同步不是必须的,即如果数据丢失,接收方还是可能解密已收到的分组。 -
加密高度确定,即只要密钥不变,相同的明文分组总是产生相同的密文分组。 -
明文分组的加密是完全独立的,与前面的分组没有任何关系。如果攻击者将密文分组重新排序,有可能会得到有效的明文。 -
密码分组链接模式(CBC)
2. 密码分组链接模式(CBC)
CBC 模式主要基于两种思想。第一,所有分组的加密都链接在一起,是得密文不仅依赖,而且还依赖前面所有的明文分组。第二,加密过程使用的初始向量(IV)进行了随机化。
3. 输出反馈模式(OFB)
OFB 模式使用分组密码来构建一个序列密钥加密方案。注意,在 OFB 模式中,密钥序列不是按位产生,而是以分组方式产生。密码的输出是 b 个密钥序列位,其中 b 为所使用的分组密码的宽度;将 b 为的明文与该 b 位的密钥序列进行异或操作即可实现对明文的加密。如下图:
OFB 模式首先使用分组密码加密 IV,得到的密钥输出为 b 位密钥序列的第一个集合;将前一个密钥输出反馈给分组密码进行加密,即可计算出密钥序列位的下一个分组;不断重复此过程。因为使用的是同步序列密码,所以加密与解密操作完全相同,且加密解密位异或操作。OFB 的一个优点就是分组密码的计算与明文无关。因此,可以预计算密钥序列材料的一个或多个分组 S1。
4. 密码反馈模式(CFB)
CFB 模式也使用分组密码作为构建序列密码的基本元件,与 OFB 模式相同的是,CFB 也使用了反馈;而不同的是,OFB 反馈的是分组密码的输出,而 CFB 反馈的是密文。OFB 的基本思想是:要生成第一个密钥序列分组 S1,必须先加密 IV;而所有后续密钥序列分组 S2,S3,。。。都是通过加密前一个密文得到的。如下图所示:
根据序列密码加密,CFB 加密解密操作也完全相同,但 CFB 模式是一个异步序列密钥加密。
5. 计数器模式(CTR)
与 OFB 和 CFB 模式一样,密钥序列也是以分组方式计算的。分组密码的输入为一个计数器,每当分组密码计算一个新密钥序列分组时,该计数器都会产生一个不同的值,如下图所示:
初始化分组密码的输入时必须避免两次使用相同过的输入值,如果攻击者知道了使用相同输入加密的明文中的任何一个,就可以计算出密钥序列分组,从而可以解密其他明文。计数器模式最大的特点就是并行化,因为计数器模式不需要任何反馈。加入有两个分组加密引擎,即可以让两个引擎同时使用第一个分组密码加密计数器值 CTR1 和 CTR2,这两个分组密码引擎完成后,一个继续加密 CTR3,而另一个则加密 CTR4。
6. 伽罗瓦计数器模式(GCM)
GCM 是一种计算消息验证码的加密模式。MAC(消息验证码)由发送者计算密码校验和,并附加在消息后面,接收方也会计算此消息的密码校验和,并校验与发送者发送过来的是否相同,这样便可以确认消失是否是发送者发送过来的,且密文是否有被修改过。
主流对称加密算法实现(Go 语言)
下面将逐一介绍上面提到的对称加密的算法实现。
1. RC4
RC4 为流加密算法,即前面提到的序列加密,由伪随机数生成器和异或运算组成,密钥长度可变,范围在[1,255]。RC4 一个字节一个字节地加解密,由于采用的是异或运算,使用的是同一套算法。
func Encrypt(bytes []byte, key []byte) ([]byte, error) {
c, err := rc4.NewCipher(key)
if err != nil {
return nil, err
}
var dst = make([]byte, len(bytes))
c.XORKeyStream(dst, bytes)
return dst, nil
}
//RC4为序列密码加密,采用异或运算,所以此处直接调用解密算法
func Decrypt(encryptData, key []byte) ([]byte, error) {
return Encrypt(encryptData, key)
}
2. DES(3DES)
DES 是一种使用 56 位(7 字节)(注:其实 DES 的输入密钥是 64 位(8 字节),但是每个字节的第 8 位都作为前面 7 位的一个奇校验位,所以实际密钥有效位为 56 位。)密钥对 64 位(8 字节)长分组进行加密的算法,加、解密过程使用相同对密钥。
DES 算法机制为迭代,一共 16 轮,每轮操作完全相同,但使用不同的子密钥,子密钥都是由主密钥推导而来的,都是从输入密钥中选择 48 个置换位。DES 加密流程如下:
3DES 即为三重 DES 加密,3DES 加密密钥长度为 192 位(24 字节)。
解密过程主要为密钥编排逆转,即解密的第一轮需要子密钥 16;第二轮需要子密钥 15;以此类推。
func Encrypt(plainText []byte, key []byte) ([]byte, error) {
//block, err := des.NewTripleDESCipher(key)为三重DES加密
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize() //分组大小
plainText = paddingWithPKCS5(plainText, blockSize) //填充
var cipherText = make([]byte, len(plainText))
//ECB每个分组单独加密
var temp = cipherText
for len(plainText) > 0 {
block.Encrypt(temp, plainText[:blockSize])
plainText = plainText[blockSize:]
temp = temp[blockSize:]
}
return cipherText, nil
}
func Decrypt(cipherText []byte, key []byte) ([]byte, error) {
//block, err := des.NewTripleDESCipher(key)为三重DES加密
block, err := des.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize() //分组大小
var plainText = make([]byte, len(cipherText))
//ECB每个分组单独加密
var temp = plainText
for len(cipherText) > 0 {
block.Decrypt(temp, cipherText[:blockSize])
cipherText = cipherText[blockSize:]
temp = temp[blockSize:]
}
plainText = unPaddingWithPKCS5(plainText)
return plainText, nil
}
func paddingWithPKCS5(data []byte, blockSize int) []byte {
//需要填充的值以及数量
padding := blockSize - len(data)%blockSize
//组装填充值([]byte)
var paddingData = []byte{byte(padding)}
paddingData = bytes.Repeat(paddingData, padding)
//append填充
data = append(data, paddingData...)
return data
}
func unPaddingWithPKCS5(data []byte) []byte {
padding := int(data[len(data)-1])
return data[:len(data)-padding]
}
3. AES
AES 拥有三种不同的密钥长度抵抗蛮力攻击,分别为 128 位,192 位和 256 位,分组大小为 128 位。
func Encrypt(plainText []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize() //分组大小
plainText = paddingWithPKCS5(plainText, blockSize) //填充
var cipherText = make([]byte, len(plainText))
//CBC模式
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
blockMode.CryptBlocks(cipherText,plainText)
return cipherText, nil
}
func Decrypt(cipherText []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize() //分组大小
var plainText = make([]byte, len(cipherText))
//CBC模式
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
blockMode.CryptBlocks(plainText, cipherText)
plainText = unPaddingWithPKCS5(plainText)
return plainText, nil
}
引用资料
《维基百科》《深入浅出密码学——常用加密技术原理与应用》
最后
如有错误,请不吝赐教!Thanks!
附上代码链接:https://github.com/pyihe/secret。
推荐阅读
站长 polarisxu
自己的原创文章
不限于 Go 技术
职场和创业经验
Go语言中文网
每天为你
分享 Go 知识
Go爱好者值得关注