Skip to content

Commit 3c1865a

Browse files
committed
aes: switch from native async to noble sync
1 parent 28e4a10 commit 3c1865a

File tree

5 files changed

+47
-112
lines changed

5 files changed

+47
-112
lines changed

package-lock.json

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"esm"
1616
],
1717
"dependencies": {
18+
"@noble/ciphers": "0.6.0",
1819
"@noble/curves": "1.6.0",
1920
"@noble/hashes": "1.5.0",
2021
"@scure/bip32": "1.5.0",

src/aes.ts

+23-100
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,47 @@
1-
import { crypto as cr } from "@noble/hashes/crypto";
2-
import { concatBytes, equalsBytes } from "./utils.js";
1+
import { ctr, cbc } from "@noble/ciphers/aes";
2+
import type { CipherWithOutput } from "@noble/ciphers/utils";
33

4-
const crypto: any = { web: cr };
5-
6-
function validateOpt(key: Uint8Array, iv: Uint8Array, mode: string) {
4+
function getCipher(
5+
key: Uint8Array,
6+
iv: Uint8Array,
7+
mode: string,
8+
pkcs7PaddingEnabled = true
9+
): CipherWithOutput {
710
if (!mode.startsWith("aes-")) {
8-
throw new Error(`AES submodule doesn't support mode ${mode}`);
9-
}
10-
if (iv.length !== 16) {
11-
throw new Error("AES: wrong IV length");
11+
throw new Error("AES: unsupported mode");
1212
}
13-
if (
14-
(mode.startsWith("aes-128") && key.length !== 16) ||
15-
(mode.startsWith("aes-256") && key.length !== 32)
16-
) {
13+
const len = key.length;
14+
if ((mode.startsWith("aes-128") && len !== 16) || (mode.startsWith("aes-256") && len !== 32)) {
1715
throw new Error("AES: wrong key length");
1816
}
19-
}
20-
21-
async function getBrowserKey(
22-
mode: string,
23-
key: Uint8Array,
24-
iv: Uint8Array
25-
): Promise<[CryptoKey, AesCbcParams | AesCtrParams]> {
26-
if (!crypto.web) {
27-
throw new Error("Browser crypto not available.");
17+
if (iv.length !== 16) {
18+
throw new Error("AES: wrong IV length");
2819
}
29-
let keyMode: string | undefined;
3020
if (["aes-128-cbc", "aes-256-cbc"].includes(mode)) {
31-
keyMode = "cbc";
21+
return cbc(key, iv, { disablePadding: !pkcs7PaddingEnabled });
3222
}
3323
if (["aes-128-ctr", "aes-256-ctr"].includes(mode)) {
34-
keyMode = "ctr";
24+
return ctr(key, iv);
3525
}
36-
if (!keyMode) {
37-
throw new Error("AES: unsupported mode");
38-
}
39-
const wKey = await crypto.web.subtle.importKey(
40-
"raw",
41-
key,
42-
{ name: `AES-${keyMode.toUpperCase()}`, length: key.length * 8 },
43-
true,
44-
["encrypt", "decrypt"]
45-
);
46-
// node.js uses whole 128 bit as a counter, without nonce, instead of 64 bit
47-
// recommended by NIST SP800-38A
48-
return [wKey, { name: `aes-${keyMode}`, iv, counter: iv, length: 128 }];
26+
throw new Error("AES: unsupported mode");
4927
}
5028

51-
export async function encrypt(
29+
export function encrypt(
5230
msg: Uint8Array,
5331
key: Uint8Array,
5432
iv: Uint8Array,
5533
mode = "aes-128-ctr",
5634
pkcs7PaddingEnabled = true
57-
): Promise<Uint8Array> {
58-
validateOpt(key, iv, mode);
59-
if (crypto.web) {
60-
const [wKey, wOpt] = await getBrowserKey(mode, key, iv);
61-
const cipher = await crypto.web.subtle.encrypt(wOpt, wKey, msg);
62-
// Remove PKCS7 padding on cbc mode by stripping end of message
63-
let res = new Uint8Array(cipher);
64-
if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc" && !(msg.length % 16)) {
65-
res = res.slice(0, -16);
66-
}
67-
return res;
68-
} else if (crypto.node) {
69-
const cipher = crypto.node.createCipheriv(mode, key, iv);
70-
cipher.setAutoPadding(pkcs7PaddingEnabled);
71-
return concatBytes(cipher.update(msg), cipher.final());
72-
} else {
73-
throw new Error("The environment doesn't have AES module");
74-
}
35+
): Uint8Array {
36+
return getCipher(key, iv, mode, pkcs7PaddingEnabled).encrypt(msg);
7537
}
7638

77-
async function getPadding(
78-
cypherText: Uint8Array,
79-
key: Uint8Array,
80-
iv: Uint8Array,
81-
mode: string
82-
) {
83-
const lastBlock = cypherText.slice(-16);
84-
for (let i = 0; i < 16; i++) {
85-
// Undo xor of iv and fill with lastBlock ^ padding (16)
86-
lastBlock[i] ^= iv[i] ^ 16;
87-
}
88-
const res = await encrypt(lastBlock, key, iv, mode);
89-
return res.slice(0, 16);
90-
}
91-
92-
export async function decrypt(
93-
cypherText: Uint8Array,
39+
export function decrypt(
40+
ciphertext: Uint8Array,
9441
key: Uint8Array,
9542
iv: Uint8Array,
9643
mode = "aes-128-ctr",
9744
pkcs7PaddingEnabled = true
98-
): Promise<Uint8Array> {
99-
validateOpt(key, iv, mode);
100-
if (crypto.web) {
101-
const [wKey, wOpt] = await getBrowserKey(mode, key, iv);
102-
// Add empty padding so Chrome will correctly decrypt message
103-
if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc") {
104-
const padding = await getPadding(cypherText, key, iv, mode);
105-
cypherText = concatBytes(cypherText, padding);
106-
}
107-
const msg = await crypto.web.subtle.decrypt(wOpt, wKey, cypherText);
108-
const msgBytes = new Uint8Array(msg);
109-
// Safari always ignores padding (if no padding -> broken message)
110-
if (wOpt.name === "aes-cbc") {
111-
const encrypted = await encrypt(msgBytes, key, iv, mode);
112-
if (!equalsBytes(encrypted, cypherText)) {
113-
throw new Error("AES: wrong padding");
114-
}
115-
}
116-
return msgBytes;
117-
} else if (crypto.node) {
118-
const decipher = crypto.node.createDecipheriv(mode, key, iv);
119-
decipher.setAutoPadding(pkcs7PaddingEnabled);
120-
return concatBytes(decipher.update(cypherText), decipher.final());
121-
} else {
122-
throw new Error("The environment doesn't have AES module");
123-
}
45+
): Uint8Array {
46+
return getCipher(key, iv, mode, pkcs7PaddingEnabled).decrypt(ciphertext);
12447
}

test/package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/test-vectors/aes.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { decrypt, encrypt } from "ethereum-cryptography/aes";
22
import { hexToBytes, toHex } from "ethereum-cryptography/utils";
3-
import { deepStrictEqual, rejects } from "./assert";
3+
import { deepStrictEqual, throws } from "./assert";
44
// Test vectors taken from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf
55
const TEST_VECTORS = [
66
{
@@ -94,8 +94,8 @@ const TEST_VECTORS = [
9494

9595
describe("aes", () => {
9696
for (const [i, vector] of TEST_VECTORS.entries()) {
97-
it(`Should encrypt the test ${i} correctly`, async () => {
98-
const encrypted = await encrypt(
97+
it(`Should encrypt the test ${i} correctly`, () => {
98+
const encrypted = encrypt(
9999
hexToBytes(vector.msg),
100100
hexToBytes(vector.key),
101101
hexToBytes(vector.iv),
@@ -106,8 +106,8 @@ describe("aes", () => {
106106
deepStrictEqual(toHex(encrypted), vector.cypherText);
107107
});
108108

109-
it(`Should decrypt the test ${i} correctly`, async () => {
110-
const decrypted = await decrypt(
109+
it(`Should decrypt the test ${i} correctly`, () => {
110+
const decrypted = decrypt(
111111
hexToBytes(vector.cypherText),
112112
hexToBytes(vector.key),
113113
hexToBytes(vector.iv),
@@ -119,8 +119,8 @@ describe("aes", () => {
119119
});
120120
}
121121

122-
it("Should throw when not padding automatically and the message isn't the right size", async () => {
123-
rejects(() =>
122+
it("Should throw when not padding automatically and the message isn't the right size", () => {
123+
throws(() =>
124124
encrypt(
125125
hexToBytes("abcd"),
126126
hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"),
@@ -131,8 +131,8 @@ describe("aes", () => {
131131
);
132132
});
133133

134-
it("Should throw when trying to use non-aes modes", async () => {
135-
rejects(() =>
134+
it("Should throw when trying to use non-aes modes", () => {
135+
throws(() =>
136136
encrypt(
137137
hexToBytes("abcd"),
138138
hexToBytes("2b7e151628aed2a6abf7158809cf4f3c"),
@@ -143,7 +143,7 @@ describe("aes", () => {
143143
);
144144
});
145145

146-
it("aes-ctr bug (browser/node result mismatch)", async () => {
146+
it("aes-ctr bug (browser/node result mismatch)", () => {
147147
// NOTE: full 0xff iv causes difference on counter overflow in CTR mode
148148
const iv = "ffffffffffffffffffffffffffffffff";
149149
const vectors = [
@@ -184,9 +184,9 @@ describe("aes", () => {
184184
const msg = hexToBytes(v.msg);
185185
const key = hexToBytes(v.key);
186186
const iv = hexToBytes(v.iv);
187-
const res = await encrypt(msg, key, iv, v.mode);
187+
const res = encrypt(msg, key, iv, v.mode);
188188
deepStrictEqual(toHex(res), v.result);
189-
const clearText = await decrypt(res, key, iv, v.mode);
189+
const clearText = decrypt(res, key, iv, v.mode);
190190
deepStrictEqual(clearText, msg);
191191
}
192192
});

0 commit comments

Comments
 (0)