在 Go 里做 AES 加解密,真正容易出错的往往不是 aes.NewCipher 这一层,而是模式、填充和跨语言约定是否一致。本文按“先理解规则,再看代码落地”的顺序整理 AES 的基础概念、PKCS7Padding 的处理方式,以及 AES-ECB-PKCS7Padding 的完整示例,方便你快速判断 Go 实现怎样才能和 Java、PHP 等语言对得上。
AES 基础:先看块大小和密钥长度
AES 是密码学中的高级加密标准,英文全称为 Advanced Encryption Standard,也常被称为 Rijndael。它被用来替代更早期的 DES。
在使用 AES 前,有两个基础参数需要先明确:
- 数据块分组长度固定为
128bit - 密钥长度可以是
128bit、192bit、256bit
这意味着,无论你选择哪种工作模式,AES 处理数据时都以固定块大小运作;而密钥长度则决定你实际使用的是哪一档 AES 规格。实际开发里,如果你要和其他语言互通,首先要确认的就是双方是否使用了同样的密钥长度、模式和填充方式。
如果你希望直接使用现成封装,文中推荐的 Go 加解密库是 forgoer/openssl,项目地址为:https://github.com/forgoer/openssl。
这个库支持的加密模式包括:
- AES-ECB/AES-CBC
- DES-ECB/DES-CBC
- 3DES-ECB/3DES-CBC
- ...
填充怎么选:ZeroPadding、PKCS7Padding、PKCS5Padding
块加密算法有一个绕不开的问题:明文长度往往不是块大小的整数倍。此时就需要先做填充,再进入加密流程。常见的填充方式主要有三种。

ZeroPadding
ZeroPadding 的特点是:当数据长度不对齐时,用 0 进行补齐;如果原始数据本身已经对齐,则不再填充。
它实现简单,但在某些场景下不够稳妥,因为原文末尾如果本来就可能出现 0,解密后边界判断会变得不够明确。
PKCS7Padding
PKCS7Padding 是跨语言开发中更常见的一种方案。规则是:假设当前数据还差 n 个字节才能对齐,那么就补 n 个字节,并且每个字节的值都写成 n。

它还有一个很关键的细节:如果原始数据长度本身已经对齐,也要额外再补一整块,每个字节都等于块大小。正因为这个规则明确,解密时才能可靠地把填充去掉。
PKCS5Padding
PKCS5Padding 可以理解为 PKCS7Padding 的子集,区别在于它的块大小固定为 8 字节。遇到 AES 这类以 16 字节分组的算法时,实际开发里更常见的说法仍然是 PKCS7Padding。
Go 中的 PKCS7Padding 实现
下面这段代码给出了常见的填充与去填充方法,适合直接放进 Go 项目中使用:
func PKCS7Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
func PKCS7UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}
这段实现的核心很直接:
PKCS7Padding先计算还差多少字节补齐,再把对应数量的字节追加到原数据尾部PKCS7UnPadding则读取最后一个字节的值,把尾部补齐内容裁掉
工作模式决定加密过程怎么跑
AES 的工作模式,决定的是“明文块如何被处理成密文块”。同一个 AES 算法,在不同工作模式下,数据处理过程并不相同。
常见的五种工作模式包括:
- CBC
- ECB
- CTR
- CFB
- OFB
这篇文章重点展示的是 ECB 模式,因为后文示例使用的是 AES-ECB-PKCS7Padding 组合。
ECB 模式的 Go 实现
func ECBEncrypt(block cipher.Block, src, key []byte) ([]byte, error) {
blockSize := block.BlockSize()
encryptData := make([]byte, len(src))
tmpData := make([]byte, blockSize)
for index := 0; index < len(src); index += blockSize {
block.Encrypt(tmpData, src[index:index+blockSize])
copy(encryptData, tmpData)
}
return encryptData, nil
}
func ECBDecrypt(block cipher.Block, src, key []byte) ([]byte, error) {
dst := make([]byte, len(src))
blockSize := block.BlockSize()
tmpData := make([]byte, blockSize)
for index := 0; index < len(src); index += blockSize {
block.Decrypt(tmpData, src[index:index+blockSize])
copy(dst, tmpData)
}
return dst, nil
}
从代码结构看,ECB 的做法就是把数据按块切开,然后逐块调用 block.Encrypt 或 block.Decrypt。因此它对输入长度有明确要求:进入这一步之前,数据必须已经完成对齐,也就是必须先做填充。
AES-ECB-PKCS7Padding:一段完整的加解密示例
AES/ECB/PKCS7Padding 是实际项目里较常见的一组组合,尤其是在需要兼容历史系统或对接 Java、PHP 等现有实现时,经常会遇到。
下面是完整示例:
// 加密
src := []byte("123456")
key := []byte("1234512345123451")
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
src = PKCS7Padding(src, block.BlockSize())
dst, err := ECBEncrypt(block, src, key)
if err != nil {
panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(dst)) // SpfAShHImQhWjd/21Pgz2Q==
// 解密
src, err = ECBDecrypt(block, dst, key)
if err != nil {
panic(err)
}
src = PKCS7UnPadding(src)
fmt.Println(string(src)) // 123456
这段示例里有哪些关键点
- 明文是
"123456" - 密钥是
"1234512345123451",长度为16字节,对应128bit - 在进入 ECB 加密前,先调用
PKCS7Padding做补齐 - 加密结果再通过
base64.StdEncoding.EncodeToString转成字符串输出 - 示例输出密文为
SpfAShHImQhWjd/21Pgz2Q== - 解密后还要执行一次
PKCS7UnPadding,才能还原得到原始字符串123456
如果你在和 Java、PHP 对接时发现结果不一致,通常就从这几个点开始排查:密钥长度是否一致、模式是不是都为 ECB、填充是否同为 PKCS7Padding、输出编码是否都使用了 Base64。
落地时怎么判断能否兼容其他语言
Go 里做 AES 加解密,代码本身并不复杂,真正决定结果是否一致的是参数约定。只要块算法、密钥长度、工作模式、填充方式和编码输出完全一致,Go 通常就能和 Java、PHP 等语言的实现互通。
如果你不想自己维护底层细节,也可以直接参考或复用 forgoer/openssl 的完整实现。关于 Aes、Des 等加密的更多完整代码,可以查看:https://github.com/forgoer/openssl。













