itx
itx

Reputation: 1409

Extract tag from cipher aes 256 GCM Golang

I have encryption and decryption in Ruby and try to rewrite with Go. I try step by step, so start with encryption in ruby and try to decryption in go, it's works. But when I try to write encryption in Go and decrypt in ruby. I'm stuck when try to extract the tag, I explain the reason why I need extract the auth tag

Encryption In ruby

plaintext = "Foo bar"
cipher = OpenSSL::Cipher.new('aes-256-gcm')
cipher.encrypt
cipher.iv = iv # string 12 len
cipher.key = key # string 32 len
cipher.auth_data = # string 12 len
cipherText = cipher.update(JSON.generate({value: plaintext})) + cipher.final
authTag = cipher.auth_tag
hexString = (iv + cipherText + authTag).unpack('H*').first

I try to concatenate an initial vector, a ciphertext and the authentication tag, so before decrypt I can extract them, especially the authentication tag, because I need set it before calling Cipher#final in Ruby

auth_tag

The tag must be set after calling Cipher#decrypt, Cipher#key= and Cipher#iv=, but before calling Cipher#final. After all decryption is performed, the tag is verified automatically in the call to Cipher#final

Here is function encryption in golang

ciphertext := aesgcm.Seal(nil, []byte(iv), []byte(plaintext), []byte(authData))
src := iv + string(ciphertext) // + try to add authentication tag here
fmt.Printf(hex.EncodeToString([]byte(src)))

How can I extract the authentication tag and concatenate it with iv and ciphertext, so I can decrypt with decryption function in ruby

raw_data = [hexString].pack('H*')
cipher_text = raw_data.slice(12, raw_data.length - 28)
auth_tag = raw_data.slice(raw_data.length - 16, 16)

cipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt
cipher.iv = iv # string 12 len
cipher.key = key # string 32 len
cipher.auth_data = # string 12 len
cipher.auth_tag = auth_tag
JSON.parse(cipher.update(cipher_text) + cipher.final)

I want to be able doing encryption in Go, and try to decryption in Ruby.

Upvotes: 5

Views: 4203

Answers (2)

colm.anseo
colm.anseo

Reputation: 22037

You want your encryption flow to be something like this:

func encrypt(in []byte, key []byte) (out []byte, err error) {

    c, err := aes.NewCipher(key)
    if err != nil {
        return
    }

    gcm, err := cipher.NewGCM(c)
    if err != nil {
        return
    }

    nonce := make([]byte, gcm.NonceSize())
    if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
        return
    }

    out = gcm.Seal(nonce, nonce, in, nil) // include the nonce in the preable of 'out'
    return
}

Your input key should be of 16, 24 or 32 bytes in length as per aes.NewCipher docs.

The encrypted out bytes from the above function will include the nonce prefix (16, 24 or 32 bytes in length) - so it can be easily extracted during the decryption stage like so:

// `in` here is ciphertext
nonce, ciphertext := in[:ns], in[ns:]

where ns is computed like so:

c, err := aes.NewCipher(key)
if err != nil {
    return
}

gcm, err := cipher.NewGCM(c)
if err != nil {
    return
}

ns := gcm.NonceSize()
if len(in) < ns {
    err = fmt.Errorf("missing nonce - input shorter than %d bytes", ns)
    return
}

EDIT:

If you are encrypting on the go side with defaults cipher settings (see above):

gcm, err := cipher.NewGCM(c)

from the source the tag byte-size will be 16.

Note: if cipher.NewGCMWithTagSize is used - then the size will obviously be different (basically anywhere between 12 to 16 bytes)

So lets assume the tag size is 16, armed with this knowledge, and knowing the full payload arrangement is:

IV/nonce + raw_ciphertext + auth_tag

the auth_tag on the Ruby side for decryption, is the final 16 bytes of the payload; and the raw_ciphertext is all bytes after the IV/nonce up until where the auth_tag begins.

Upvotes: 7

rustyx
rustyx

Reputation: 85361

aesgcm.Seal automatically appends the GCM tag at the end of the ciphertext. You can see it in the source:

    var tag [gcmTagSize]byte
    g.auth(tag[:], out[:len(plaintext)], data, &tagMask)
    copy(out[len(plaintext):], tag[:])                   // <---------------- here

So you're done, you don't need anything else. gcm.Seal already returns the ciphertext with the auth tag appended at the end.

Similarly you don't need to extract the auth tag for gcm.Open, it does it automatically, too:

    tag := ciphertext[len(ciphertext)-g.tagSize:]        // <---------------- here
    ciphertext = ciphertext[:len(ciphertext)-g.tagSize]

So all you have to do during decryption is to extract the IV (nonce) and pass the rest in as ciphertext.

Upvotes: 4

Related Questions