Last active
December 30, 2022 05:45
-
-
Save snowkidind/630b2a4c961e694b0ade18047e60b704 to your computer and use it in GitHub Desktop.
Node: Encrypt and Decrypt a file starting point
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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