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")) |
This comment has been minimized.
This comment has been minimized.
Sorry I didn't see this comment. You're welcome to use any of this for free. It's not really anything besides how to use the standard libraries. |
This comment has been minimized.
This comment has been minimized.
No problem. Thank you for the answer. |
This comment has been minimized.
This comment has been minimized.
Many thanks for this useful gist |
This comment has been minimized.
This comment has been minimized.
Could I decrypt this password? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
I don't see a license here. Is this freely usable? Thanks.