Skip to content

Instantly share code, notes, and snippets.

@kendru
Last active October 24, 2023 13:15
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save kendru/965f46013352ba0a560fb0fa6c19d7ed to your computer and use it in GitHub Desktop.
Save kendru/965f46013352ba0a560fb0fa6c19d7ed to your computer and use it in GitHub Desktop.
Decrypting AES-256-GCM encoded in Go from Node
// Usage: go run main.go | node decode.js
/*
// main.go:
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
func main() {
plaintext := "Quantum Magic"
key, err := base64.StdEncoding.DecodeString("MVYLJBSMa8YakAmm66f9carJp80r9S7hMRyQIWgzCPI=")
checkErr(err)
aesCypher, err := aes.NewCipher(key)
checkErr(err)
gcm, err := cipher.NewGCM(aesCypher)
checkErr(err)
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
checkErr(err)
encrypted := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
fmt.Print(base64.StdEncoding.EncodeToString(encrypted))
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
*/
// decode.js
const crypto = require('crypto')
const key = Buffer.from('MVYLJBSMa8YakAmm66f9carJp80r9S7hMRyQIWgzCPI=', 'base64');
const ivLength = 12;
const tagLength = 16;
function decode(input) {
const inputBuffer = Buffer.from(input, 'base64');
const iv = Buffer.allocUnsafe(ivLength);
const tag = Buffer.allocUnsafe(tagLength);
const data = Buffer.alloc(inputBuffer.length - ivLength - tagLength, 0);
inputBuffer.copy(iv, 0, 0, ivLength);
inputBuffer.copy(tag, 0, inputBuffer.length - tagLength);
inputBuffer.copy(data, 0, ivLength);
// console.log(iv.toString('hex'))
// console.log(spaces(iv) + data.toString('hex'))
// console.log(spaces(iv) + spaces(data) + tag.toString('hex'))
// console.log(inputBuffer.toString('hex'))
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)
decipher.setAuthTag(tag);
let dec = decipher.update(data, null, 'utf8');
dec += decipher.final('utf8');
return dec;
}
function spaces(b) {
return b.toString('hex')
.split('')
.map(() => ' ')
.join('');
}
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('readable', () => {
const chunk = process.stdin.read();
if (chunk !== null) input += chunk;
});
process.stdin.on('end', () => {
const decoded = decode(input);
console.log(`Input: ${input}`)
console.log(`Decoded: ${decoded}`)
});
@evgenybron
Copy link

👍

@tristancaron
Copy link

Thanks! Do you have an example of how to dow the other way around? (Node -> Go)

@akirabbq
Copy link

akirabbq commented Jul 2, 2020

Golang appends the 16 bytes long Tag at the end of the encrypted data. For Node -> Go, the trick is to call .getAuthTag() after .final() and append the Tag data:

Typescript:

function encryptAESGCM(key: Buffer, data: Buffer) {
  let algor: crypto.CipherGCMTypes;
  if (key.length === 16) {
    algor = "aes-128-gcm";
  } else if (key.length === 24) {
    algor = "aes-192-gcm";
  } else if (key.length === 32) {
    algor = "aes-256-gcm";
  } else {
    throw new Error("invalid AES key length");
  }
  const nonce = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv(algor, key, nonce);
  return Buffer.concat([nonce, cipher.update(data), cipher.final(), cipher.getAuthTag()]);
}

Go:

func DecryptAESGCM(key []byte, nonce []byte, data []byte) (decrypted []byte, err error) {
	var block cipher.Block
	block, err = aes.NewCipher(key)
	if err != nil {
		return
	}
	var aead cipher.AEAD
	aead, err = cipher.NewGCMWithNonceSize(block, len(nonce))

	if err != nil {
		return
	}
	decrypted, err = aead.Open(nil, nonce, data, nil)

	return
}

...
DecryptAESGCM(key, encrypted[0:12], encrypted[12:])

@aldulea
Copy link

aldulea commented Aug 5, 2021

Thanks 👍

@365553158
Copy link

Thanks! Do you have decode.go?

@Ne0t0N
Copy link

Ne0t0N commented Jul 7, 2022

func dec(secret, nonce, authTag, cipherText []byte) []byte {
	block, err := aes.NewCipher(secret)
	if err != nil {
		panic(err.Error())
	}

	aesgcm, err := cipher.NewGCMWithNonceSize(block, len(nonce))
	if err != nil {
		panic(err.Error())
	}

        // if Node code does not combine a cipherText with an authTag
	cipherTextWithTag := append(cipherText, authTag...)
	plaintext, err := aesgcm.Open(nil, nonce, cipherTextWithTag, nil)
	if err != nil {
		panic(err.Error())
	}

	fmt.Printf("Plain text: %s\n", plaintext)
	return plaintext
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment