Golang AES加密解密工具实现:从原理到文件批量处理
AES(高级加密标准)是一种基于128位分组长度的对称加密算法,广泛应用于HTTPS协议、无线网络安全及各类数据存储场景。本文详细解析AES的底层工作原理,并展示如何使用Go语言的 crypto/aes 和 crypto/cipher 包构建一个支持文件流处理的加密工具,实现大文件的逐块加密与解密。
AES加密算法详解
AES属于对称加密体系,加密和解密使用相同的密钥。相较于早期的DES算法(56位密钥易被暴力破解),AES支持128、192和256位密钥长度,其安全性在目前的算力条件下被视为不可破解。
AES的主要应用场景
AES已广泛应用于各类需要数据安全的环境中:
- 网络通信:如HTTPS协议中利用AES保护数据传输。
- 无线网络:与WPA2协议配合保障WiFi连接安全。
- 应用程序:微信、支付宝等App利用其加密消息与支付凭证。
- 数据存档:7z、WinZip等压缩工具利用AES加密备份文件。
- 存储系统:部分操作系统文件系统通过AES确保磁盘数据安全。
AES加密内部流程
AES是对称分组加密,首先将明文按128bit(16字节)划分为数据块,不足部分采用 PKCS7Padding 等方法进行填充。每个加密轮次包含四个核心步骤:
1. 数据分块
将输入数据转换为4×4字节矩阵(状态矩阵)。对于最后一个数据块,如果长度不足128位,则需填充至指定长度。常见的填充方式包括PKCS7Padding、ZeroPadding等,解密时需根据填充规则移除多余字节。
| 标准 | 密钥长度 | 轮数 | 分组长度 |
|---|---|---|---|
| AES-128 | 128位 (16字节) | 10 | 128位 (16字节) |
| AES-192 | 192位 (24字节) | 12 | 128位 (16字节) |
| AES-256 | 256位 (32字节) | 14 | 128位 (16字节) |
2. 密钥扩展
AES通过密钥调度算法将初始密钥扩展为多个子密钥(轮密钥),用于每一轮的 AddRoundKey 操作。
3. 字节替代 (SubBytes)
利用预定义的S盒(Substitution-box)对状态矩阵中的每个字节进行非线性替换。
4. 行移位 (ShiftRows)
将状态矩阵的第一行保持不变,其余各行向左循环移位不同位数(第二行移1位,第三行移2位,第四行移3位)。
5. 列混淆 (MixColumns)
在有限域GF(2^8)上,对状态矩阵的每一列与固定矩阵进行乘法运算,以实现字节的扩散。
6. 加轮密钥 (AddRoundKey)
将当前状态矩阵与对应的轮密钥矩阵进行异或(XOR)运算。首轮之前加入初始密钥,最后一轮加密不执行MixColumns,但依然执行AddRoundKey。
AES常见工作模式
ECB (Electronic Codebook)
每个数据块独立加密。优点是计算简单高效,缺点是相同明文块加密后得到相同密文块,难以隐藏数据模式,安全性较低。
CBC (Cipher Block Chaining)
CBC模式引入了初始向量(IV)。第一个明文块先与IV异或后再加密,后续每个明文块与前一个密文块异或后再加密。这种方式使得即使明文块相同,加密出的密文也不同,安全性显著优于ECB。
Golang AES加密工具实现
下面通过Go语言提供的 crypto/aes 和 crypto/cipher 标准库,构建一个具有文件处理能力的AES加密工具 scode。
第1步:实现PKCS7填充与去填充
由于AES要求数据长度必须是块大小(16字节)的整数倍,因此在加密前需补足字节,解密后需去除填充:
// pkcs7Padding 填充
func pkcs7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
// pkcs7UnPadding 移除
func pkcs7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("加密字符串错误!")
}
unPadding := int(data[length-1])
return data[:(length - unPadding)], nil
}
第2步:实现CBC模式的加密与解密函数
使用 NewCBCEncrypter 和 NewCBCDecrypter 封装加密逻辑。密钥的前16个字节作为IV,剩余的字节作为AES密钥主体。
// AesEncrypt 加密
func AesEncrypt(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
encryptBytes := pkcs7Padding(data, blockSize)
crypted := make([]byte, len(encryptBytes))
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
blockMode.CryptBlocks(crypted, encryptBytes)
return crypted, nil
}
// AesDecrypt 解密
func AesDecrypt(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
crypted := make([]byte, len(data))
blockMode.CryptBlocks(crypted, data)
crypted, err = pkcs7UnPadding(crypted)
if err != nil {
return nil, err
}
return crypted, nil
}
第3步:文件流的分段加密
为避免内存溢出,针对大文件采用分块读取(例如每次读取100MB)的策略,加密后追加写入新文件。
func EncryptFile(fileName string) (err error) {
f, err := os.Open(fileName)
if err != nil {
fmt.Println("未找到文件")
return
}
defer f.Close()
fInfo, _ := f.Stat()
fLen := fInfo.Size()
fmt.Println("待处理文件大小:", fLen)
maxLen := 1024 * 1024 * 100 // 每次100mb
var forNum int64 = 0
getLen := fLen
if fLen > int64(maxLen) {
getLen = int64(maxLen)
forNum = fLen / int64(maxLen)
fmt.Println("需要加密次数:", forNum+1)
}
// 创建输出文件
ff, err := os.OpenFile("en_"+fileName, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
fmt.Println("文件写入错误")
return err
}
defer ff.Close()
// 循环加密并写入
for i := 0; i < int(forNum+1); i++ {
a := make([]byte, getLen)
n, err := f.Read(a)
if err != nil {
fmt.Println("文件读取错误")
return err
}
getByte, err := EncryptByAes(a[:n])
if err != nil {
fmt.Println("加密错误")
return err
}
getBytes := append([]byte(getByte), []byte("\n")...)
buf := bufio.NewWriter(ff)
buf.WriteString(string(getBytes[:]))
buf.Flush()
}
ffInfo, _ := ff.Stat()
fmt.Printf("加密后文件为:%s,文件大小为:%v Byte \n", ffInfo.Name(), ffInfo.Size())
return nil
}
第4步:整合命令行为工具
使用Cobra框架添加 encode 和 decode 命令。解密过程增加了简单的密码校验机制(硬编码示例密钥对比):
func(cmd *cobra.Command, args []string) {
copy(PwdKey, readPass())
Pwd := []byte("csgo!gogo")
if ByteSliceEqual(PwdKey, Pwd) {
// 组合成完整的16字节key并进行解密
PwdKey = append(PwdKey, 7, 3, 5, 5, 6, 0, 8)
if err := DecryptFile(args[0]); err != nil {
panic(err)
}
} else {
fmt.Println("密码错误")
os.Exit(1)
}
}
工具验证与使用
编译并运行工具 scode 测试文件加密与解密:
# 对 tar.gz 文件进行加密 $ ./scode encode xpower.tar.gz 待处理文件大小: 3397 加密后文件为:en_xpower.tar.gz,文件大小为:4545 Byte # 尝试不带密码解密(预期失败) $ ./scode decode en_xpower.tar.gz ENTER PASSWORD: 密码错误 # 输入正确密码解密 $ ./scode decode en_xpower.tar.gz ENTER PASSWORD: 待处理文件大小: 4545 解密后文件为:de_en_xpower.tar.gz,文件大小为:3159 Byte
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









