M README.md => README.md +15 -31
@@ 7,11 7,17 @@ Basic security API for Typescript that optimizes for easy use
## Important Compatibility Note
-Note that there will be breaking changes introduced after version 0.1.9.
-These changes will start a 0.2.x version scheme. If you have a dependency
-on this library as it is presently developed, I recommend fixing your
-use at version 0.1.9 until you can undertake a migration forward.
-Alternately, you may wish to simply fork version 0.1.9.
+The 0.2.0 release breaks compatibility with the previous relesae, 0.1.9. This
+was noted in the 0.1.9 README. The changes include:
+
+- Using AES-GCM 256 as the default cipher instead of AES-CBC 128.
+- Moving `encryptToHex` and `decryptToHex` to static methods that feature
+ per-encrypt IVs automatically generated and prepended to the encrypted
+ message.
+- Removing the `Crypter` class.
+
+No deprecation notices are included as the change of the cipher implies the need
+to regenerate encrypted messages generated with versions prior.
## Motivation
@@ 22,7 28,7 @@ In almost all cases, I want to work with values that can be printed and stored
directly, which for me means using hex encoding as a serialization format.
This module is not for cryptography experts. This module is not for people who
-want to tune parameters. This module is for people for whom AES-CBC is a "good
+want to tune parameters. This module is for people for whom AES-GCM is a "good
enough" cipher, for whom v4 UUIDs are a "good enough" random value, and for whom
sha256 is a "good enough" hash.
@@ 49,31 55,9 @@ randomUUID(); // re-export crypto.randomUUID();
### Encryption/Decryption
```ts
-// generate a Crypter using new, random key and IV
-// (you can access them via crypter.Key, crypter.IV)
-const crypter = Crypter.generate();
-
-// or maybe you already have hex-exported Key and IV that you had stored
-const crypter = Crypter.fromHex(hexKey, hexIV);
-
-// or maybe you want to generate new Key and IV
const key = await Key.generate();
-const iv = IV.fromString("user@example.com");
-const crypter = await new Crypter(key, iv);
-
-// or you want to create Key and IV instances directly from
-// hex - exports;
-const key = await Key.fromHex(hexKey);
-const iv = IV.fromHex(hexIV);
-
-// and you can make these exports easily:
-const hexKey = key.toHex();
-const hexIV = iv.toHex();
-
-// to encrypt some clear text:
const clearText = "hello world";
-const hexCrypted = await crypter.encryptToHex(clearText);
-
-// ...and get the clear text back
-const decrypted = await crypter.decryptFromHex(hexCrypted);
+const hexCryptedWithIV = await encryptToHex(clearText, key);
+const decrypted = await decryptFromHex(hexCryptedWithIV, key);
+// clearText == decrypted
```
M mod.ts => mod.ts +33 -66
@@ 35,13 35,13 @@ const isUUID = (s: string): boolean => {
*/
const randomUUID = (): string => crypto.randomUUID();
-const AES_CBC = "AES-CBC";
+const AES_GCM = "AES-GCM";
/**
* @class Key provides a simple wrapper for AES CryptoKeys allowing for serialization to hex.
*/
class Key {
- static readonly Params: AesKeyGenParams = { name: AES_CBC, length: 128 };
+ static readonly Params: AesKeyGenParams = { name: AES_GCM, length: 256 };
static readonly Extractable = true;
static readonly Usages: KeyUsage[] = ["encrypt", "decrypt"];
@@ 154,73 154,40 @@ class IV {
}
/**
- * @class Crypter provides a simple wrapper for encrypting to and decrypting from hex-encoded values
+ * encrypt a clear text and hex-encode the resulting encrypted value
+ * @param {string} clearText - the string to encrypt
+ * @param {Key} key - Key instance
+ * @returns {Promise<string>} - the hex-encoding of the encrypted iv + clearText
*/
-class Crypter {
- readonly key: Key;
- readonly iv: IV;
-
- /**
- * @constructor
- * @param {Key} key - the encryption Key
- * @param {IV} iv - the encryption IV
- */
- constructor(key: Key, iv: IV) {
- this.key = key;
- this.iv = iv;
- }
-
- /**
- * construct a new Crypter using a hex-encoded Key and IV
- * @param {string} hexKey - the hex-encoded encryption Key (from Key.toHex())
- * @param {string} hexIV - the hex-encoded encryption IV (from IV.toHex())
- * @returns {Promise<Crypter>} - a new Crypter constructed from hexKey and hexIV
- */
- static async fromHex(hexKey: string, hexIV: string): Promise<Crypter> {
- return new Crypter(await Key.fromHex(hexKey), IV.fromHex(hexIV));
- }
-
- /**
- * construct a new Crypter using newly generated Key and IV
- * @returns {Promise<Crypter>} - a new Crypter constructed with generated Key and IV
- */
- static async generate(): Promise<Crypter> {
- return new Crypter(await Key.generate(), IV.generate());
- }
-
- /**
- * decrypt a hex-encoded encryption output and return the original clear text
- * @param {string} hexEncrypted - the output of a previous call to encryptToHex
- * @returns {Promise<string>} - the decrypted clear text
- */
- async decryptFromHex(hexEncrypted: string): Promise<string> {
- return bytesToString(
- new Uint8Array(
- await crypto.subtle.decrypt(
- { name: AES_CBC, iv: this.iv.bytes },
- this.key.cryptoKey,
- hexToBytes(hexEncrypted),
- ),
+async function encryptToHex(clearText: string, key: Key): Promise<string> {
+ const iv = IV.generate();
+ return iv.toHex() + bytesToHex(
+ new Uint8Array(
+ await crypto.subtle.encrypt(
+ { name: AES_GCM, iv: iv.bytes },
+ key.cryptoKey,
+ stringToBytes(clearText),
),
- );
- }
+ ),
+ );
+}
- /**
- * encrypt a clear text and hex-encode the resulting encrypted value
- * @param {string} clearText - the string to encrypt
- * @returns {Promise<string>} - the decrypted clear text
- */
- async encryptToHex(clearText: string): Promise<string> {
- return bytesToHex(
- new Uint8Array(
- await crypto.subtle.encrypt(
- { name: AES_CBC, iv: this.iv.bytes },
- this.key.cryptoKey,
- stringToBytes(clearText),
- ),
+/**
+ * @param {string} encrypted - the output of encryptToHex
+ * @param {Key} key - Key instance
+ * @returns {Promise<string>} - the decrypted clear text
+ */
+async function decryptFromHex(encrypted: string, key: Key): Promise<string> {
+ const iv = IV.fromHex(encrypted.substring(0, 2 * IV.Length));
+ return bytesToString(
+ new Uint8Array(
+ await crypto.subtle.decrypt(
+ { name: AES_GCM, iv: iv.bytes },
+ key.cryptoKey,
+ hexToBytes(encrypted.substring(2 * IV.Length)),
),
- );
- }
+ ),
+ );
}
-export { Crypter, isUUID, IV, Key, randomUUID, sha256Hex };
+export { decryptFromHex, encryptToHex, isUUID, IV, Key, randomUUID, sha256Hex };
M test.ts => test.ts +18 -5
@@ 1,8 1,17 @@
import {
assert,
assertEquals,
+ assertNotEquals,
} from "https://deno.land/std@0.208.0/assert/mod.ts";
-import { Crypter, isUUID, IV, Key, randomUUID, sha256Hex } from "./mod.ts";
+import {
+ decryptFromHex,
+ encryptToHex,
+ isUUID,
+ IV,
+ Key,
+ randomUUID,
+ sha256Hex,
+} from "./mod.ts";
/**
* uuid round trip
@@ 53,10 62,14 @@ Deno.test("iv", async () => {
/**
* encrypt/decrypt
*/
-Deno.test("encrypt", async () => {
- const crypter = await Crypter.generate();
+Deno.test("encrypt/decrypt", async () => {
+ const k = await Key.generate();
const clearText = "hello world";
- const hexCrypted = await crypter.encryptToHex(clearText);
- const decrypted = await crypter.decryptFromHex(hexCrypted);
+ const hexCryptedWithIV = await encryptToHex(clearText, k);
+ const decrypted = await decryptFromHex(hexCryptedWithIV, k);
assertEquals(decrypted, clearText, "round trip encrypt decrypt");
+ const hexCryptedWithIV2 = await encryptToHex(clearText, k);
+ assertNotEquals(hexCryptedWithIV, hexCryptedWithIV2, "different iv");
+ const decrypted2 = await decryptFromHex(hexCryptedWithIV, k);
+ assertEquals(decrypted2, clearText, "round trip encrypt decrypt");
});