Golang AES ECB Encryption


Trying to emulate an algorithm in Go that is basically AES ECB Mode encryption.

Here’s what I have so far

  1. func Decrypt(data []byte) []byte {
  2. cipher, err := aes.NewCipher([]byte(KEY))
  3. if err == nil {
  4. cipher.Decrypt(data, PKCS5Pad(data))
  5. return data
  6. }
  7. return nil
  8. }

I also have a PKCS5Padding algorithm, which is tested and working, which pads the data first. I cant find any information on how to switch the encryption mode in the Go AES package (it’s definitely not in the docs).

I have this code in another language, which is how I know this algorithm isn’t working quite correctly.

EDIT: Here is the method as I have interpreted from on the issue page

  1. func AESECB(ciphertext []byte) []byte {
  2. cipher, _ := aes.NewCipher([]byte(KEY))
  3. fmt.Println("AESing the data")
  4. bs := 16
  5. if len(ciphertext)%bs != 0 {
  6. panic("Need a multiple of the blocksize")
  7. }
  8. plaintext := make([]byte, len(ciphertext))
  9. for len(plaintext) > 0 {
  10. cipher.Decrypt(plaintext, ciphertext)
  11. plaintext = plaintext[bs:]
  12. ciphertext = ciphertext[bs:]
  13. }
  14. return plaintext
  15. }

This is actually not returning any data, maybe I screwed something up when changing it from encripting to decripting

what does the error says ? do you have / can you provide an example playground ? - fabrizioM
You pad the plaintext before encryption, and you unpad it after. And you do that only for the last block, not anywhere in between. - Maarten Bodewes
@owlstead the implementation I saw actually padded the encrypted data before unencrypting…could this possibly be correct? github.com/martinp/pysnap/blob/master/pysnap/utils.py#L40 - Jameo
Do you specifically need ECB mode encryption here? What are you trying to encrypt/is there a necessary interop with an API? - elithrar
@Jameo No, that cannot be correct. It would mean that you add a full block of padding (as the ciphertext is always x times the blocksize). Thus the plaintext would consist of everything including the padding at the end and a block of random garbage. So it would decrypt, yes, but the result would not be correct. - Maarten Bodewes

ESB is a very straightforward mode of operation. The data to be encrypted is divided into byte blocks, all having the same size. For each block, a cipher is applied, in this case AES, generating the encrypted block.

The code snippet below decrypts AES-128 data in ECB (note that the block size is 16 bytes):

  1. package main
  2. import (
  3. "crypto/aes"
  4. )
  5. func DecryptAes128Ecb(data, key []byte) []byte {
  6. cipher, _ := aes.NewCipher([]byte(key))
  7. decrypted := make([]byte, len(data))
  8. size := 16
  9. for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
  10. cipher.Decrypt(decrypted[bs:be], data[bs:be])
  11. }
  12. return decrypted
  13. }

As mentioned by @OneOfOne, ECB is insecure and very easy to detect, as repeated blocks will always encrypt to the same encrypted blocks. This Crypto SE answer gives a very good explanation why.

Thanks for this, was helpful. Worth noting this isn’t restricted to 128-bit encryption - it will also work for 256-bit by passing in a 32 byte key instead of a 16 byte key. - Jarvis Johnson

ECB is left out intentionally because it’s insecure, check issue 5597.

good point, also if you follow the link, you will find the simple ECB mode implementation in Go - Kluyg
But, of course, you wouldn’t put that “simple ECB mode implementation” into any software (ever) because of its significant flaws, right? :) - elithrar
@elithrar definitely not for new software…this is a requirement for an existing API that I am trying to interact with - Jameo

I used your code so I feel the need to show you how I fixed it.

I am doing the cryptopals challenges for this problem in Go.

I’ll walk you through the mistake since the code is mostly correct.

  1. for len(plaintext) > 0 {
  2. cipher.Decrypt(plaintext, ciphertext)
  3. plaintext = plaintext[bs:]
  4. ciphertext = ciphertext[bs:]
  5. }

The loop does decrypt the data but does not put it anywhere. It simply shifts the two arrays along producing no output.

  1. i := 0
  2. plaintext := make([]byte, len(ciphertext))
  3. finalplaintext := make([]byte, len(ciphertext))
  4. for len(ciphertext) > 0 {
  5. cipher.Decrypt(plaintext, ciphertext)
  6. ciphertext = ciphertext[bs:]
  7. decryptedBlock := plaintext[:bs]
  8. for index, element := range decryptedBlock {
  9. finalplaintext[(i*bs)+index] = element
  10. }
  11. i++
  12. plaintext = plaintext[bs:]
  13. }
  14. return finalplaintext[:len(finalplaintext)-5]

What this new improvement does is store the decrypted data into a new []byte called finalplaintext. If you return that you get the data.

It’s important to do it this way since the Decrypt function only works one block size at a time.

I return a slice because I suspect it’s padded. I am new to cryptography and Go so anyone feel free to correct/revise this.

I was confused by a couple of things.

First i needed a aes-256 version of the above algorithm, but apparently the aes.Blocksize (which is 16) won’t change when the given key has length 32. So it is enough to give a key of length 32 to make the algorithm aes-256

Second, the decrypted value still contains padding and the padding value changes depending on the length of the encrypted string. E.g. when there are 5 padding characters the padding character itself will be 5.

Here is my function which returns a string:

  1. func DecryptAes256Ecb(hexString string, key string) string {
  2. data, _ := hex.DecodeString(hexString)
  3. cipher, _ := aes.NewCipher([]byte(key))
  4. decrypted := make([]byte, len(data))
  5. size := 16
  6. for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
  7. cipher.Decrypt(decrypted[bs:be], data[bs:be])
  8. }
  9. // remove the padding. The last character in the byte array is the number of padding chars
  10. paddingSize := int(decrypted[len(decrypted)-1])
  11. return string(decrypted[0 : len(decrypted)-paddingSize])
  12. }

1. AES has one block size of 16-bytes, it is not related to the key size. AES has three key sizes of 128, 192 & 256 bits. 2. The padding seen here is PKCS#7#PKCS7), padding is necessary if the data to be encrypted is not always a multiple of the block size. 3. Most AES implementations (the aes Go implementations does not) will handle input data longer than one block and automatically handle the block calls and padding. 4. Instead see crypto/cipher which does handle blocking and padding. - zaph