为了账号安全,请及时绑定邮箱和手机立即绑定

Golang (Go) AES CBC 密文由于某种原因被填充了 16 个 0x00 字节

Golang (Go) AES CBC 密文由于某种原因被填充了 16 个 0x00 字节

Go
摇曳的蔷薇 2022-12-19 19:20:21
我正在 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: b8ae2fe8669c0401fb289e6ab6247924AES IV: e0332fc2a9743e4f从此处提取的代码摘录,但稍作修改: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 个字节。这说:传递大于 src 的 dst 是可以接受的,在这种情况下,CryptBlocks 只会更新 dst[:len(src)] 而不会触及 dst 的其余部分。但为什么会这样呢?密文的长度需要比明文的长度长,在线 AES 计算器证明了这一点。
查看完整描述

1 回答

?
慕村225694

TA贡献1880条经验 获得超4个赞

在线工具结果的密文,如果是明文:

aaaaaaaaaaaa{"key1": "value1", "key2": "value2"}

用 PKCS#7 填充,发布的密钥和 IV 是 UTF8 编码的。由于明文的大小(48 字节)已经是块大小(AES 为 16 字节)的整数倍,因此根据PKCS#7 填充规则填充一个完整的块,从而产生 64 字节的明文和密文。

从问题中不清楚使用的是哪种在线工具,但可以使用任何可靠的加密工具(例如 CyberChef,s。这个在线计算。CyberChef 默认为 AES/CBC 应用 PKCS#7 填充。


发布的代码会产生不同的密文,因为:

  1. 没有应用 PKCS#7 填充。这使得密文短了一个块(即最后一个块ccd202bac41937be75731f23796f1516丢失了)。

  2. aes.BlockSize + len(plaintext)为密文分配了字节大小。这导致分配的大小字节太大aes.BlockSize(即密文末尾包含 16 个 0x00 值)。

因此,要使 Go 代码生成与在线工具相同的密文,1. 必须添加 PKCS#7 填充,并且 2.len(plaintext)必须为密文分配仅字节的大小。

以下代码是一个可能的实现(对于 PKCS#7 ,使用pkcs7pad填充):

import (

    ...

    "github.com/zenazn/pkcs7pad"

)

...

key := []byte("b8ae2fe8669c0401fb289e6ab6247924")

iv := []byte("e0332fc2a9743e4f")

plaintext := []byte("aaaaaaaaaaaa{\"key1\": \"value1\", \"key2\": \"value2\"}")

plaintext = pkcs7pad.Pad(plaintext, aes.BlockSize)  // 1. pad the plaintext with PKCS#7

block, err := aes.NewCipher(key)

if err != nil {

    panic(err)

}

ciphertext := make([]byte, len(plaintext))          // 2. allocate len(plaintext)

mode := cipher.NewCBCEncrypter(block, iv)

mode.CryptBlocks(ciphertext, plaintext)

fmt.Printf("%x\n", ciphertext) // caf8fe667f4087e1b67d8c9c57fcb1f56b368cafb4bfecbda1e481661ab7b93d87703fb140368d3034d5187c53861c74ccd202bac41937be75731f23796f1516

请注意,由于 PKCS#7 填充,a不再需要显式填充。


上述代码中使用的静态IV 是一个漏洞,因为它会导致密钥/IV 对的重用,这是不安全的。因此,在实践中,通常会为每次加密生成一个随机 IV。IV 不是秘密的,是解密所必需的,并且通常与密文连接在一起。在解密端,将IV和密文分开,用于解密。

由于 IV 的大小对应于块大小,aes.BlockSize + len(plaintext)因此必须为密文分配一个大小,它等于原始代码中的大小。可能这不是偶然的,而是在设计时考虑了随机 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)   // create a random IV

if err != nil {

    panic(err)

}

mode := cipher.NewCBCEncrypter(block, iv)

mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

fmt.Printf("%x\n", ciphertext)

输出的前 16 个字节对应于(随机)IV,其余对应于实际密文。


查看完整回答
反对 回复 2022-12-19
  • 1 回答
  • 0 关注
  • 308 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号