10

I am adding a go application to an already existing python codebase. I've been having trouble dealing with encryption between the languages. This is using go 1.2.1 and Python 2.7.x / PyCrypto 2.7a1.

Here is the Python sample:

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

And this is the Go sample:

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
}

Here is the golang link, I could not find a Python pastebin that had PyCrypto installed.

As suggested by the title & source, the two snippets produce different cyphertext:
Python: dbf6b1877ba903330cb9cf0c4f530d40bf77fe2bf505820e993741c7f698ad6b
Golang: db70cd9e6904359cb848410bfa38d7d0a47b594f7eff72d547d3772c9d4f5dbe

Both languages can decrypt their 'native' cypthertext, but neither can decrypt the others'. Because the python implementation already exists, I'm looking for a solution that will allow Go to decrypt cyphertext encrypted with the example PyCrypto AES settings & key size.

Daniel Drexler
  • 527
  • 4
  • 14

5 Answers5

10

Research on the current system has revealed that our python system uses CFB8 (8 bit segments). Go does not support this out of the box, but the source code used in the current CFBDecrypter / CFBEncrypter looks like it can be adapted fairly easily.

Daniel Drexler
  • 527
  • 4
  • 14
  • 7
    don't know if I should upvote you for stating that or downvote for the lost 30 minutes of my life trying to figure this one out :) – Not_a_Golfer May 27 '14 at 22:04
  • 2
    Take solace in the fact that I lost far more than 30 minutes of my life figuring it out :D – Daniel Drexler May 29 '14 at 00:14
  • Any pointers of what CFB# Go _does_ use? Got a similar issue "translating" from Go to C#. – Demonslay335 Mar 18 '17 at 01:55
  • I ended up being able to solve this. Go seems to be using a feedback of 128, but without padding, which C# complains about for size on decryption. Solution: pad the encrypted text yourself to nearest 128B (keep track of how many bytes you appended, simple 0 bytes works), then just remove that padding after decryption. Voila! – Demonslay335 Mar 18 '17 at 03:02
7

If anyone is looking for Go implementation of CFB mode with segment size = 8 you can use this:

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
}
Ilmari Karonen
  • 49,047
  • 9
  • 93
  • 153
kostya
  • 9,221
  • 1
  • 29
  • 36
  • 2
    I believe that weakens security properties by reducing the cycle length. It also introduces inefficiencies. You should probably use Go's implementation with its full feedback size and fix Python or Mcrypt. Also see [Cipher Feedback Mode](http://crypto.stackexchange.com/q/2476) on the Security Stack Exchange. – jww Nov 18 '16 at 14:30
  • 1
    @jww: For new systems, or if you're free to change the encryption scheme of an existing system, I agree. (In fact, I'd go further and suggest that you probably shouldn't use CFB mode at all; you should instead use CTR mode or, better yet, an [authenticated encryption](https://crypto.stackexchange.com/questions/12178/why-should-i-use-authenticated-encryption-instead-of-just-encryption) mode like [SIV](https://github.com/miscreant/miscreant/wiki/AES-SIV).) However, sometimes you do have to interoperate with legacy systems using cipher modes that you cannot change. – Ilmari Karonen Aug 17 '19 at 23:13
  • 1
    Ps. While [testing your code](https://repl.it/repls/GrowlingStarchyAbandonware) for [another question](https://stackoverflow.com/questions/57540370/how-to-properly-write-aes-cfb8-encryption-in-go), I noticed that your code was broken in encryption mode due to a typo (`= src[i]` instead of `= dst[i]`) in `XORKeyStream`. I've edited your answer to fix it, and it seems to work now (insofar as it now correctly decrypts its own ciphertext, and matches implementations in other languages). – Ilmari Karonen Aug 17 '19 at 23:33
6

It seems that the cipher can be made compatible to Go's crypto/cipher if we change segment_size of AES object from the default 8 to AES.block_size*8 (which is 128), like this:

Crypto.Cipher.AES.new(
            key=password, 
            mode=Crypto.Cipher.AES.MODE_CFB, 
            IV=iv,
            segment_size=AES.block_size*8
)
chaoslawful
  • 61
  • 1
  • 3
1

I found that easiest way to deal with this from Python side is to use M2Crypto library.

Final code looks like:

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

Works perfect without need to change something in Go.

CrazyCrow
  • 4,125
  • 1
  • 28
  • 39
-1

i solve by adapt python code like this (golang encode and python decode):

# 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)
ianwoolf
  • 99
  • 1
  • 4