Skip to content

Instantly share code, notes, and snippets.

@snowkidind
Last active December 30, 2022 05:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snowkidind/630b2a4c961e694b0ade18047e60b704 to your computer and use it in GitHub Desktop.
Save snowkidind/630b2a4c961e694b0ade18047e60b704 to your computer and use it in GitHub Desktop.
Node: Encrypt and Decrypt a file starting point
const crypto = require('crypto')
const fs = require('fs')
// Higher number decreases the probability that n can be brute forced
// iterations is the number of steps to use to derive a new key. This is intended to scale over time based on the power of cpus
const pbkdf2iterations = 106667
const encryptionKey = 'blahblahblahttayakaewpleng'
const encryptfile = async (enc_key, plaintextbytes) => {
// turn encryption key into an 8 bit byte array
const passphrasebytes = new TextEncoder("utf-8").encode(enc_key)
// create a salt of some "cryptographically strong random values"
// the presence of the salt will cause each encryption of the same data to be different
const pbkdf2salt = crypto.getRandomValues(new Uint8Array(8))
// Interpret the provided keyData (passphrasebytes) to create a <CryptoKey> instance.
// format of keyData: raw, pkcs8, spki, or jwk
// algorithms:
// -- symmetric algorithm means the private key is shared on both sides
// AES-CBC, AES-CTR, AES-GCM, AES-KW, HDKF, HMAC, PBKDF2,
// -- asymmetric means the private key on one side and a public key on the other side
// RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5 ECDSA, Ed25519, Ed448, ECDH, X25519, X448,
// extractable: When true, the <CryptoKey> can be extracted using either subtleCrypto.exportKey() or subtleCrypto.wrapKey()
// keyUsages: validated based on the named algorithm and will vary from one key type to the next.
// encrypt - encrypt data
// decrypt - decrypt data
// sign - generate digital signatures
// verify - verify digital signatures
// deriveKey - derive a new key
// deriveBits - derive bits
// wrapKey - wrap another key
// unwrapKey - unwrap another key
// Here, import a CryptoKey from passphrasebytes of raw data using PBKDF2 symmetric key for the usage of bit derivation
const passphrasekey = await crypto.subtle.importKey('raw', passphrasebytes, { name: 'PBKDF2' }, false, ['deriveBits'])
// subtle.deriveBits(algorithm, baseKey, length)
// algorithm: <AlgorithmIdentifier> | <EcdhKeyDeriveParams> | <HkdfParams> | <Pbkdf2Params>
// baseKey is what we generated above
// length: Using the method and parameters specified in algorithm and the keying material provided by baseKey,
// deriveBits attempts to generate length bits. must be multiple of 8
// for the 'ECDH', 'X25519', and 'X448' algorithms, length = null the max bits for a given algorithm is generated
// Here derive bits using the PBKDF2 alg, salt it, run for iterations using this passphrasekey and length
// 384 is determined by the intended use of the derived key material:
// 32 bytes for the key and 16 bytes for the IV, for a total of 48 bytes or 384 bits.
let pbkdf2bytes = await crypto.subtle.deriveBits(
{ "name": 'PBKDF2', "salt": pbkdf2salt, "iterations": pbkdf2iterations, "hash": 'SHA-256' }, passphrasekey, 384)
// schlop it into a byte array
pbkdf2bytes = new Uint8Array(pbkdf2bytes)
// split up into front and back
const keybytes = pbkdf2bytes.slice(0, 32) // bytes to be used in key
// An initialization vector is usedto ensure that the encryption of a message is not deterministic.
const ivbytes = pbkdf2bytes.slice(32) // remaining bytes for initialization vector
// now import a CryptoKey using only the keybytes and perform AES-CBC (Cipher Block Chaining) algorithm
// on 256 bit blocks for the usage of encryption
const key = await crypto.subtle.importKey('raw', keybytes, { name: 'AES-CBC', length: 256 }, false, ['encrypt'])
// Using the method and parameters specified by algorithm and the keying material provided by key,
// algorithms: <RsaOaepParams> | <AesCtrParams> | <AesCbcParams> | <AesGcmParams>
// returns <ArrayBuffer> containing the encrypted result.
let cipherbytes = await crypto.subtle.encrypt({ name: "AES-CBC", iv: ivbytes }, key, plaintextbytes)
cipherbytes = new Uint8Array(cipherbytes)
const resultbytes = new Uint8Array(cipherbytes.length + 16)
// Pass the results into the "file"
// The format here is compatible / similar to the open ssl enc
resultbytes.set(new TextEncoder("utf-8").encode('Salted__'))
resultbytes.set(pbkdf2salt, 8)
resultbytes.set(cipherbytes, 16)
return resultbytes
}
async function decryptFile(enc_key, cipherbytes) {
const passphrasebytes = new TextEncoder("utf-8").encode(enc_key)
const pbkdf2salt = cipherbytes.slice(8, 16)
const passphrasekey = await crypto.subtle.importKey('raw', passphrasebytes, { name: 'PBKDF2' }, false, ['deriveBits'])
let pbkdf2bytes = await crypto.subtle.deriveBits({ "name": 'PBKDF2', "salt": pbkdf2salt, "iterations": pbkdf2iterations, "hash": 'SHA-256' }, passphrasekey, 384)
pbkdf2bytes = new Uint8Array(pbkdf2bytes)
let keybytes = pbkdf2bytes.slice(0, 32)
let ivbytes = pbkdf2bytes.slice(32)
cipherbytes = cipherbytes.slice(16)
const key = await crypto.subtle.importKey('raw', keybytes, { name: 'AES-CBC', length: 256 }, false, ['decrypt'])
let plaintextbytes = await crypto.subtle.decrypt({ name: "AES-CBC", iv: ivbytes }, key, cipherbytes)
plaintextbytes = new Uint8Array(plaintextbytes)
return new Buffer.from(plaintextbytes).toString('ascii')
}
; (async () => {
const v = process.version.slice(1, 3)
if (Number(v) < 19) {
console.log('This script was written in Node 19.3.0')
process.exit(1)
}
try {
const rawDataFile = __dirname + '/afile.txt'
const encryptedFile = __dirname + '/encrypted.enc'
const plaintextbytes = await fs.readFileSync(rawDataFile) // File arrives in an array buffer
const bytes = await encryptfile(encryptionKey, plaintextbytes)
await fs.writeFileSync(encryptedFile, bytes)
const cipherbytes = await fs.readFileSync(encryptedFile)
const decrypted = await decryptFile(encryptionKey, cipherbytes)
console.log(decrypted)
} catch (error) {
console.log(error)
process.exit(1)
}
process.exit(0)
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment