UNPKG

singlecrypt-text

Version:

A simple, secure and fast symmetric encryption library that makes use of AES-GCM.

222 lines (190 loc) 7.02 kB
const encryptionAlgorithm = "AES-GCM" const ivBytesLength = 12 /** * @type {KeyUsage[]} */ const keyUsages = ["encrypt", "decrypt"] Object.freeze(keyUsages) /** * @type {Parameters<Uint8Array<ArrayBuffer>["toBase64"]>[0]} */ const base64UrlOptions = Object.freeze({ alphabet: "base64url" }) /** * Before encrypting and decrypting values, a symmetric `CryptoKey` must be created. * This method also converts your value key to a SHA-256 hash. * * @async * @function createSymmetricKeyFromText * @param {string} text - Text key to be hashed. A 32-byte high entropy string is recommended. * @param {boolean} [extractable] - Whether the generated key is extractable. Defaults to `false`.. * @param {TextEncoder} [textEncoder] - If you have an instance of a `TextEncoder`, you can reuse it. * @returns {Promise<CryptoKey>} A `CryptoKey` containing a SHA-256 hash used to encrypt and decrypt strings. * @throws {TypeError} Thrown if `text` is invalid. */ export async function createSymmetricKeyFromText( text, extractable = false, textEncoder = new TextEncoder() ) { return ( await crypto.subtle.importKey( "raw", await crypto.subtle.digest("SHA-256", textEncoder.encode(text)), encryptionAlgorithm, extractable, keyUsages ) ) } /** * Encrypts a value with a `CryptoKey` previously generated with `createSymmetricKeyFromText`. * * @async * @function encryptTextSymmetrically * @param {CryptoKey} key - Symmetric key generated with `createSymmetricKeyFromText`. * @param {string} text - String value to be encrypted. * @param {boolean} [urlSafe] - The encrypted values default to `base64` alphabet; this property enables the `base64url` alphabet. Enabled by default. * @param {TextEncoder} [textEncoder] - If you have an instance of a `TextEncoder`, you can reuse it. * @returns {Promise<string>} The value encrypted and encoded as a Base64 string. * @throws {DOMException} Raised when: * - The provided key is not valid. * - The operation failed (e.g., AES-GCM plaintext longer than 2^39−256 bytes). */ export async function encryptTextSymmetrically( key, text, urlSafe = true, textEncoder = new TextEncoder() ) { const iv = crypto.getRandomValues(new Uint8Array(ivBytesLength)) const base64Options = urlSafe ? base64UrlOptions : undefined return ( iv.toBase64(base64Options) + new Uint8Array( await crypto.subtle.encrypt( { name: encryptionAlgorithm, iv }, key, textEncoder.encode(text) ) ).toBase64(base64Options) ) } /** * Decrypts a value with a `CryptoKey` previously generated with `createSymmetricKeyFromText`. * * @async * @function decryptTextSymmetrically * @param {CryptoKey} key - Symmetric key used to encrypt the value. * @param {string} encryptedText - Encrypted value to be decrypted. * @param {boolean} [urlSafe] - The encrypted values default to `base64` alphabet; this property enables the `base64url` alphabet. Enabled by default. * @param {TextDecoder} [textDecoder] - If you have an instance of a `TextDecoder`, you can reuse it. * @returns {Promise<string>} The value decrypted. * @throws {TypeError} Thrown if `encryptedText` is not a string. * @throws {SyntaxError} Thrown if `encryptedText` contains characters outside Base64 alphabet. * @throws {DOMException} Raised when: * - The provided key is not valid. * - The operation failed. */ export async function decryptTextSymmetrically( key, encryptedText, urlSafe = true, textDecoder = new TextDecoder() ) { const data = Uint8Array.fromBase64(encryptedText, urlSafe ? base64UrlOptions : undefined) return ( textDecoder.decode( await crypto.subtle.decrypt( { name: encryptionAlgorithm, iv: data.subarray(0, ivBytesLength) }, key, data.subarray(ivBytesLength) ) ) ) } /** * Class that simplifies the encryption and decryption using the same key. */ export class SingleCryptText { /** * @type {(CryptoKey|undefined)} */ #key /** * @type {(Promise<CryptoKey>|undefined)} */ #keyPromise #textEncoder #textDecoder /** * The encrypted values default to `base64` alphabet; this property enables the `base64url` alphabet. * Enabled by default. * * @type {boolean} */ urlSafe /** * Create an instance using a text as a key. * * @param {string} text - Text key to be hashed. A 32-byte high entropy string is recommended. * @param {boolean} [urlSafe] - The encrypted values default to `base64` alphabet; this property enables the `base64url` alphabet. Enabled by default. * @param {boolean} [extractable] - Whether the generated key is extractable. Defaults to `false`.. * @param {TextEncoder} [textEncoder] - If you have an instance of a `TextEncoder`, you can reuse it. * @param {TextDecoder} [textDecoder] - If you have an instance of a `TextDecoder`, you can reuse it. * @throws {TypeError} Thrown if `text` is invalid. */ constructor( text, urlSafe = true, extractable = false, textEncoder = new TextEncoder(), textDecoder = new TextDecoder() ) { this.#keyPromise = createSymmetricKeyFromText(text, extractable, textEncoder) this.#textEncoder = textEncoder this.#textDecoder = textDecoder this.urlSafe = urlSafe } /** * @async * @function getKey * @returns {Promise<CryptoKey>} */ async getKey() { if (!this.#key && this.#keyPromise) { this.#key = await this.#keyPromise this.#keyPromise = undefined } // @ts-expect-error: TS doesn't detect that `#key` has been defined. return this.#key } /** * Encrypts a value. * * @async * @param {string} text - String value to be encrypted. * @returns {Promise<string>} The value encrypted and encoded as a Base64 string. * @throws {DOMException} Raised when: * - The provided key is not valid. * - The operation failed (e.g., AES-GCM plaintext longer than 2^39−256 bytes). */ async encrypt(text) { return encryptTextSymmetrically(await this.getKey(), text, this.urlSafe, this.#textEncoder) } /** * @async * @param {string} encryptedText - Encrypted value to be decrypted. * @returns {Promise<string>} The value decrypted. * @throws {TypeError} Thrown if `encryptedText` is not a string. * @throws {SyntaxError} Thrown if `encryptedText` contains characters outside Base64 alphabet. * @throws {DOMException} Raised when: * - The provided key is not valid. * - The operation failed. */ async decrypt(encryptedText) { return decryptTextSymmetrically(await this.getKey(), encryptedText, this.urlSafe, this.#textDecoder) } } export default SingleCryptText