Skip to content

Commit a2817bb

Browse files
committed
Remove AES functions in aes.ts and export new ones for backward compatibility
1 parent d29cfef commit a2817bb

File tree

1 file changed

+4
-150
lines changed

1 file changed

+4
-150
lines changed

src/crypto/aes.ts

+4-150
Original file line numberDiff line numberDiff line change
@@ -14,153 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { decodeBase64, encodeBase64 } from "../base64.ts";
18-
19-
// salt for HKDF, with 8 bytes of zeros
20-
const zeroSalt = new Uint8Array(8);
21-
22-
export interface IEncryptedPayload {
23-
[key: string]: any; // extensible
24-
/** the initialization vector in base64 */
25-
iv: string;
26-
/** the ciphertext in base64 */
27-
ciphertext: string;
28-
/** the HMAC in base64 */
29-
mac: string;
30-
}
31-
32-
/**
33-
* Encrypt a string using AES-CTR.
34-
*
35-
* @param data - the plaintext to encrypt
36-
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key for
37-
* encryption. Obviously, the same key must be provided when decrypting.
38-
* @param name - the name of the secret. Used as an input to the HKDF operation which is used to derive the AES key,
39-
* so again the same value must be provided when decrypting.
40-
* @param ivStr - the base64-encoded initialization vector to use. If not supplied, a random one will be generated.
41-
*
42-
* @returns The encrypted result, including the ciphertext itself, the initialization vector (as supplied in `ivStr`,
43-
* or generated), and an HMAC on the ciphertext — all base64-encoded.
44-
*/
45-
export async function encryptAES(
46-
data: string,
47-
key: Uint8Array,
48-
name: string,
49-
ivStr?: string,
50-
): Promise<IEncryptedPayload> {
51-
let iv: Uint8Array;
52-
if (ivStr) {
53-
iv = decodeBase64(ivStr);
54-
} else {
55-
iv = new Uint8Array(16);
56-
globalThis.crypto.getRandomValues(iv);
57-
58-
// clear bit 63 of the IV to stop us hitting the 64-bit counter boundary
59-
// (which would mean we wouldn't be able to decrypt on Android). The loss
60-
// of a single bit of iv is a price we have to pay.
61-
iv[8] &= 0x7f;
62-
}
63-
64-
const [aesKey, hmacKey] = await deriveKeys(key, name);
65-
const encodedData = new TextEncoder().encode(data);
66-
67-
const ciphertext = await globalThis.crypto.subtle.encrypt(
68-
{
69-
name: "AES-CTR",
70-
counter: iv,
71-
length: 64,
72-
},
73-
aesKey,
74-
encodedData,
75-
);
76-
77-
const hmac = await globalThis.crypto.subtle.sign({ name: "HMAC" }, hmacKey, ciphertext);
78-
79-
return {
80-
iv: encodeBase64(iv),
81-
ciphertext: encodeBase64(ciphertext),
82-
mac: encodeBase64(hmac),
83-
};
84-
}
85-
86-
/**
87-
* Decrypt an AES-encrypted string.
88-
*
89-
* @param data - the encrypted data, returned by {@link encryptAES}.
90-
* @param key - the encryption key to use as an input to the HKDF function which is used to derive the AES key. Must
91-
* be the same as provided to {@link encryptAES}.
92-
* @param name - the name of the secret. Also used as an input to the HKDF operation which is used to derive the AES
93-
* key, so again must be the same as provided to {@link encryptAES}.
94-
*/
95-
export async function decryptAES(data: IEncryptedPayload, key: Uint8Array, name: string): Promise<string> {
96-
const [aesKey, hmacKey] = await deriveKeys(key, name);
97-
98-
const ciphertext = decodeBase64(data.ciphertext);
99-
100-
if (!(await globalThis.crypto.subtle.verify({ name: "HMAC" }, hmacKey, decodeBase64(data.mac), ciphertext))) {
101-
throw new Error(`Error decrypting secret ${name}: bad MAC`);
102-
}
103-
104-
const plaintext = await globalThis.crypto.subtle.decrypt(
105-
{
106-
name: "AES-CTR",
107-
counter: decodeBase64(data.iv),
108-
length: 64,
109-
},
110-
aesKey,
111-
ciphertext,
112-
);
113-
114-
return new TextDecoder().decode(new Uint8Array(plaintext));
115-
}
116-
117-
async function deriveKeys(key: Uint8Array, name: string): Promise<[CryptoKey, CryptoKey]> {
118-
const hkdfkey = await globalThis.crypto.subtle.importKey("raw", key, { name: "HKDF" }, false, ["deriveBits"]);
119-
const keybits = await globalThis.crypto.subtle.deriveBits(
120-
{
121-
name: "HKDF",
122-
salt: zeroSalt,
123-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
124-
// @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879
125-
info: new TextEncoder().encode(name),
126-
hash: "SHA-256",
127-
},
128-
hkdfkey,
129-
512,
130-
);
131-
132-
const aesKey = keybits.slice(0, 32);
133-
const hmacKey = keybits.slice(32);
134-
135-
const aesProm = globalThis.crypto.subtle.importKey("raw", aesKey, { name: "AES-CTR" }, false, [
136-
"encrypt",
137-
"decrypt",
138-
]);
139-
140-
const hmacProm = globalThis.crypto.subtle.importKey(
141-
"raw",
142-
hmacKey,
143-
{
144-
name: "HMAC",
145-
hash: { name: "SHA-256" },
146-
},
147-
false,
148-
["sign", "verify"],
149-
);
150-
151-
return Promise.all([aesProm, hmacProm]);
152-
}
153-
154-
// string of zeroes, for calculating the key check
155-
const ZERO_STR = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
156-
157-
/** Calculate the MAC for checking the key.
158-
*
159-
* @param key - the key to use
160-
* @param iv - The initialization vector as a base64-encoded string.
161-
* If omitted, a random initialization vector will be created.
162-
* @returns An object that contains, `mac` and `iv` properties.
163-
*/
164-
export function calculateKeyCheck(key: Uint8Array, iv?: string): Promise<IEncryptedPayload> {
165-
return encryptAES(ZERO_STR, key, "", iv);
166-
}
17+
// Export for backwards compatibility
18+
export type { SecretEncryptedPayload as IEncryptedPayload } from "../utils/encryptAES.ts";
19+
export { encryptAES } from "../utils/encryptAES.ts";
20+
export { decryptAES } from "../utils/decryptAES.ts";

0 commit comments

Comments
 (0)