7

Based on the Golang documentation on CFB decryption I wrote a minimal working example to decrypt a string that was encrypted with AES CFB and then base 64 encoded in python3.

The golang decryption works fine when the message was encrypted within Golang (with the encryption function from the Golang doc example). However when I encrypt the message in a python script using the python crypto package, I am unable to decrypt it in the golang script successfully. I don't get the right bytes back.

$ python3 stack.py 
Going to encrypt and base64 "This is not encrypted" result:
b'jf9A5LCxKWPuNb1XiH+G3APAgR//'

Now going to call the Golang script:
b'Hello from Golang, going to decrypt: jf9A5LCxKWPuNb1XiH+G3APAgR//
Result:  Tl!\xca/\xf1\xc0\xb2\xd01Y\x02V\xec\xdf\xecy\xd38&\xd9\n'

Blocksize is 16 by default for both AES implementations.

So the question: What is going wrong?

Golang script:

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "encoding/base64"
    "fmt"
    "os"

)

func main() {
    key := []byte("TfvY7I358yospfWKcoviZizOShpm5hyH")
    iv := []byte("mb13KcoviZizvYhp")
    payload_python := os.Args[1]

    fmt.Println("Hello from Golang, going to decrypt: "+payload_python+" Result: "+string(decrypt(key, payload_python, iv)))
}


func decrypt(key []byte, cryptoText string, iv []byte) []byte {
    ciphertext, _ := base64.StdEncoding.DecodeString(cryptoText)    //decode base64 coding

    //prepare decryption based on key and iv
    block, _ := aes.NewCipher(key)
    stream := cipher.NewCFBDecrypter(block, iv)

    //decrypt
    stream.XORKeyStream(ciphertext, ciphertext)

    return ciphertext
}

Python script:

 #!/usr/bin/env python3
import base64
from Crypto.Cipher import AES
from subprocess import check_output


original_message = 'This is not encrypted'

key = 'TfvY7I358yospfWKcoviZizOShpm5hyH'
iv = 'mb13KcoviZizvYhp'

#prepare encryption
cfb_cipher_encrypt = AES.new(key, AES.MODE_CFB, iv)
#encrypt and base64 encode
encryptedpayload = base64.b64encode(cfb_cipher_encrypt.encrypt(original_message))

print('Going to encrypt and base64 "{}" result:\n{}\n'.format(original_message,encryptedpayload))

print('Now going to call the Golang script:')
print(check_output('go run stack.go {}'.format(encryptedpayload.decode()),shell=True))
Sebastian
  • 5,471
  • 5
  • 35
  • 53
  • 1
    For one thing, you're using the base64 URL encoding to decode the payload in Go, but your output from Python is clearly base64 standard encoding (it contains slashes). – Adrian Jun 30 '17 at 15:32
  • 2
    You're also ignoring all errors in your Go code which is the first thing to fix if you run into a problem you can't easily solve. – Adrian Jun 30 '17 at 15:35
  • 1
    Good point @Adrian about the base64 standard encoding! I changed it, unfortunately to no avail. When I add code to `panic()` when an error is not `nil` I still get the same output. – Sebastian Jun 30 '17 at 15:48
  • When I do the same encryption in Go that you're doing in Python, I get `"jfsIXb8pmvFQa5B1u/06QpzqlWIt"`, not the same result, so I'm not surprised that decrypting what you get from Python in Go isn't working, though I've no idea why. My first instinct is a text encoding issue (Go uses UTF-8 strings and Python uses ASCII) but the key, IV, and secret are all in the basic ASCII range so I don't know if there's any merit to that. – Adrian Jun 30 '17 at 16:09
  • Also: Python2 might have used ASCII however Python3 defaults to UTF-8, so I agree, that doesn't seem to be the problem. Just now I found this SO post: https://stackoverflow.com/questions/25462314/go-aes-cfb-compatibility/ it looks like the same issue somewhat, but I don't know how to check the chunk size? Block size is 16 for both.. – Sebastian Jun 30 '17 at 16:20
  • Could it have anything to do with the fact that in Go, the underlying string type is actually defined as `type string []byte` ? Does Python3 operate on the string in bytes, or characters when doing the encryption? – RayfenWindspear Jun 30 '17 at 17:02
  • Seems to be related to padding and segment size, see my answer below – eugenioy Jun 30 '17 at 20:23

1 Answers1

5

Try encrypting from Python like this.

The result can then be unencrypted from Go successfully.

 #!/usr/bin/env python3
import base64
from Crypto.Cipher import AES

MODE = AES.MODE_CFB
BLOCK_SIZE = 16
SEGMENT_SIZE = 128

def _pad_string(value):
    length = len(value)
    pad_size = BLOCK_SIZE - (length % BLOCK_SIZE)
    return value.ljust(length + pad_size, '\x00')

def encrypt(key, iv, plaintext):
    aes = AES.new(key, MODE, iv, segment_size=SEGMENT_SIZE)
    plaintext = _pad_string(plaintext)
    encrypted_text = aes.encrypt(plaintext)
    return encrypted_text

key = 'TfvY7I358yospfWKcoviZizOShpm5hyH'
iv = 'mb13KcoviZizvYhp'
original_message = 'This is not encrypted'

encryptedpayload = base64.b64encode(encrypt(key, iv, original_message))

print('Going to encrypt and base64 "{}" result:\n{}\n'.format(original_message,encryptedpayload))

Source: http://chase-seibert.github.io/blog/2016/01/29/cryptojs-pycrypto-ios-aes256.html

eugenioy
  • 11,825
  • 28
  • 35
  • Yes it works! `Hello from Golang, going to decrypt: jfsIXb8pmvFQa5B1u/06QpzqlWItvB70V1204lf+5gw= Result: This is not encrypted\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00` So they both have different default implementation, is there one of them that is more common? Could I also adjust the Go parameters so that it decrypts the default python AES CFB encryption successful? Or would that involve changing the source code? – Sebastian Jul 01 '17 at 09:23
  • From what the link I included in the answer is saying, it seems the Go one is more interoperable, not sure how to tweak the Go parameters to adjust to the other one. – eugenioy Jul 01 '17 at 12:52
  • Watch out for a padding code. It may happen that your value will be padded with additional 16 bytes and that might not be correct behaviour. IMHO additional `module` ought to fix this issue. – Dáve Jul 03 '17 at 07:18
  • Ok to add some details to eugenioy his answer: the segment size is the cause of the mismatch. When you increasing the segment size in PyCrypto to 128 (the default of Go) you also need to pad your input to the block size (16) for a reason that is beyond me. If you really want to, you can also write code in Go to change the segment size to 8 (the default of PyCrypto) see: https://stackoverflow.com/a/37234233/2051646 however it seems that some implementations like CryptoJS don't support changing the segment size. – Sebastian Jul 03 '17 at 14:04