Golang实现AES加解密
在数据的传输过程,为了安全,我们需要对其进行加密。加密算法分为双向加密和单向加密。单向加密包括MD5、SHA等摘要算法,它们是不可逆的。双向加密包括对称加密和非对称加密,对称加密包括AES加密、DES加密等。双向加密是可逆的,它使用相同的密钥进行加密和解密。本文主要介绍AES算法以及如何使用golang实现AES加解密。
AES简介
AES是高级加密标准,在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,目前已经被全世界广泛使用,同时AES已经成为对称密钥加密中最流行的算法之一。AES支持三种长度的密钥:128位,192位,256位。
加解密过程为:字节代替、行移位、列混淆、轮密钥加。
相关概念:
-
密钥:AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用
-
分组: 把明文拆分成一个个独立的明文块,每一个明文块长度128bit。加密生成一个个独立的密文块,这些密文块拼接在一起。填充模式有:
- NoPadding :不做任何填充,但是要求明文必须是16字节的整数倍。
- PKCS5Padding: 如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字符,且每个字节的值等于缺少的字符数。
- ISO10126Padding: 如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字节,最后一个字符值等于缺少的字符数,其他字符填充随机数
-
加密模式:
- 电码本模式(Electronic Codebook Book (ECB)) 模式是将整个明文分成若干段相同的小段,然后对每一小段进行加密。
- 密码分组链接模式(Cipher Block Chaining (CBC)) 模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算,再与密钥进行加密
- 计算器模式(Counter (CTR))不常用,在 CTR 模式中, 有一个自增的算子,这个算子用密钥加密之后输出和明文异或的结果得到密文,相当于一次一密
- 密码反馈模式(Cipher FeedBack (CFB))不常用
- 输出反馈模式(Output FeedBack (OFB))
golang实现AES加解密
golang使用“crypto/aes”进行AES加解密 。接着我们通过一个使用CBC模式对原文进行加解密的例子来演示如何实现。
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
)
func main() {
// 原始数据
plaintext := []byte("Hello, world!")
// 密钥,长度必须为 16、24 或 32 字节(128位,192位,256位)
key := []byte("0123456789abcdef")
// 使用 AES 创建加密算法对象
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// 对数据进行填充,使其长度为块大小的整数倍
plaintext = PKCS5Padding(plaintext, block.BlockSize())
// 创建 CBC 模式的加密算法对象
iv := []byte("0123456789abcdef") // 初始化向量,长度必须为块大小
mode := cipher.NewCBCEncrypter(block, iv)
// 加密数据
ciphertext := make([]byte, len(plaintext))
mode.CryptBlocks(ciphertext, plaintext)
// 将密文进行 base64 编码
encoded := base64.StdEncoding.EncodeToString(ciphertext)
fmt.Println(encoded)
// 解码 base64 编码的密文
decoded, _ := base64.StdEncoding.DecodeString(encoded)
// 创建 CBC 模式的解密算法对象
mode = cipher.NewCBCDecrypter(block, iv)
// 解密数据
decrypted := make([]byte, len(decoded))
mode.CryptBlocks(decrypted, decoded)
// 去除填充数据
decrypted = PKCS5UnPadding(decrypted)
fmt.Println(string(decrypted))
}
// 对数据进行填充,使其长度为 blockSize 的整数倍
func PKCS5Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...)
}
// 去除填充数据
func PKCS5UnPadding(data []byte) []byte {
length := len(data)
unpadding := int(data[length-1])
return data[:(length - unpadding)]
}
输出:
+HzZQh0Do42Kg1PQsdhdcw==
Hello, world!
上例中,分别演示了
- 对加密过程:创建加密对象、原文填充、创建CBC模式,加密。
- 解密过程: 创建CBC解密模式,解密,去除填充。
为了方便使用,我们对上例的加解密进行封装,详细如下:
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
)
func main() {
plaintext := []byte("Hello, world!")
key := []byte("0123456789abcdef")
iv := []byte("0123456789abcdef")
// 加密数据
encoded, err := AesCBCEncrypt(plaintext, key, iv)
if err != nil {
panic(err)
}
fmt.Println(encoded)
// 解密数据
decoded, err := AesCBCDecrypt(encoded, key, iv)
if err != nil {
panic(err)
}
fmt.Println(string(decoded))
}
// AesCBCEncrypt 使用 AES CBC 模式加密数据
func AesCBCEncrypt(plaintext, key, iv []byte) (string, error) {
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
plaintext = PKCS5Padding(plaintext, block.BlockSize())
mode := cipher.NewCBCEncrypter(block, iv)
ciphertext := make([]byte, len(plaintext))
mode.CryptBlocks(ciphertext, plaintext)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// AesCBCDecrypt 使用 AES CBC 模式解密数据
func AesCBCDecrypt(ciphertext string, key, iv []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
decoded, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return nil, err
}
if len(decoded)%block.BlockSize() != 0 {
return nil, errors.New("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
decrypted := make([]byte, len(decoded))
mode.CryptBlocks(decrypted, decoded)
decrypted = PKCS5UnPadding(decrypted)
return decrypted, nil
}
// PKCS5Padding 对数据进行填充,使其长度为 blockSize 的整数倍
func PKCS5Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padtext...)
}
// PKCS5UnPadding 去除填充数据
func PKCS5UnPadding(data []byte) []byte {
length := len(data)
unpadding := int(data[length-1])
return data[:(length - unpadding)]
}
总结
本文主要介绍对称加密算法AES,AES使用不同指定长度的密钥,对原文进行分组,可选用更安全的加密模式CBC。在 golang中,可以使用“crypto/aes”快速实现原文填充、加密模式、加密、解密、去除填充等AES加解密过程。