Skip to content

Instantly share code, notes, and snippets.

@enyachoke
Forked from tscholl2/aes.go
Created January 15, 2019 06:51
Show Gist options
  • Save enyachoke/5c60f5eebed693d9b4bacddcad693b47 to your computer and use it in GitHub Desktop.
Save enyachoke/5c60f5eebed693d9b4bacddcad693b47 to your computer and use it in GitHub Desktop.
simple AES encryption/decryption example with PBKDF2 key derivation in Go, Javascript, and Python
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"strings"
"golang.org/x/crypto/pbkdf2"
)
func deriveKey(passphrase string, salt []byte) ([]byte, []byte) {
if salt == nil {
salt = make([]byte, 8)
// http://www.ietf.org/rfc/rfc2898.txt
// Salt.
rand.Read(salt)
}
return pbkdf2.Key([]byte(passphrase), salt, 1000, 32, sha256.New), salt
}
func encrypt(passphrase, plaintext string) string {
key, salt := deriveKey(passphrase, nil)
iv := make([]byte, 12)
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
// Section 8.2
rand.Read(iv)
b, _ := aes.NewCipher(key)
aesgcm, _ := cipher.NewGCM(b)
data := aesgcm.Seal(nil, iv, []byte(plaintext), nil)
return hex.EncodeToString(salt) + "-" + hex.EncodeToString(iv) + "-" + hex.EncodeToString(data)
}
func decrypt(passphrase, ciphertext string) string {
arr := strings.Split(ciphertext, "-")
salt, _ := hex.DecodeString(arr[0])
iv, _ := hex.DecodeString(arr[1])
data, _ := hex.DecodeString(arr[2])
key, _ := deriveKey(passphrase, salt)
b, _ := aes.NewCipher(key)
aesgcm, _ := cipher.NewGCM(b)
data, _ = aesgcm.Open(nil, iv, data, nil)
return string(data)
}
func main() {
c := encrypt("hello", "world")
fmt.Println(c)
fmt.Println(decrypt("hello", c))
fmt.Println(decrypt("hello", "c2932347953ad4a4-25f496d260de9c150fc9e4c6-20bc1f8439796cc914eb783b9996a8d9c32d45e2df"))
}
/**
* Encodes a utf8 string as a byte array.
* @param {String} str
* @returns {Uint8Array}
*/
function str2buf(str) {
return new TextEncoder("utf-8").encode(str);
}
/**
* Decodes a byte array as a utf8 string.
* @param {Uint8Array} buffer
* @returns {String}
*/
function buf2str(buffer) {
return new TextDecoder("utf-8").decode(buffer);
}
/**
* Decodes a string of hex to a byte array.
* @param {String} hexStr
* @returns {Uint8Array}
*/
function hex2buf(hexStr) {
return new Uint8Array(hexStr.match(/.{2}/g).map(h => parseInt(h, 16)));
}
/**
* Encodes a byte array as a string of hex.
* @param {Uint8Array} buffer
* @returns {String}
*/
function buf2hex(buffer) {
return Array.prototype.slice
.call(new Uint8Array(buffer))
.map(x => [x >> 4, x & 15])
.map(ab => ab.map(x => x.toString(16)).join(""))
.join("");
}
/**
* Given a passphrase, this generates a crypto key
* using `PBKDF2` with SHA256 and 1000 iterations.
* If no salt is given, a new one is generated.
* The return value is an array of `[key, salt]`.
* @param {String} passphrase
* @param {UInt8Array} salt [salt=random bytes]
* @returns {Promise<[CryptoKey,UInt8Array]>}
*/
function deriveKey(passphrase, salt) {
salt = salt || crypto.getRandomValues(new Uint8Array(8));
return crypto.subtle
.importKey("raw", str2buf(passphrase), "PBKDF2", false, ["deriveKey"])
.then(key =>
crypto.subtle.deriveKey(
{ name: "PBKDF2", salt, iterations: 1000, hash: "SHA-256" },
key,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"],
),
)
.then(key => [key, salt]);
}
/**
* Given a passphrase and some plaintext, this derives a key
* (generating a new salt), and then encrypts the plaintext with the derived
* key using AES-GCM. The ciphertext, salt, and iv are hex encoded and joined
* by a "-". So the result is `"salt-iv-ciphertext"`.
* @param {String} passphrase
* @param {String} plaintext
* @returns {Promise<String>}
*/
function encrypt(passphrase, plaintext) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const data = str2buf(plaintext);
return deriveKey(passphrase).then(([key, salt]) =>
crypto.subtle
.encrypt({ name: "AES-GCM", iv }, key, data)
.then(ciphertext => `${buf2hex(salt)}-${buf2hex(iv)}-${buf2hex(ciphertext)}`),
);
}
/**
* Given a key and ciphertext (in the form of a string) as given by `encrypt`,
* this decrypts the ciphertext and returns the original plaintext
* @param {String} passphrase
* @param {String} saltIvCipherHex
* @returns {Promise<String>}
*/
function decrypt(passphrase, saltIvCipherHex) {
const [salt, iv, data] = saltIvCipherHex.split("-").map(hex2buf);
return deriveKey(passphrase, salt)
.then(([key]) => crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data))
.then(v => buf2str(new Uint8Array(v)));
}
// EXAMPLE
/*
encrypt("hello", "world")
.then(v => console.log("ENCRYPTED", v) || v)
.then(v => decrypt("hello", v))
.then(v => console.log("DECRYPTED ", v) || v);
decrypt(
"hello",
"6102677198e41d98-84c95e2d7caf6f2d4ccbfe3c-3093cef35d0dba7a24d37f7d4580b5ad83c154329c",
).then(console.log);
*/
import hashlib
import os
from binascii import hexlify, unhexlify
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def deriveKey(passphrase: str, salt: bytes=None) -> [str, bytes]:
if salt is None:
salt = os.urandom(8)
return hashlib.pbkdf2_hmac("sha256", passphrase.encode("utf8"), salt, 1000), salt
def encrypt(passphrase: str, plaintext: str) -> str:
key, salt = deriveKey(passphrase)
aes = AESGCM(key)
iv = os.urandom(12)
plaintext = plaintext.encode("utf8")
ciphertext = aes.encrypt(iv, plaintext, None)
return "%s-%s-%s" % (hexlify(salt).decode("utf8"), hexlify(iv).decode("utf8"), hexlify(ciphertext).decode("utf8"))
def decrypt(passphrase: str, ciphertext: str) -> str:
salt, iv, ciphertext = map(unhexlify, ciphertext.split("-"))
key, _ = deriveKey(passphrase, salt)
aes = AESGCM(key)
plaintext = aes.decrypt(iv, ciphertext, None)
return plaintext.decode("utf8")
if __name__ == "__main__":
ciphertext = encrypt("hello", "world")
print(ciphertext)
print(decrypt("hello", ciphertext))
print(decrypt("hello", "6102677198e41d98-84c95e2d7caf6f2d4ccbfe3c-3093cef35d0dba7a24d37f7d4580b5ad83c154329c"))
@velcrine
Copy link

amazing. thanks a lot buddy.

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