Golang AES加密解密工具实现:从原理到文件批量处理

Chloe
Chloe
发布时间:2026-10-15 | 6人浏览过
原创

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-128128位 (16字节)10128位 (16字节)
AES-192192位 (24字节)12128位 (16字节)
AES-256256位 (32字节)14128位 (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
以上就是通过Go语言实现AES CBC模式加密及文件流处理的详细内容。该方案展示了如何结合Go的标准密码学库处理复杂的填充逻辑与大规模数据读写,更多关于Golang实用小工具的参考资料请关注本站其它相关文章!
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表