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�����|�����>�B��B�
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
// 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)
}
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 (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
ciphertextHex := "546ddf226c4c556c7faa386940f4fff9b09f7e3a2ccce2ed26f7424cf9c8cd743e826bc8a2854bb574df9f86a94e7b2b1e63886953a6a3eb69eaa5fa03d69ba5" // 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("SECRET")))
h.Write([]byte("my passphrase")) // 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) < 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)
}
Security:
salt|IV|ciphertext.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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With