3
\$\begingroup\$

While the task at hand is relatively straightforward, it turned out AES in Go is a bit clunky so I was wondering if this seems right. Also, any tips on Go coding in general are more than welcome.

// Package aesjson provides a function to encode an object to JSON and apply
// AES encryption to it.
package aesjson

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "encoding/json"
    "io"
    "log"
)

const (
    aesKey = "U3RhY2tPdmVyZmxvdyBmb3IgdGhlIHdpbiEgICAgICA="
)

var (
    aesCipher cipher.Block
)

func init() {
    aesKeyDecoded, err := base64.StdEncoding.DecodeString(aesKey)
    if err != nil {
        log.Fatalln(err)
    }
    aesCipher, err = aes.NewCipher(aesKeyDecoded)
    if err != nil {
        log.Fatalln(err)
    }
}

func spaces(c int) []byte {
    return bytes.Repeat([]byte(" "), c)
}

// EncodeJSON encodes the given object to JSON (via json.Marshal) and then
// encrypts it using the AES encryption algorithm.
// It returns the base64 of the encrypted JSON with the IV prepended to it.
func EncodeJSON(val interface{}) (string, error) {
    valjs, err := json.Marshal(val)
    if err != nil {
        return "", err
    }
    paddingLen := 16 - len(valjs)%16
    text := append(valjs, spaces(paddingLen)...)

    ciphertext := make([]byte, aes.BlockSize+len(text))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return "", err
    }

    mode := cipher.NewCBCEncrypter(aesCipher, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], text)
    return base64.StdEncoding.EncodeToString(ciphertext), nil
}

And this is my test file - really interested if this is the right approach to testing a function like this.

package aesjson_test

import (
    crand "crypto/rand"
    "log"
    "math/rand"
    "testing"

    "projectfolder/aesjson"
)

func init() {
    // Remove randomness
    crand.Reader = rand.New(rand.NewSource(1))

    log.Println("Randomness disabled!")
}

func TestEncode(t *testing.T) {
    input := &aesjson.Token{
        ID: "1234567890-1234567890",
    }
    want := "Uv38ByGCZU8WP18PmmIdcprw1O0tpU/WBGgDIBYBpYaoJ2Lay7VnJGhKzswMb7ndHORd4kaege0cnsKkHMogTA=="

    got, err := aesjson.EncodeJSON(input)
    if err != nil {
        t.Fatalf("failed with error: %v", err)
    }

    if got != want {
        t.Errorf("want: %q\ngot: %q", want, got)
    }
}
\$\endgroup\$
  • \$\begingroup\$ There are mode implementations that can add padding automatically. Not sure if you did it manually for a specific reason? I recommend this talk from gophercon 2016: youtube.com/… \$\endgroup\$ – hermancain Jan 24 '17 at 15:12
  • \$\begingroup\$ Interesting video - I am, however, limited by the decryption code, which uses a very limited library (that only supports CBC for example) and I'm padding with spaces to keep it valid JSON. That said, could you clarify which mode you're referring to? The BlockMode interface specifically states that "the length of src must be a multiple of the block size". \$\endgroup\$ – fstanis Jan 24 '17 at 20:39
  • \$\begingroup\$ GCM can encrypt streams of blocks and adds padding IIRC. This miight be informative, although it sparsely mentions GCM: stackoverflow.com/questions/1220751/… \$\endgroup\$ – hermancain Jan 24 '17 at 20:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.