使用Go解密在Python中使用CFB模式加密的数据。

huangapple go评论83阅读模式
英文:

Decrypt in Go what was encrypted with AES in CFB mode in Python

问题

问题

我想要在Go语言中解密Python加密的内容。加密/解密函数在每种语言中分别工作,但当我在Python中加密并在Go中解密时,输出的结果是乱码:

Rx����d��I�K|�ap���k��B%F���UV�~d3h�����|�����>�B��B�

Python中的加密/解密

def encrypt(plaintext, key=config.SECRET, key_salt='', no_iv=False):
    """Encrypt shit the right way"""

    # sanitize inputs
    key = SHA256.new((key + key_salt).encode()).digest()
    if len(key) not in AES.key_size:
        raise Exception()
    if isinstance(plaintext, string_types):
        plaintext = plaintext.encode('utf-8')

    # pad plaintext using PKCS7 padding scheme
    padlen = AES.block_size - len(plaintext) % AES.block_size
    plaintext += (chr(padlen) * padlen).encode('utf-8')

    # generate random initialization vector using CSPRNG
    if no_iv:
        iv = ('\0' * AES.block_size).encode()
    else:
        iv = get_random_bytes(AES.block_size)
    log.info(AES.block_size)
    # encrypt using AES in CFB mode
    ciphertext = AES.new(key, AES.MODE_CFB, iv).encrypt(plaintext)

    # prepend iv to ciphertext
    if not no_iv:
        ciphertext = iv + ciphertext
    # return ciphertext in hex encoding
    log.info(ciphertext)
    return ciphertext.hex()


def decrypt(ciphertext, key=config.SECRET, key_salt='', no_iv=False):
    """Decrypt shit the right way"""

    # sanitize inputs
    key = SHA256.new((key + key_salt).encode()).digest()
    if len(key) not in AES.key_size:
        raise Exception()
    if len(ciphertext) % AES.block_size:
        raise Exception()
    try:
        ciphertext = codecs.decode(ciphertext, 'hex')
    except TypeError:
        log.warning("Ciphertext wasn't given as a hexadecimal string.")

    # split initialization vector and ciphertext
    if no_iv:
        iv = '\0' * AES.block_size
    else:
        iv = ciphertext[:AES.block_size]
        ciphertext = ciphertext[AES.block_size:]

    # decrypt ciphertext using AES in CFB mode
    plaintext = AES.new(key, AES.MODE_CFB, iv).decrypt(ciphertext).decode()

    # validate padding using PKCS7 padding scheme
    padlen = ord(plaintext[-1])
    if padlen < 1 or padlen > AES.block_size:
        raise Exception()
    if plaintext[-padlen:] != chr(padlen) * padlen:
        raise Exception()
    plaintext = plaintext[:-padlen]

    return plaintext

Go中的加密/解密

// PKCS5Padding adds padding to the plaintext to make it a multiple of the block size
func PKCS5Padding(src []byte, blockSize int) []byte {
	padding := blockSize - len(src)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(src, padtext...)
}

// Encrypt encrypts the plaintext,the input salt should be a random string that is appended to the plaintext
// that gets fed into the one-way function that hashes it.
func Encrypt(plaintext) string {
	h := sha256.New()
	h.Write([]byte(os.Getenv("SECRET")))
	key := h.Sum(nil)
	plaintextBytes := PKCS5Padding([]byte(plaintext), aes.BlockSize)
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	ciphertext := make([]byte, aes.BlockSize+len(plaintextBytes))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}
	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintextBytes)
	// return hexadecimal representation of the ciphertext
	return hex.EncodeToString(ciphertext)
}
func PKCS5UnPadding(src []byte) []byte {
	length := len(src)
	unpadding := int(src[length-1])
	return src[:(length - unpadding)]
}
func Decrypt(ciphertext string) string {

	h := sha256.New()
	// have to check if the secret is hex encoded
	h.Write([]byte(os.Getenv("SECRET")))
	key := h.Sum(nil)
	ciphertext_bytes := []byte(ciphertext)
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	log.Print(aes.BlockSize)
	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	iv := ciphertext_bytes[:aes.BlockSize]
	if len(ciphertext) < aes.BlockSize {
		panic("ciphertext too short")
	}
	ciphertext_bytes = ciphertext_bytes[aes.BlockSize:]
	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(ciphertext_bytes, ciphertext_bytes)
	plaintext := PKCS5UnPadding(ciphertext_bytes)
	return string(plaintext)
}
英文:

Issue

I want to be able to decrypt in Go what was encrypted in Python. The encrypting/decrypting functions work respectively in each language but not when I am encrypting in Python and decrypting in Go, I am guessing there is something wrong with the encoding because I am getting gibberish output:

Rx����d��I�K|�ap���k��B%F���UV�~d3h�&#209;����|�����&gt;�B��B�

Encryption/Decryption in Python

def encrypt(plaintext, key=config.SECRET, key_salt=&#39;&#39;, no_iv=False):
&quot;&quot;&quot;Encrypt shit the right way&quot;&quot;&quot;
# sanitize inputs
key = SHA256.new((key + key_salt).encode()).digest()
if len(key) not in AES.key_size:
raise Exception()
if isinstance(plaintext, string_types):
plaintext = plaintext.encode(&#39;utf-8&#39;)
# pad plaintext using PKCS7 padding scheme
padlen = AES.block_size - len(plaintext) % AES.block_size
plaintext += (chr(padlen) * padlen).encode(&#39;utf-8&#39;)
# generate random initialization vector using CSPRNG
if no_iv:
iv = (&#39;\0&#39; * AES.block_size).encode()
else:
iv = get_random_bytes(AES.block_size)
log.info(AES.block_size)
# encrypt using AES in CFB mode
ciphertext = AES.new(key, AES.MODE_CFB, iv).encrypt(plaintext)
# prepend iv to ciphertext
if not no_iv:
ciphertext = iv + ciphertext
# return ciphertext in hex encoding
log.info(ciphertext)
return ciphertext.hex()
def decrypt(ciphertext, key=config.SECRET, key_salt=&#39;&#39;, no_iv=False):
&quot;&quot;&quot;Decrypt shit the right way&quot;&quot;&quot;
# sanitize inputs
key = SHA256.new((key + key_salt).encode()).digest()
if len(key) not in AES.key_size:
raise Exception()
if len(ciphertext) % AES.block_size:
raise Exception()
try:
ciphertext = codecs.decode(ciphertext, &#39;hex&#39;)
except TypeError:
log.warning(&quot;Ciphertext wasn&#39;t given as a hexadecimal string.&quot;)
# split initialization vector and ciphertext
if no_iv:
iv = &#39;\0&#39; * AES.block_size
else:
iv = ciphertext[:AES.block_size]
ciphertext = ciphertext[AES.block_size:]
# decrypt ciphertext using AES in CFB mode
plaintext = AES.new(key, AES.MODE_CFB, iv).decrypt(ciphertext).decode()
# validate padding using PKCS7 padding scheme
padlen = ord(plaintext[-1])
if padlen &lt; 1 or padlen &gt; AES.block_size:
raise Exception()
if plaintext[-padlen:] != chr(padlen) * padlen:
raise Exception()
plaintext = plaintext[:-padlen]
return plaintext

Encryption/Decryption in Go

// PKCS5Padding adds padding to the plaintext to make it a multiple of the block size
func PKCS5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// Encrypt encrypts the plaintext,the input salt should be a random string that is appended to the plaintext
// that gets fed into the one-way function that hashes it.
func Encrypt(plaintext) string {
h := sha256.New()
h.Write([]byte(os.Getenv(&quot;SECRET&quot;)))
key := h.Sum(nil)
plaintextBytes := PKCS5Padding([]byte(plaintext), aes.BlockSize)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// The IV needs to be unique, but not secure. Therefore it&#39;s common to
// include it at the beginning of the ciphertext.
ciphertext := make([]byte, aes.BlockSize+len(plaintextBytes))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic(err)
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintextBytes)
// return hexadecimal representation of the ciphertext
return hex.EncodeToString(ciphertext)
}
func PKCS5UnPadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}
func Decrypt(ciphertext string) string {
h := sha256.New()
// have to check if the secret is hex encoded
h.Write([]byte(os.Getenv(&quot;SECRET&quot;)))
key := h.Sum(nil)
ciphertext_bytes := []byte(ciphertext)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
log.Print(aes.BlockSize)
// The IV needs to be unique, but not secure. Therefore it&#39;s common to
// include it at the beginning of the ciphertext.
iv := ciphertext_bytes[:aes.BlockSize]
if len(ciphertext) &lt; aes.BlockSize {
panic(&quot;ciphertext too short&quot;)
}
ciphertext_bytes = ciphertext_bytes[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext_bytes, ciphertext_bytes)
plaintext := PKCS5UnPadding(ciphertext_bytes)
return string(plaintext)
}

答案1

得分: 5

CFB模式使用的段大小对应于每个加密步骤加密的位数,参见CFB

Go语言仅支持128位的段大小(CFB128),至少在没有深层修改的情况下(参见这里这里)。相比之下,PyCryptodome中的段大小是可配置的,默认为8位(CFB8),参见这里。发布的Python代码使用了这个默认值,所以这两个代码是不兼容的。由于Go代码中的段大小不可调整,因此在Python代码中必须将其设置为CFB128:

cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128) 

此外,Python代码中的密文是十六进制编码的,因此在Go代码中必须进行十六进制解码,而这在发布的代码中尚未发生。

通过这两个更改,可以解密使用Python代码生成的密文。


以下Go代码中的密文是使用Python代码使用128位的段大小和密码短语my passphrase创建的,并成功解密:

package main

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

func main() {
	ciphertextHex := "546ddf226c4c556c7faa386940f4fff9b09f7e3a2ccce2ed26f7424cf9c8cd743e826bc8a2854bb574df9f86a94e7b2b1e63886953a6a3eb69eaa5fa03d69ba5" // 修复1:在Python端应用CFB128
	fmt.Println(Decrypt(ciphertextHex))                                                                                                                 // The quick brown fox jumps over the lazy dog
}

func PKCS5UnPadding(src []byte) []byte {
	length := len(src)
	unpadding := int(src[length-1])
	return src[:(length - unpadding)]
}
func Decrypt(ciphertext string) string {
	h := sha256.New()
	//h.Write([]byte(os.Getenv("SECRET")))
	h.Write([]byte("my passphrase")) // 从Python端应用密码短语
	key := h.Sum(nil)
	//ciphertext_bytes := []byte(ciphertext)
	ciphertext_bytes, _ := hex.DecodeString(ciphertext) // 修复2:十六进制解码密文
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	iv := ciphertext_bytes[:aes.BlockSize]
	if len(ciphertext) < aes.BlockSize {
		panic("ciphertext too short")
	}
	ciphertext_bytes = ciphertext_bytes[aes.BlockSize:]
	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(ciphertext_bytes, ciphertext_bytes)
	plaintext := PKCS5UnPadding(ciphertext_bytes)
	return string(plaintext)
}

安全性:

  • 使用摘要作为密钥派生函数是不安全的。应用专用的密钥派生函数,如PBKDF2。
  • 静态或缺失的盐也是不安全的。为每个加密操作使用随机生成的盐。将非秘密盐与密文(类似于IV)连接起来,例如salt|IV|ciphertext
  • 变体no_iv=True应用静态IV(零IV),这是不安全的,不应使用。正确的方法在变体no_iv=False中描述。
  • CFB是一种流密码模式,因此不需要填充/去填充,因此可以在两端都删除。
英文:

The CFB mode uses a segment size which corresponds to the bits encrypted per encryption step, see CFB.

Go only supports a segment size of 128 bits (CFB128), at least without deeper modifications (s. here and here). In contrast, the segment size in PyCryptodome is configurable and defaults to 8 bits (CFB8), s. here. The posted Python code uses this default value, so the two codes are incompatible. Since the segment size is not adjustable in the Go code, it must be set to CFB128 in the Python code:

cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128) 

Also, the ciphertext is hex encoded in the Python code, so it must be hex decoded in the Go code, which does not yet happen in the posted code.

With these both changes, the ciphertext produced with the Python code can be decrypted.


The ciphertext in the following Go Code was created with the Python code using a segment size of 128 bits and the passphrase my passphrase and is successfully decrypted:

package main

import (
	&quot;crypto/aes&quot;
	&quot;crypto/cipher&quot;
	&quot;crypto/sha256&quot;
	&quot;encoding/hex&quot;
	&quot;fmt&quot;
)

func main() {
	ciphertextHex := &quot;546ddf226c4c556c7faa386940f4fff9b09f7e3a2ccce2ed26f7424cf9c8cd743e826bc8a2854bb574df9f86a94e7b2b1e63886953a6a3eb69eaa5fa03d69ba5&quot; // Fix 1: Apply CFB128 on the Python side
	fmt.Println(Decrypt(ciphertextHex))                                                                                                                 // The quick brown fox jumps over the lazy dog
}

func PKCS5UnPadding(src []byte) []byte {
	length := len(src)
	unpadding := int(src[length-1])
	return src[:(length - unpadding)]
}
func Decrypt(ciphertext string) string {
	h := sha256.New()
	//h.Write([]byte(os.Getenv(&quot;SECRET&quot;)))
	h.Write([]byte(&quot;my passphrase&quot;)) // Apply passphrase from Python side
	key := h.Sum(nil)
	//ciphertext_bytes := []byte(ciphertext)
	ciphertext_bytes, _ := hex.DecodeString(ciphertext) // Fix 2. Hex decode ciphertext
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}
	iv := ciphertext_bytes[:aes.BlockSize]
	if len(ciphertext) &lt; aes.BlockSize {
		panic(&quot;ciphertext too short&quot;)
	}
	ciphertext_bytes = ciphertext_bytes[aes.BlockSize:]
	stream := cipher.NewCFBDecrypter(block, iv)
	stream.XORKeyStream(ciphertext_bytes, ciphertext_bytes)
	plaintext := PKCS5UnPadding(ciphertext_bytes)
	return string(plaintext)
}

Security:<br>

  • Using a digest as key derivation function is insecure. Apply a dedicated key derivation function like PBKDF2.
  • A static or missing salt is also insecure. Use a randomly generated salt for each encryption. Concatenate the non-secret salt with the ciphertext (analogous to the IV), e.g. salt|IV|ciphertext.
  • The variant no_iv=True applies a static IV (zero IV), which is insecure and should not be used. The correct way is described with the variant no_iv=False.
  • CFB is a stream cipher mode and therefore does not require padding/unpadding, which can therefore be removed on both sides.

huangapple
  • 本文由 发表于 2022年4月24日 17:43:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/71987196.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定