2
\$\begingroup\$

AES encryption in Go is already pretty simple, Gencrypt is a package that acts as a wrapper around Go's standard packages used for AES encryption to make it even more simple.

https://gitlab.com/zfeldt/gencrypt

go get gitlab.com/zfeldt/gencrypt

My questions are mostly centered around this part of the package (gencrypt.go):

// NOTE: This is a wrapper around portions of the standard libraries crypto
// package.

// NOTE: For those deploying on systems not equipped with CPUs supporting
// AES-NI [0], you should be aware of possible bottle-necks when it comes to
// the AES encryption process [1].
// >>"Final caveat, all these recommendations apply only to the amd64
// >> architecture, for which fast, constant time implementations of the crypto
// >> primitives (AES-GCM, ChaCha20-Poly1305, P256) are available. Other
// >> architectures are probably not fit for production use." [1]
// [0] https://en.wikipedia.org/wiki/AES_instruction_set#New_instructions
// [1] https://blog.gopheracademy.com/advent-2016/exposing-go-on-the-internet/

// Package gencrypt provides methods for encrypting and decrypting data with
// the AES encryption method. Based on George Tankersley's talk at Gophercon
// 2016.
package gencrypt

import (
        "crypto/aes"
        "crypto/cipher"
        "crypto/rand"
)

// Galois implements the cipher.AEAD interface type (Authenticated Encryption
// with Associated Data), which allows us to seal and open streams of data,
// check overhead, and check the nonce size.
type Galois struct {
        GCM cipher.AEAD
}

// NewGCM takes a key and returns a new Galois struct. A 32-byte key is used to
// indicate AES-256. 16 and 24-byte keys are accepted for AES-128 and AES-192
// respectively, but are not recommended.
func NewGCM(key []byte) (*Galois, error) {
        g := &Galois{}
        // Here we retrieve a new cipher.Block using the key provided. block is a
        // 128-bit block cipher (cipher.Block) used for encrypting and decrypting
        // data in individual blocks. The mode implementations (e.g. Galois Counter
        // Mode) extend that capability to streams of blocks.
        block, err := aes.NewCipher(key[:])
        if err != nil {
                return g, err
        }

        // We pass the cipher.Block to cipher.NewGCM() to retrieve a new GCM (Galois
        // Counter Mode).
        g.GCM, err = cipher.NewGCM(block)
        if err != nil {
                return g, err
        }

        // We return the Galois struct containing the GCM so that it can be used for
        // encryption and decryption by the client.
        return g, nil
}

// AESEncrypt is a method of the Galois struct which encrypts data using the
// mode (GCM) and returns an encrypted []byte.
func (g *Galois) AESEncrypt(data []byte) ([]byte, error) {
        // We use the gcm.NonceSize() method to create a byte slice with the
        // appropriate nonce length, then use the rand.Read() method to write random
        // bytes to the slice, thus creating our nonce.
        nonce := make([]byte, g.GCM.NonceSize())
        _, err := rand.Read(nonce)
        if err != nil {
                return nil, err
        }

        // gcm.Seal() returns a []byte containing the encrypted data. The nonce is
        // used both as the dst []byte, which encrypted data is appended to, and to
        // derive the initial GCM counter state (for more details see the
        // cipher/gcm.go file in the Go source code).
        return g.GCM.Seal(nonce, nonce, data, nil), nil
}

// AESDecrypt is a method of the Galois struct which decrypts data using the
// mode (GCM) and returns a decrypted []byte, which can be converted to a type
// (e.g. string) of the original data.
func (g *Galois) AESDecrypt(data []byte) ([]byte, error) {
        // We return the decrypted data by passing it through gcm.Open(). Remember:
        // the data argument contains the nonce at the beginning of the slice, and
        // has the encrypted data appended after it, as seen below. The decrypted
        // data is returned as a []byte that can then be converted into its original
        // form.
        return g.GCM.Open(nil, data[:g.GCM.NonceSize()], data[g.GCM.NonceSize():], nil)
}

You can see example usage on the gitlab readme linked at the top.

This is basically my first Go package and I'm wondering a few things:

  • Is this code considered idiomatic?
  • Currently the code is used by instantiating a new Galois struct with a key and calling the methods of the struct to encrypt and decrypt data. Originally it was designed with the encryption and decryption functions decoupled from the Galois struct, and the GCM was passed as an argument.

Current:

gcm, _ := gencrypt.NewGCM(key)
encryptedData, _ := gcm.AESEncrypt(data)

Old:

gcm, _ := gencrypt.NewGCM(key)
encryptedData, _ := gencrypt.AESEncrypt(gcm, data) // this line changed

Is there a better way? Is one way preferred to another? Does it matter? Also, do these different patterns have specific names?

  • Did I over-do the comments?

  • What are other obvious flaws that a potential employer might point out?

  • Taking a look at the rest of the package repo, test files, doc file, etc, are there any obvious improvements that can be made? I know I can add more tests and need to test AES128 and AES192.

\$\endgroup\$
  • \$\begingroup\$ Perhaps I could move the nonce creation code to the NewGCM function? Is it more secure to create the nonce each time I run Galois.AESEncrypt, or should I just create one nonce as a property of the Galois struct? \$\endgroup\$ – hermancain Jan 24 '17 at 15:26
1
\$\begingroup\$

This is a good reference for go crypto, have you seen it? I'd compare with that.

https://github.com/gtank/cryptopasta

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.