Golang (Go) AES CBC 密文由于某种原因被填充了 16 个 0x00 字节
Golang (Go) AES CBC ciphertext gets padded with 16 0x00 bytes for some reason
我正在 Golang (Go) 中测试 AES 256 CBC 实现。
plaintext: {"key1": "value1", "key2": "value2"}
因为明文是 36 B 并且需要是块大小 (16 B) 的倍数,所以我用 12 个随机字节手动填充到 48 B。
我知道这不是最安全的方法,但我只是在测试,我会找到更好的生产设置方法。
输入:
plaintext: aaaaaaaaaaaa{"key1": "value1", "key2": "value2"}
AES 256 key: b8ae2fe8669c0401fb289e6ab6247924
AES IV: e0332fc2a9743e4f
从here:
中提取的代码摘录,但稍作修改
block, err := aes.NewCipher(key)
if err != nil {
fmt.Println("Error creating a new AES cipher by using your key!");
fmt.Println(err);
os.Exit(1);
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, plaintext)
fmt.Printf("%x\n", ciphertext)
fmt.Println("len(ciphertext):",len(ciphertext))
CipherText = PlainText + Block - (PlainText MOD Block)
这个等式给出了 CBC 的密文长度。
因此,ciphertext := make([]byte, aes.BlockSize+len(plaintext))
行满足了这个要求,因为我的明文总是被填充为块大小的倍数。
问题:
使用 Go 我得到以下密文:
caf8fe667f4087e1b67d8c9c57fcb1f56b368cafb4bfecbda1e481661ab7b93d87703fb140368d3034d5187c53861c7400000000000000000000000000000000
无论明文长度如何,我总是在密文末尾得到 16 个 0x00 字节。
如果我用在线 AES 计算器做同样的事情,我会得到这个密文:
caf8fe667f4087e1b67d8c9c57fcb1f56b368cafb4bfecbda1e481661ab7b93d87703fb140368d3034d5187c53861c74ccd202bac41937be75731f23796f1516
前 48 个字节 caf8fe667f4087e1b67d8c9c57fcb1f56b368cafb4bfecbda1e481661ab7b93d87703fb140368d3034d5187c53861c74
相同。但我遗漏了最后 16 个字节。
This 说:
It is acceptable to pass a dst bigger than src, and in that case,
CryptBlocks will only update dst[:len(src)] and will not touch the
rest of dst.
但为什么会这样呢?密文的长度需要比明文的长度长,在线AES计算器证明。
由于 Go 代码中使用的 AES/CBC 实现不会隐式填充,代码仅在满足大小标准时才有效,即如果明文大小是块大小的整数倍(16 字节AES)。后者通过 explicit 填充 a
满足明文示例。在这些条件下,密文大小等于明文大小,即len(plaintext)
.
因为在Go代码中ciphertext
的大小是用aes.BlockSize+len(plaintext)
分配的,其中aes.BlockSize
是AES块大小(16字节),ciphertext
是16字节大于实际密文,这是最后 16 个 0x00 值的原因。要删除这些,ciphertext
的大小只需要分配 len(plaintext)
.
此外,由于明文一般不符合大小标准,因此需要添加padding。可靠的填充是 PKCS#7,它在 Go 中实现为例如pkcs7pad.
由于在线工具使用PKCS#7 padding,以下Go代码提供在线工具的结果:
import (
...
"github.com/zenazn/pkcs7pad"
)
...
key := []byte("b8ae2fe8669c0401fb289e6ab6247924")
iv := []byte("e0332fc2a9743e4f")
plaintext := []byte("aaaaaaaaaaaa{\"key1\": \"value1\", \"key2\": \"value2\"}")
plaintext = pkcs7pad.Pad(plaintext, aes.BlockSize)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
ciphertext := make([]byte, len(plaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, plaintext)
fmt.Printf("%x\n", ciphertext)
PKCS#7 如果已经满足大小标准,则填充会添加一个完整的块,如本例所示(因为使用 a
进行了显式填充),这就是密文较长的原因没有 PKCS#7 填充的密文。
请注意,由于 PKCS#7 填充,当然不再需要 a
的显式填充。
由于 key/IV 对的重用是不安全的,因此静态 IV(如代码中所示)也是不安全的。因此,为了避免这种情况,通常会为每次加密生成一个 random IV。 IV 不是秘密的,需要解密,因此通常与密文连接 (IV|ciphertext).
为此,必须用 aes.BlockSize+len(plaintext)
定义 ciphertext
的大小。因此,如果要使用随机生成的 IV 而不是静态 IV,则可以保留原始大小定义:
import (
...
"crypto/rand"
"io"
"github.com/zenazn/pkcs7pad"
)
...
key := []byte("b8ae2fe8669c0401fb289e6ab6247924")
plaintext := []byte("{\"key1\": \"value1\", \"key2\": \"value2\"}")
plaintext = pkcs7pad.Pad(plaintext, aes.BlockSize)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
fmt.Printf("%x\n", ciphertext)
在此实现中,前 16 个字节对应于 IV,其余为实际密文。在解密过程中,两部分必须分开。
我正在 Golang (Go) 中测试 AES 256 CBC 实现。
plaintext: {"key1": "value1", "key2": "value2"}
因为明文是 36 B 并且需要是块大小 (16 B) 的倍数,所以我用 12 个随机字节手动填充到 48 B。 我知道这不是最安全的方法,但我只是在测试,我会找到更好的生产设置方法。
输入:
plaintext: aaaaaaaaaaaa{"key1": "value1", "key2": "value2"}
AES 256 key: b8ae2fe8669c0401fb289e6ab6247924
AES IV: e0332fc2a9743e4f
从here:
中提取的代码摘录,但稍作修改block, err := aes.NewCipher(key)
if err != nil {
fmt.Println("Error creating a new AES cipher by using your key!");
fmt.Println(err);
os.Exit(1);
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, plaintext)
fmt.Printf("%x\n", ciphertext)
fmt.Println("len(ciphertext):",len(ciphertext))
CipherText = PlainText + Block - (PlainText MOD Block)
这个等式给出了 CBC 的密文长度。
因此,ciphertext := make([]byte, aes.BlockSize+len(plaintext))
行满足了这个要求,因为我的明文总是被填充为块大小的倍数。
问题:
使用 Go 我得到以下密文:
caf8fe667f4087e1b67d8c9c57fcb1f56b368cafb4bfecbda1e481661ab7b93d87703fb140368d3034d5187c53861c7400000000000000000000000000000000
无论明文长度如何,我总是在密文末尾得到 16 个 0x00 字节。
如果我用在线 AES 计算器做同样的事情,我会得到这个密文:
caf8fe667f4087e1b67d8c9c57fcb1f56b368cafb4bfecbda1e481661ab7b93d87703fb140368d3034d5187c53861c74ccd202bac41937be75731f23796f1516
前 48 个字节 caf8fe667f4087e1b67d8c9c57fcb1f56b368cafb4bfecbda1e481661ab7b93d87703fb140368d3034d5187c53861c74
相同。但我遗漏了最后 16 个字节。
This 说:
It is acceptable to pass a dst bigger than src, and in that case, CryptBlocks will only update dst[:len(src)] and will not touch the rest of dst.
但为什么会这样呢?密文的长度需要比明文的长度长,在线AES计算器证明。
由于 Go 代码中使用的 AES/CBC 实现不会隐式填充,代码仅在满足大小标准时才有效,即如果明文大小是块大小的整数倍(16 字节AES)。后者通过 explicit 填充 a
满足明文示例。在这些条件下,密文大小等于明文大小,即len(plaintext)
.
因为在Go代码中ciphertext
的大小是用aes.BlockSize+len(plaintext)
分配的,其中aes.BlockSize
是AES块大小(16字节),ciphertext
是16字节大于实际密文,这是最后 16 个 0x00 值的原因。要删除这些,ciphertext
的大小只需要分配 len(plaintext)
.
此外,由于明文一般不符合大小标准,因此需要添加padding。可靠的填充是 PKCS#7,它在 Go 中实现为例如pkcs7pad.
由于在线工具使用PKCS#7 padding,以下Go代码提供在线工具的结果:
import (
...
"github.com/zenazn/pkcs7pad"
)
...
key := []byte("b8ae2fe8669c0401fb289e6ab6247924")
iv := []byte("e0332fc2a9743e4f")
plaintext := []byte("aaaaaaaaaaaa{\"key1\": \"value1\", \"key2\": \"value2\"}")
plaintext = pkcs7pad.Pad(plaintext, aes.BlockSize)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
ciphertext := make([]byte, len(plaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, plaintext)
fmt.Printf("%x\n", ciphertext)
PKCS#7 如果已经满足大小标准,则填充会添加一个完整的块,如本例所示(因为使用 a
进行了显式填充),这就是密文较长的原因没有 PKCS#7 填充的密文。
请注意,由于 PKCS#7 填充,当然不再需要 a
的显式填充。
由于 key/IV 对的重用是不安全的,因此静态 IV(如代码中所示)也是不安全的。因此,为了避免这种情况,通常会为每次加密生成一个 random IV。 IV 不是秘密的,需要解密,因此通常与密文连接 (IV|ciphertext).
为此,必须用 aes.BlockSize+len(plaintext)
定义 ciphertext
的大小。因此,如果要使用随机生成的 IV 而不是静态 IV,则可以保留原始大小定义:
import (
...
"crypto/rand"
"io"
"github.com/zenazn/pkcs7pad"
)
...
key := []byte("b8ae2fe8669c0401fb289e6ab6247924")
plaintext := []byte("{\"key1\": \"value1\", \"key2\": \"value2\"}")
plaintext = pkcs7pad.Pad(plaintext, aes.BlockSize)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
iv := ciphertext[:aes.BlockSize]
_, err = io.ReadFull(rand.Reader, iv)
if err != nil {
panic(err)
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)
fmt.Printf("%x\n", ciphertext)
在此实现中,前 16 个字节对应于 IV,其余为实际密文。在解密过程中,两部分必须分开。