使用AES-CFB时,Go和Pycrypto产生不同的结果

10

我正在将一个Go应用程序添加到已存在的Python代码库中。 我一直在处理语言之间的加密问题。 这是使用go 1.2.1和Python 2.7.x / PyCrypto 2.7a1。

以下是Python示例:

import Crypto.Cipher
import Crypto.Hash.HMAC
import Crypto.Hash.SHA256
import Crypto.PublicKey.RSA
from binascii import hexlify, unhexlify

#encrypt
payload =  unhexlify("abababababababababababababababababababababababababababababababab")
password = unhexlify("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF")
iv = unhexlify("00000000000000000000000000000000")

print "IV: ", hexlify(iv), "len: ", len(iv)
print "Password length: ", len(password)


cipher = Crypto.Cipher.AES.new(
            key=password, 
            mode=Crypto.Cipher.AES.MODE_CFB, 
            IV=iv)

payload = cipher.encrypt(payload)

print hexlify(payload) #dbf6b1877ba903330cb9cf0c4f530d40bf77fe2bf505820e993741c7f698ad6b

以下是 Go 语言的示例代码:

package main

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

// encrypt
func main() {
    payload, err1 := hex.DecodeString("abababababababababababababababababababababababababababababababab")
    password, err2 := hex.DecodeString("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF")
    iv, err3 := hex.DecodeString("00000000000000000000000000000000")

    if err1 != nil {
        fmt.Printf("error 1: %v", err1)
        return
    }

    if err2 != nil {
        fmt.Printf("error 2: %v", err2)
        return
    }

    if err3 != nil {
        fmt.Printf("error 3: %v", err3)
        return
    }

    aesBlock, err4 := aes.NewCipher(password)

    fmt.Printf("IV length:%v\n", len(iv))
    fmt.Printf("password length:%v\n", len(password))

    if err4 != nil {
        fmt.Printf("error 4: %v", err4)
        return
    }

    cfbDecrypter := cipher.NewCFBEncrypter(aesBlock, iv)
    cfbDecrypter.XORKeyStream(payload, payload) 

    fmt.Printf("%v\n", hex.EncodeToString(payload)) // db70cd9e6904359cb848410bfa38d7d0a47b594f7eff72d547d3772c9d4f5dbe
}

这是golang链接,我找不到安装了PyCrypto的Python pastebin。

正如标题和来源所建议的那样,这两个代码片段产生不同的加密文本:
Python:dbf6b1877ba903330cb9cf0c4f530d40bf77fe2bf505820e993741c7f698ad6b
Golang:db70cd9e6904359cb848410bfa38d7d0a47b594f7eff72d547d3772c9d4f5dbe

两种语言都可以解密其'本地'的加密文本,但都无法解密另一种语言的加密文本。因为Python实现已经存在,所以我正在寻找一个解决方案,使Go能够解密使用示例PyCrypto AES设置和密钥大小加密的加密文本。

5个回答

10

研究显示,我们的 Python 系统目前使用的是 CFB8(8 位分段)。Go 本身不支持此功能,但当前的 CFBDecrypter / CFBEncrypter 源代码看起来相对容易适应。


7
不知道该给你点赞还是扣分,一方面感谢你说了出来,另一方面浪费了我30分钟的时间试图理解这个问题 :) - Not_a_Golfer
2
安慰自己的是,我浪费了远超过30分钟的时间来弄清楚它 :D - Daniel Drexler
有没有任何指针可以告诉CFB# Go使用了什么?我遇到了类似的问题,从Go转换到C#。 - Demonslay335
我最终解决了这个问题。Go似乎使用128的反馈,但没有填充,这会导致C#在解密时抱怨大小。解决方案:自己将加密文本填充到最近的128B(跟踪附加的字节数,简单的0字节即可),然后在解密后删除该填充。就这样! - Demonslay335

7

如果有人正在寻找分段大小为8的CFB模式的Go实现,可以使用以下代码:

import "crypto/cipher"

// CFB stream with 8 bit segment size
// See http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
type cfb8 struct {
    b         cipher.Block
    blockSize int
    in        []byte
    out       []byte

    decrypt bool
}

func (x *cfb8) XORKeyStream(dst, src []byte) {
    for i := range src {
        x.b.Encrypt(x.out, x.in)
        copy(x.in[:x.blockSize-1], x.in[1:])
        if x.decrypt {
            x.in[x.blockSize-1] = src[i]
        }
        dst[i] = src[i] ^ x.out[0]
        if !x.decrypt {
            x.in[x.blockSize-1] = dst[i]
        }
    }
}

// NewCFB8Encrypter returns a Stream which encrypts with cipher feedback mode
// (segment size = 8), using the given Block. The iv must be the same length as
// the Block's block size.
func newCFB8Encrypter(block cipher.Block, iv []byte) cipher.Stream {
    return newCFB8(block, iv, false)
}

// NewCFB8Decrypter returns a Stream which decrypts with cipher feedback mode
// (segment size = 8), using the given Block. The iv must be the same length as
// the Block's block size.
func newCFB8Decrypter(block cipher.Block, iv []byte) cipher.Stream {
    return newCFB8(block, iv, true)
}

func newCFB8(block cipher.Block, iv []byte, decrypt bool) cipher.Stream {
    blockSize := block.BlockSize()
    if len(iv) != blockSize {
        // stack trace will indicate whether it was de or encryption
        panic("cipher.newCFB: IV length must equal block size")
    }
    x := &cfb8{
        b:         block,
        blockSize: blockSize,
        out:       make([]byte, blockSize),
        in:        make([]byte, blockSize),
        decrypt:   decrypt,
    }
    copy(x.in, iv)

    return x
}

2
我认为通过减少循环长度来削弱安全性能,这也会引入低效率。你应该使用Go的实现,具有完整的反馈大小,并修复Python或Mcrypt。此外,请参阅Security Stack Exchange上的Cipher Feedback Mode。 - jww
1
@jww:对于新系统,或者如果您可以自由更改现有系统的加密方案,我同意。 (事实上,我会进一步建议您根本不应该使用CFB模式;相反,您应该使用CTR模式或者更好的是认证加密模式,如SIV。)然而,有时您必须与使用您无法更改的密码模式的旧系统进行交互操作。 - Ilmari Karonen
1
在测试您的代码以回答另一个问题时,我注意到您的代码在加密模式下由于XORKeyStream中的一个错别字(= src [i]而不是= dst [i])而无法正常工作。我已经编辑了您的答案以进行修复,现在它似乎可以正常工作(至少现在可以正确解密自己的密文,并且与其他语言中的实现相匹配)。 - Ilmari Karonen

6
似乎只要将AES对象的“segment_size”从默认值8更改为“AES.block_size*8”(即128),就可以使密码与Go的crypto/cipher兼容,代码如下:segment_size of AES object from the default 8 to AES.block_size*8。请注意保留HTML标记。
Crypto.Cipher.AES.new(
            key=password, 
            mode=Crypto.Cipher.AES.MODE_CFB, 
            IV=iv,
            segment_size=AES.block_size*8
)

我遇到了一个错误:ValueError: 输入字符串的长度必须是16的倍数 - 企业应用架构模式大师
你需要记得给你的输入填充。 - Daniel Goldberg
在CFB模式下,理论上不需要为输入进行任何填充,无论反馈大小如何。但如果pycrypto坚持这样做,您可以使用任意字节填充输入,然后在加密/解密后从输出的末尾删除相同数量的字节。 - Ilmari Karonen

1
我发现从Python方面处理这个问题最简单的方法是使用 M2Crypto 库。

最终代码如下:

import M2Crypto.EVP

iv = ciphertext[:16]
ciphertext = ciphertext[16:]

cipher = M2Crypto.EVP.Cipher('aes_256_cfb', t, iv, 0)
text = cipher.update(ciphertext)
print text

完美运行,无需更改Go中的任何内容。

-1
我通过调整 Python 代码来解决这个问题(使用 Golang 进行编码,Python 进行解码):
# golang encode
padNum := len(data) % 16
if padNum != 0 {
    for i := 0; i < 16-padNum; i++ {
        data = append(data, ',')
    }
}

# python decode
cipher = AES.new(key=self.key, mode=AES.MODE_CFB, IV=iv,segment_size=128)

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