Question: 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�Ñ����|�����>�B��B�  Encryption/Decryption in 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  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("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) } 

Answer: 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:  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.