Skip to content

Instantly share code, notes, and snippets.

@brettscott
Last active January 26, 2024 08:01
  • Star 59 You must be signed in to star a gist
  • Fork 17 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save brettscott/2ac58ab7cb1c66e2b4a32d6c1c3908a7 to your computer and use it in GitHub Desktop.
AES 256 CBC encryption between Golang and Node JS
// Node v6.9.0
//
// TEST FILE (cut down for simplicity)
// To ensure Golang encrypted string can be decrypted in NodeJS.
//
let crypto;
try {
crypto = require('crypto');
} catch (err) {
console.log('crypto support is disabled!');
}
const ALGORITHM = 'aes-256-cbc';
const CIPHER_KEY = "abcdefghijklmnopqrstuvwxyz012345"; // Same key used in Golang
const BLOCK_SIZE = 16;
const plainText = "1234567890"; // This plainText was encrypted to make the cipherText below by Golang
const cipherText = "f17ba46472fa64e40ca496d1b4c91e8fac967926dfbdd7097b4c8f8ebd18f898"; // hexidecimal cipherText created by Golang
const decrypted = decrypt(cipherText);
if (decrypted !== plainText) {
console.log(`FAILED: expected ${plainText} but got "${decrypted}"`);
} else {
console.log(`PASSED: ${plainText}`);
}
// Decrypts cipher text into plain text
function decrypt(cipherText) {
const contents = Buffer.from(cipherText, 'hex');
const iv = contents.slice(0, BLOCK_SIZE);
const textBytes = contents.slice(BLOCK_SIZE);
const decipher = crypto.createDecipheriv(ALGORITHM, CIPHER_KEY, iv);
let decrypted = decipher.update(textBytes, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
// Encrypts plain text into cipher text
function encrypt(plainText) {
const iv = crypto.randomBytes(BLOCK_SIZE);
const cipher = crypto.createCipheriv(ALGORITHM, CIPHER_KEY, iv);
let cipherText;
try {
cipherText = cipher.update(plainText, 'utf8', 'hex');
cipherText += cipher.final('hex');
cipherText = iv.toString('hex') + cipherText
} catch (e) {
cipherText = null;
}
return cipherText;
}
// Golang v1.8
package blahblah
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"github.com/mergermarket/go-pkcs7"
"io"
)
// Cipher key must be 32 chars long because block size is 16 bytes
const CIPHER_KEY = "abcdefghijklmnopqrstuvwxyz012345"
// Encrypt encrypts plain text string into cipher text string
func Encrypt(unencrypted string) (string, error) {
key := []byte(CIPHER_KEY)
plainText := []byte(unencrypted)
plainText, err := pkcs7.Pad(plainText, aes.BlockSize)
if err != nil {
return "", fmt.Errorf(`plainText: "%s" has error`, plainText)
}
if len(plainText)%aes.BlockSize != 0 {
err := fmt.Errorf(`plainText: "%s" has the wrong block size`, plainText)
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
cipherText := make([]byte, aes.BlockSize+len(plainText))
iv := cipherText[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", err
}
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(cipherText[aes.BlockSize:], plainText)
return fmt.Sprintf("%x", cipherText), nil
}
// Decrypt decrypts cipher text string into plain text string
func Decrypt(encrypted string) (string, error) {
key := []byte(CIPHER_KEY)
cipherText, _ := hex.DecodeString(encrypted)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
if len(cipherText) < aes.BlockSize {
panic("cipherText too short")
}
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]
if len(cipherText)%aes.BlockSize != 0 {
panic("cipherText is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(cipherText, cipherText)
cipherText, _ = pkcs7.Unpad(cipherText, aes.BlockSize)
return fmt.Sprintf("%s", cipherText), nil
}
// Golang v1.8
//
// TEST FILE
//
package blahblah
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestAES(t *testing.T) {
t.Run("Encrypts and decrypts", func(t *testing.T) {
plainTexts := []string{"1234567890", "123456789012345678901234567890123456789012345678901234567890", "1", ""}
for _, plainText := range plainTexts {
encrypted, err := Encrypt(plainText)
if err != nil {
t.Fatalf("Failed to encrypt: %s - %s", plainText, err.Error())
}
decrypted, err := Decrypt(encrypted)
if err != nil {
t.Fatalf("Failed to decrypt: %s - %s", plainText, err.Error())
}
assert.Equal(t, plainText, decrypted)
}
})
}
@giftedunicorn
Copy link

giftedunicorn commented Jun 28, 2019

This is the best tutorial for the people who are from Node. Hope more people can see this!

@rebotak
Copy link

rebotak commented Dec 5, 2019

thank you so muchhhhh~

@bradford-hamilton
Copy link

Thank you for putting this together!

@nashmb1
Copy link

nashmb1 commented Jan 6, 2020

Thank you

@kranthisai
Copy link

Thanks really helpful.

@nkmishra1997
Copy link

Used base64 encode/decode on top of it.
This works, thanks a lot!!

@franky-continu
Copy link

I get an error:

cipherText, _ = pkcs7.Unpad(cipherText, aes.BlockSize)

runtime error: makeslice: len out of range /Users/franky/sdk/go1.14.3/src/runtime/slice.go:27 (0x1052f08) panicmakeslicelen: panic(errorString("makeslice: len out of range")) /Users/franky/sdk/go1.14.3/src/runtime/slice.go:44 (0x1052fbc) makeslice: panicmakeslicelen() /Users/franky/go/src/github.com/mergermarket/go-pkcs7/pkcs7.go:22 (0x1c4fc39) Unpad: buf := make([]byte, bufLen) /Users/franky/Workspace/continu/gopen-api/main.go:169 (0x1c99cae) Decrypt: cipherText, _ = pkcs7.Unpad(cipherText, aes.BlockSize)

@buYoung
Copy link

buYoung commented Aug 25, 2020

I get an error:

cipherText, _ = pkcs7.Unpad(cipherText, aes.BlockSize)

runtime error: makeslice: len out of range /Users/franky/sdk/go1.14.3/src/runtime/slice.go:27 (0x1052f08) panicmakeslicelen: panic(errorString("makeslice: len out of range")) /Users/franky/sdk/go1.14.3/src/runtime/slice.go:44 (0x1052fbc) makeslice: panicmakeslicelen() /Users/franky/go/src/github.com/mergermarket/go-pkcs7/pkcs7.go:22 (0x1c4fc39) Unpad: buf := make([]byte, bufLen) /Users/franky/Workspace/continu/gopen-api/main.go:169 (0x1c99cae) Decrypt: cipherText, _ = pkcs7.Unpad(cipherText, aes.BlockSize)

run your command this code
go get pault.ag/go/pkcs7

@BarotShlok
Copy link

Thank you for sharing code...

@franky-continu
Copy link

When I try to build an App on Cloud66 the docker image fails to build with that Library:

` #12 [8/14] RUN go get github.com/paultag/go-pkcs7
#12 1.071 unrecognized import path "pault.ag/go/pkcs7/utils": reading https://pault.ag/go/pkcs7/utils?go-get=1: 404 Not Found
#12 ERROR: executor failed running [/bin/sh -c go get github.com/paultag/go-pkcs7]: runc did not terminate sucessfully

[8/14] RUN go get github.com/paultag/go-pkcs7:


failed to solve with frontend dockerfile.v0: failed to build LLB: executor failed running [/bin/sh -c go get github.com/paultag/go-pkcs7]: runc did not terminate sucessfully `

@buzzy
Copy link

buzzy commented Jul 23, 2022

I would say this solution is not complete. In node, you can have the password in any length and it's converted to a passphrase automatically. In this example, you are using a password with exactly 32 chars, which is hardly the case always.

@brettscott
Copy link
Author

@buzzy take it as it is, expand on it and share!

I want to remind you it’s not productive to simply complain about free OSS software being incomplete or not meeting your own needs.

A better approach would have been to provide what’s missing in these comments for others to benefit from, or link to your own gist/package.

As for being “incomplete”, the above gist is complete for my needs when created years ago, and has hopefully saved others (perhaps even yourself) hours of time.

Also, take a closer look at cipher keys.

@buzzy
Copy link

buzzy commented Jul 23, 2022

@brettscott Well, another library has existed since 4 years that already does all this automatically, so I don't really have to re-invent the wheel. https://github.com/Luzifer/go-openssl

@zoulux
Copy link

zoulux commented Jan 8, 2023

good

@tirzasrwn
Copy link

Thank you!

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