4

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.

itx
  • 1,327
  • 1
  • 15
  • 38
  • Can you clarify your ultimate goal? At the start you state `I have encryption and decryption in Ruby and try to rewrite with Go.` - but at the end you want to `decrypt with decryption function in ruby`. Are you encrypting on the `go` side - and then want to decrypt in the `ruby` end? – colm.anseo Jul 13 '21 at 16:10
  • @colm.anseo yes.. if you see I told about step by step, I mean I can't do with big bang process, so I have successfully to use ruby for encrypt and use go for decrypt, and I want to try inverse of that, use go for encrypt and use ruby for decypt in the exact time – itx Jul 13 '21 at 16:14

2 Answers2

4

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.

colm.anseo
  • 19,337
  • 4
  • 43
  • 52
  • thank you, but how can I extract the tag? it seems that prepend the vector, if you see decryption in ruby, I need the tag to verify before calling cipher final, I can't find any reference in golang to extract the tag, and I think the tag is automatically append to cipher text in golang – itx Jul 12 '21 at 17:12
  • The auth tags are implicited added during encryption and verified during decryption. There's no need for the caller to do this, the `cipher` package does all the heavy lifting for you. – colm.anseo Jul 12 '21 at 20:13
  • absolutly I'm seeing in Golang we don't need that, but when I decrypt in ruby, you can see that needed for verification – itx Jul 13 '21 at 02:17
  • you mean I just extract with slice cipher_text with size 16 bytes – itx Jul 13 '21 at 16:01
  • the cipher_text is variable length - but the `nonce/IV` and `auth_tag` are fixed (or agreed upon at encyption type). Since they sandwich the `cipher_text` its easy to compute when looking at the entire payload length. I've update the answer to reflect this. – colm.anseo Jul 13 '21 at 17:53
3

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.

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • I thank you for explaining in detail, of couse I know if I use Go for decryption I don't need to exact the auth tag, but my case is I use Go with encryption and use Ruby for decryption, in Ruby I need to set auth tag before cipher final, so I need to exact the auth tag in Go and concatenate it with cipher – itx Jul 13 '21 at 16:04
  • You don't need to extract the auth tag in Go. You need to extract the auth tag in Ruby. And your Ruby code already does that: `auth_tag = raw_data.slice(raw_data.length - 16, 16)`. The tag will be there, Go puts it there automatically. – rustyx Jul 13 '21 at 23:23