Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
@f3ndot/aesutil
Advanced tools
An opinionated NodeJS convenience library for AES-256-GCM Encryption/Decryption with optional Additional Data (AAD/AEAD) in a portable way
An ✨opinionated✨ NodeJS convenience library for AES-256-GCM Encryption/Decryption with optional Additional Data (AAD/AEAD) in a portable way.
Meant for ciphertext storage outside the running environment, such as in a database hosted elsewhere or publicly exposed.
yarn add @f3ndot/aesutil
Set the AESUTIL_JS_AES_ENCRYPTION_KEY
environment variable to a cryptographically random 32 byte (256-bit) key, encoded in Base64. For example, using OpenSSL:
export AESUTIL_JS_AES_ENCRYPTION_KEY=$(openssl rand -base64 32)
Save that in your env whichever way you like, for example a .env
file if your project is setup for it:
AESUTIL_JS_AES_ENCRYPTION_KEY="uQDJyFHpG7qKPZgGhC/74eIWx/ItMof+T00Tho2Cam8="
That's all that's required to get started
AesUtil()
classFor those who want additional configurable control, you can use AesUtil()
instead of the two simple convenience functions described in Usage. All parameters are optional and the defaults are shown below:
const aesUtil = new AesUtil({
// encryption key to use as a Base64-encoded string or the raw Buffer bytes.
// Always use to avoid needing to set the ENV VAR on your system
providedKey: undefined,
// the encrypted output string is not encoded in Base64 and instead returns
// the raw bytes in a string. Handy for `BINARY` DB column or file
// destinations
binaryMode: false,
// for when the supplied input string is in an encoding other than utf8 like
// latin-1, ascii, hex, or even binary
plaintextEncoding: "utf8",
});
If you just want to pass the key, the constructor supports that:
const encodedEncKey = "vLPSzkuV7rprQUGJdUGcuB+bx/rNX+a0QfZPSuiFdxY=";
const aesUtil = new AesUtil(encodedEncKey); // Can also be a Buffer containing the 32-byte unencoded key
Very simple. You can use the simple functional methods, or the class for more configurability:
import { encryptValue } from "@f3ndot/aesutil";
const encryptedDataForDb = encryptValue("some sensitive plaintext");
// => 'Am4ubpry3kg3BDDK.qWgj/gOHyV9pv5U/RZ6Rzw==.WOF0+fh4hnRi7IqyUKqU15u/5nyPspvX'
storeToDb(encryptedDataForDb);
Or if using the class alternative:
import { AesUtil } from "@f3ndot/aesutil";
let aesUtil = new AesUtil();
let encryptedDataForDb = aesUtil.encrypt("some sensitive plaintext");
// => 'Am4ubpry3kg3BDDK.qWgj/gOHyV9pv5U/RZ6Rzw==.WOF0+fh4hnRi7IqyUKqU15u/5nyPspvX'
aesUtil = new AesUtil({ binaryMode: true });
encryptedDataForDb = aesUtil.encrypt("some sensitive plaintext");
// => '\bPR\x1F\x9BâÓ\x99\x10/\x93,QN_\x16\x98`½\b\x85ûèa\x02ì¼\x160[Öázª>\x14¾\x88!x8\x91 \x02\x03«Úþ ¹Xó'
aesUtil = new AesUtil({ plaintextEncoding: "hex" });
encryptedDataForDb = aesUtil.encrypt("414141"); // will efficiently be stored as 3 "a" bytes (before Base64-encoding) as a result
// => 'YWFhYWFhYWFhYWFh.1b42zgCE1hBUVAQ2y1c4fg==.f7y/'
import { decryptValue } from "@f3ndot/aesutil";
const encryptedDataFromDb =
"Am4ubpry3kg3BDDK.qWgj/gOHyV9pv5U/RZ6Rzw==.WOF0+fh4hnRi7IqyUKqU15u/5nyPspvX";
const plaintext = decryptValue(encryptedDataFromDb); // => 'some sensitive plaintext'
Or alternatively, yet again:
import { AesUtil } from "@f3ndot/aesutil";
let encryptedDataFromDb =
"Am4ubpry3kg3BDDK.qWgj/gOHyV9pv5U/RZ6Rzw==.WOF0+fh4hnRi7IqyUKqU15u/5nyPspvX";
let aesUtil = new AesUtil(); // Can also be passed the key, Base64-encoded or not
let plaintext = aesUtil.decrypt(encryptedDataFromDb); // => 'some sensitive plaintext'
encryptedDataFromDb =
"\bPR\x1F\x9BâÓ\x99\x10/\x93,QN_\x16\x98`½\b\x85ûèa\x02ì¼\x160[Öázª>\x14¾\x88!x8\x91 \x02\x03«Úþ ¹Xó";
aesUtil = new AesUtil({ binaryMode: true });
plaintext = aesUtil.decrypt(encryptedDataFromDb); // => 'some sensitive plaintext'
encryptedDataFromDb = "YWFhYWFhYWFhYWFh.1b42zgCE1hBUVAQ2y1c4fg==.f7y/";
aesUtil = new AesUtil({ plaintextEncoding: "hex" });
plaintext = aesUtil.decrypt(encryptedDataFromDb); // => '414141'
Since AES-256-GCM is used, you can optionally supply associated data to tie to the ciphertext. This is particularly useful in a database context where a given ciphertext may belong to only one row. Associated Data would prevent ciphertext reuse.
import { encryptValue } from "@f3ndot/aesutil";
const encryptedDataForDb = encryptValue("some medical history", "user-id-1");
// => '4G4slwTqQpz3MYUf.vfgpx8urncMXtFCD+xJAKw==.fgyJEpyTr26PBknvHe3VYSeX8xM='
updateUserMedicalFile("user-id-1", encryptedDataForDb);
import { AesUtil } from "@f3ndot/aesutil";
const aesUtil = new AesUtil();
const encryptedDataForDb = aesUtil.encrypt("some medical history", "user-id-1");
// => '4G4slwTqQpz3MYUf.vfgpx8urncMXtFCD+xJAKw==.fgyJEpyTr26PBknvHe3VYSeX8xM='
updateUserMedicalFile("user-id-1", encryptedDataForDb);
import { decryptValue } from "@f3ndot/aesutil";
const encryptedDataForUser1FromDb =
"4G4slwTqQpz3MYUf.vfgpx8urncMXtFCD+xJAKw==.fgyJEpyTr26PBknvHe3VYSeX8xM=";
const user1History = decryptValue(encryptedDataForUser1FromDb, "user-id-1"); // => 'some medical history'
const user2History = decryptValue(encryptedDataForUser1FromDb, "user-id-2"); // => Throws an error
import { AesUtil } from "@f3ndot/aesutil";
const encryptedDataForUser1FromDb =
"4G4slwTqQpz3MYUf.vfgpx8urncMXtFCD+xJAKw==.fgyJEpyTr26PBknvHe3VYSeX8xM=";
const aesUtil = new AesUtil();
const user1History = aesUtil.decrypt(encryptedDataForUser1FromDb, "user-id-1"); // => 'some medical history'
const user2History = aesUtil.decrypt(encryptedDataForUser1FromDb, "user-id-2"); // => Throws an error
Since the ciphertext, its IV, and auth tag are all encoded as Base64 strings smushed together, the resulting string is very portable and versatile. It can reasonably be copied around and transported anywhere. This can be useful in situations where binary data/non-ASCII characters would get mangled. Hex encoding could've been chosen and accomplishes the same job, but it takes up more characters.
And while SQL databases have the BINARY
type, the additional overhead for storing the string as TEXT
is small and consistency makes developer error less likely. Ditto for storing IV and auth tag alongside. While those could be stored in separate BINARY
columns, it's just more work.
Ultimately there's new AesUtil({ binaryMode: true })
now for those who want it, but the default shall remain the portable string version.
Because NIST 800-38D says so.
Side-steps the whole debate on what best Password-Based Key Derivation function to use and keeps things fast (a property we want on symmetrically-encrypted data). Forces the implementer/developer to obtain a cryptographically random 256-bit key and use it directly versus deriving one from a less entropic password.
🚫 This isn't for storing passwords! Never encrypt your passwords! 🚫
Things going slowly is a desireable property for hashing (commonly misnomered as encrypting) passwords, where the plaintext no longer needs to be known, and verifying/authenticating should be slowed to stymie brute-force attackers.
Conversely, adding an encryption layer for security at rest or transport in untrusted environments should not bog down your application/system. This library is intending to solve that problem.
However, if you intentionally want your encryption layer to be slowed down, such as when you're encrypting data with user-supplied passwords, then you can implement that outside of the library and pass the resulting key in. Example:
// Example where the library is used as part of user password-encrypted
import crypto from "crypto";
import { AesUtil } from "@f3ndot/aesutil";
// It's up to *YOU* to decide how to derive the key, with what algorithms, costs, and settings. Here's an example:
const derivedKey = crypto.scryptSync(
"some user-supplied password",
"some salt",
32
);
const aesUtil = new AesUtil(derivedKey);
aesUtil.encrypt("...");
Guarantees encrypted data for a particular row cannot be reused in other rows. Consider a table of users with API keys that for business reasons cannot be hashed and thus are symmetrically encrypted. Should a vulnerability occur that would allow an attacker to duplicate the encrypted API key ciphertext to other user rows, an attacker could:
Because I don't have a use case for large inputs/outputs yet. PR's welcome 😌
Copyright (c) 2023 Justin Bull under the MIT License
FAQs
An opinionated NodeJS convenience library for AES-256-GCM Encryption/Decryption with optional Additional Data (AAD/AEAD) in a portable way
The npm package @f3ndot/aesutil receives a total of 0 weekly downloads. As such, @f3ndot/aesutil popularity was classified as not popular.
We found that @f3ndot/aesutil demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.