Reputation: 1409
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
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
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
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