Go AES CFB兼容性

4

我正在使用Go开发一个客户端应用程序,依赖于AES CFB算法。服务器端使用C语言编写。我的问题是,Go的AES CFB实现似乎与许多其他实现(包括OpenSSL)不同。我编写了以下代码来测试我的理论:

package main

import (
  "fmt"
  "encoding/hex"
  "crypto/cipher"
  "crypto/aes"
)

func encrypt_aes_cfb(plain, key, iv []byte) (encrypted []byte) {
  block, err := aes.NewCipher(key)
  if err != nil {
    panic(err)
  }
  encrypted = make([]byte, len(plain))
  stream := cipher.NewCFBEncrypter(block, iv)
  stream.XORKeyStream(encrypted, plain)
  return
}

func decrypt_aes_cfb(encrypted, key, iv []byte) (plain []byte) {
  block, err := aes.NewCipher(key)
  if err != nil {
    panic(err)
  }
  plain = make([]byte, len(encrypted))
  stream := cipher.NewCFBDecrypter(block, iv)
  stream.XORKeyStream(plain, encrypted)
  return
}

func main() {
  plain := []byte("Hello world.....")
  key := []byte("01234567890123456789012345678901")
  iv := []byte("0123456789012345")
  enc := encrypt_aes_cfb(plain, key, iv)
  dec := decrypt_aes_cfb(enc, key, iv)
  fmt.Println("Key: ", hex.EncodeToString(key))
  fmt.Println("IV:  ", hex.EncodeToString(iv))
  fmt.Println("Enc: ", hex.EncodeToString(enc))
  fmt.Println("In:  ", hex.EncodeToString(plain))
  fmt.Println("Out: ", hex.EncodeToString(dec))
}

当这段代码被运行时,看起来完美无缺,但是如果将加密后的字节复制到另一个AES实现中,并使用相同的密钥和IV进行解密,则明文会受损(除了第一字节)。 http://aes.online-domain-tools.com/ 提供了一种简单的测试方法。您有什么建议原因以及如何解决此问题吗?
谢谢 Steve

你好,这个问题解决了吗?我遇到了同样的错误。 - user998953
2个回答

4
首先,必须警告一下:CFB模式是自行开发的加密算法。除非您正在实现OpenPGP,否则应该使用像AES-GCM或NaCl的secretbox这样的AE模式。如果您被迫使用CFB模式,希望您至少使用HMAC对密文进行身份验证。
除此之外,在Go中使用CFB模式是为了支持OpenPGP。(OpenPGP在不同的地方使用了一个名为OCFB的调整后的CFB模式和标准的CFB模式。)Go OpenPGP代码似乎至少与其他实现相互操作。
Nick正确指出Go crypto包中缺少测试向量。测试来自OpenPGP代码,但是包应该独立存在,因此我将使用[1]第F.3.13节中的测试向量添加到crypto/cipher中的测试中。
我的最佳猜测是,CFB是由块大小参数化的。这通常是一个二进制位数,最多达到底层密码的块大小。如果没有指定块大小,则通常将其视为密码块大小,这就是Go代码所做的。请参见[1],第6.3节。[2]给出了更友好的解释。
在黑暗时代(90年代末),人们担心在丢失密文的情况下密码重新同步等问题,因此使用了较小的块大小。如果另一个实现正在使用CFB1或CFB8,则它与Go的CFB模式和许多其他模式非常不同。(Go的代码不支持较小的块大小。)
[1] http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf

[2] http://zh.wikipedia.org/wiki/分组密码工作模式#密码反馈(CFB)


谢谢Adam,我其实正要放弃这个项目。为了学习Go语言,我实现了原始的Mixmaster规范(使用3DES CBC)。那还好,但新的Mix规范支持大型RSA密钥,除了3DES之外还使用AES_CFB。现在它可能已经超出了合理的学习范畴,我应该放手并继续前进了! - Steve Crook

2

由于我不确定输入和输出的位/字节顺序,因此我使用以下输入进行了调查:

Key:  00000000000000000000000000000000
IV:   00000000000000000000000000000000
Enc:  66
In:   00
Out:  00

http://play.golang.org/p/wl2y1EE6lK

这与您提供的工具相匹配,然后是这个:

Key:  00000000000000000000000000000000
IV:   00000000000000000000000000000000
Enc:  66e94b
In:   000000
Out:  000000

http://play.golang.org/p/DNC42m2oU5

这个工具不匹配:

(注意,not匹配)

6616f9

http://aes.online-domain-tools.com/link/63687gDNzymApefh/

第一个字节匹配,这表明可能存在反馈问题。

因此,我检查了 Go 语言包的代码 并且认为这里存在一个 bug:这里

func (x *cfb) XORKeyStream(dst, src []byte) {
    for len(src) > 0 {
        if x.outUsed == len(x.out) {
            x.b.Encrypt(x.out, x.next)
            x.outUsed = 0
        }

        if x.decrypt {
            // We can precompute a larger segment of the
            // keystream on decryption. This will allow
            // larger batches for xor, and we should be
            // able to match CTR/OFB performance.
            copy(x.next[x.outUsed:], src)
        }
        n := xorBytes(dst, src, x.out[x.outUsed:])
        if !x.decrypt {
            copy(x.next[x.outUsed:], dst) // BUG? `dst` should be `src`
        }
        dst = dst[n:]
        src = src[n:]
        x.outUsed += n
    }
}

编辑

重新审视CFB模式后,发现Go的代码是正确的,所以可能其他实现存在问题。


1
有趣的是...在测试中也没有任何测试向量... - Nick Craig-Wood
1
另一个可能性是Go语言很好,而AES URL已经失效。我发现了一个服务器端的错误,因此我在脆弱的基础上构建了一个案例。 - Steve Crook
是的,实际上在仔细查看CFB后,它似乎应该从“dst”反馈,而且Go的代码看起来非常不错。 - Emil Davtyan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接