diff --git a/web/account/qr-code-login.react.js b/web/account/qr-code-login.react.js
--- a/web/account/qr-code-login.react.js
+++ b/web/account/qr-code-login.react.js
@@ -4,10 +4,10 @@
 import * as React from 'react';
 
 import { qrCodeLinkURL } from 'lib/facts/links.js';
+import { generateKeyCommon } from 'lib/media/aes-crypto-utils-common.js';
 import { uintArrayToHexString } from 'lib/media/data-utils.js';
 
 import css from './qr-code-login.css';
-import { generateKey } from '../media/aes-crypto-utils.js';
 import { useSelector } from '../redux/redux-utils.js';
 
 function QrCodeLogin(): React.Node {
@@ -22,7 +22,7 @@
         return;
       }
 
-      const rawAESKey: Uint8Array = await generateKey();
+      const rawAESKey: Uint8Array = await generateKeyCommon(crypto);
       const aesKeyAsHexString: string = uintArrayToHexString(rawAESKey);
 
       const url = qrCodeLinkURL(aesKeyAsHexString, ed25519Key);
diff --git a/web/media/aes-crypto-utils.js b/web/media/aes-crypto-utils.js
deleted file mode 100644
--- a/web/media/aes-crypto-utils.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// @flow
-
-const KEY_SIZE = 32; // bytes
-const IV_LENGTH = 12; // bytes - unique Initialization Vector (nonce)
-const TAG_LENGTH = 16; // bytes - GCM auth tag
-
-async function generateKey(): Promise<Uint8Array> {
-  const algorithm = { name: 'AES-GCM', length: 256 };
-  const key = await crypto.subtle.generateKey(algorithm, true, [
-    'encrypt',
-    'decrypt',
-  ]);
-  const keyData = await crypto.subtle.exportKey('raw', key);
-  return new Uint8Array(keyData);
-}
-
-async function encrypt(
-  keyBytes: Uint8Array,
-  plaintext: Uint8Array,
-): Promise<Uint8Array> {
-  if (keyBytes.length !== KEY_SIZE) {
-    throw new Error('Invalid AES key size');
-  }
-
-  // we're creating the buffer now so we can avoid reallocating it later
-  const outputBuffer = new ArrayBuffer(
-    plaintext.length + IV_LENGTH + TAG_LENGTH,
-  );
-  const ivBytes = new Uint8Array(outputBuffer, 0, IV_LENGTH);
-  const iv = crypto.getRandomValues(ivBytes);
-
-  const algorithm = { name: 'AES-GCM', iv: iv, tagLength: TAG_LENGTH * 8 };
-  const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, [
-    'encrypt',
-  ]);
-  const ciphertextWithTag = await crypto.subtle.encrypt(
-    algorithm,
-    key,
-    plaintext,
-  );
-
-  const result = new Uint8Array(outputBuffer);
-  result.set(new Uint8Array(ciphertextWithTag), iv.length);
-  return result;
-}
-
-async function decrypt(
-  keyBytes: Uint8Array,
-  sealedData: Uint8Array,
-): Promise<Uint8Array> {
-  if (keyBytes.length !== KEY_SIZE) {
-    throw new Error('Invalid AES key size');
-  }
-  if (sealedData.length < IV_LENGTH + TAG_LENGTH) {
-    throw new Error('Invalid ciphertext size');
-  }
-
-  const iv = sealedData.subarray(0, IV_LENGTH);
-  const ciphertextWithTag = sealedData.subarray(IV_LENGTH);
-
-  const algorithm = { name: 'AES-GCM', iv, tagLength: TAG_LENGTH * 8 };
-  const key = await crypto.subtle.importKey('raw', keyBytes, 'AES-GCM', false, [
-    'decrypt',
-  ]);
-
-  const plaintextBuffer = await crypto.subtle.decrypt(
-    algorithm,
-    key,
-    ciphertextWithTag,
-  );
-  return new Uint8Array(plaintextBuffer);
-}
-
-export { generateKey, encrypt, decrypt };
diff --git a/web/media/aes-crypto-utils.test.js b/web/media/aes-crypto-utils.test.js
--- a/web/media/aes-crypto-utils.test.js
+++ b/web/media/aes-crypto-utils.test.js
@@ -1,6 +1,10 @@
 // @flow
 
-import { generateKey, encrypt, decrypt } from './aes-crypto-utils.js';
+import {
+  generateKeyCommon,
+  encryptCommon,
+  decryptCommon,
+} from 'lib/media/aes-crypto-utils-common.js';
 
 // some mock data
 const testPlaintext = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
@@ -20,69 +24,77 @@
 
 describe('generateKey', () => {
   it('generates 32-byte AES key', async () => {
-    const key = await generateKey();
+    const key = await generateKeyCommon(crypto);
     expect(key.length).toBe(32);
   });
 });
 
 describe('encrypt', () => {
   it('generates ciphertext with IV and tag included', async () => {
-    const encrypted = await encrypt(testEncryptionKey, testPlaintext);
+    const encrypted = await encryptCommon(
+      crypto,
+      testEncryptionKey,
+      testPlaintext,
+    );
     // IV and tag are randomly generated, so we can't check the exact value
     // IV + plaintext + tag = 12 + 11 + 16 = 39
     expect(encrypted.length).toBe(testPlaintext.length + 12 + 16);
   });
 
   it('is decryptable by decrypt()', async () => {
-    const key = await generateKey();
-    const encrypted = await encrypt(key, randomData);
-    const decrypted = await decrypt(key, encrypted);
+    const key = await generateKeyCommon(crypto);
+    const encrypted = await encryptCommon(crypto, key, randomData);
+    const decrypted = await decryptCommon(crypto, key, encrypted);
     expect(decrypted).toEqual(randomData);
   });
 });
 
 describe('decrypt', () => {
   it('decrypts ciphertext', async () => {
-    const decrypted = await decrypt(testEncryptionKey, testSealedData);
+    const decrypted = await decryptCommon(
+      crypto,
+      testEncryptionKey,
+      testSealedData,
+    );
     expect(decrypted).toEqual(testPlaintext);
   });
 
   it('fails with wrong key', async () => {
-    const key = await generateKey();
-    const encrypted = await encrypt(key, randomData);
+    const key = await generateKeyCommon(crypto);
+    const encrypted = await encryptCommon(crypto, key, randomData);
 
-    const wrongKey = await generateKey();
-    await expect(decrypt(wrongKey, encrypted)).rejects.toThrow();
+    const wrongKey = await generateKeyCommon(crypto);
+    await expect(decryptCommon(crypto, wrongKey, encrypted)).rejects.toThrow();
   });
 
   it('fails with wrong ciphertext', async () => {
-    const key = await generateKey();
-    const encrypted = await encrypt(key, randomData);
+    const key = await generateKeyCommon(crypto);
+    const encrypted = await encryptCommon(crypto, key, randomData);
 
     // change the first byte of the ciphertext (it's 13th byte in the buffer)
     // first 12 bytes are IV, so changing the first byte of the ciphertext
     encrypted[12] = encrypted[12] ^ 1;
 
-    await expect(decrypt(key, encrypted)).rejects.toThrow();
+    await expect(decryptCommon(crypto, key, encrypted)).rejects.toThrow();
   });
 
   it('fails with wrong IV', async () => {
-    const key = await generateKey();
-    const encrypted = await encrypt(key, randomData);
+    const key = await generateKeyCommon(crypto);
+    const encrypted = await encryptCommon(crypto, key, randomData);
 
     // change the first byte of the IV (it's 1st byte in the buffer)
     encrypted[0] = encrypted[0] ^ 1;
 
-    await expect(decrypt(key, encrypted)).rejects.toThrow();
+    await expect(decryptCommon(crypto, key, encrypted)).rejects.toThrow();
   });
 
   it('fails with wrong tag', async () => {
-    const key = await generateKey();
-    const encrypted = await encrypt(key, randomData);
+    const key = await generateKeyCommon(crypto);
+    const encrypted = await encryptCommon(crypto, key, randomData);
 
     // change the last byte of the tag (tag is the last 16 bytes of the buffer)
     encrypted[encrypted.length - 1] = encrypted[encrypted.length - 1] ^ 1;
 
-    await expect(decrypt(key, encrypted)).rejects.toThrow();
+    await expect(decryptCommon(crypto, key, encrypted)).rejects.toThrow();
   });
 });
diff --git a/web/media/encryption-utils.js b/web/media/encryption-utils.js
--- a/web/media/encryption-utils.js
+++ b/web/media/encryption-utils.js
@@ -3,6 +3,7 @@
 import invariant from 'invariant';
 import { thumbHashToDataURL } from 'thumbhash';
 
+import * as AES from 'lib/media/aes-crypto-utils-common.js';
 import { hexToUintArray, uintArrayToHexString } from 'lib/media/data-utils.js';
 import { fileInfoFromData } from 'lib/media/file-utils.js';
 import { fetchableMediaURI } from 'lib/media/media-utils.js';
@@ -13,7 +14,6 @@
 import { getMessageForException } from 'lib/utils/errors.js';
 import { calculatePaddedLength, pad, unpad } from 'lib/utils/pkcs7-padding.js';
 
-import * as AES from './aes-crypto-utils.js';
 import { base64DecodeBuffer } from '../utils/base64-utils.js';
 
 const PADDING_THRESHOLD = 5000000; // 5MB
@@ -61,8 +61,8 @@
   let key, encryptedData, sha256;
   try {
     const plaintextData = shouldPad ? pad(data) : data;
-    key = await AES.generateKey();
-    encryptedData = await AES.encrypt(key, plaintextData);
+    key = await AES.generateKeyCommon(crypto);
+    encryptedData = await AES.encryptCommon(crypto, key, plaintextData);
 
     const hashBytes = await crypto.subtle.digest('SHA-256', encryptedData);
     sha256 = btoa(String.fromCharCode(...new Uint8Array(hashBytes)));
@@ -182,7 +182,7 @@
   const decryptStartTime = Date.now();
   try {
     const keyBytes = hexToUintArray(encryptionKey);
-    const plaintext = await AES.decrypt(keyBytes, data);
+    const plaintext = await AES.decryptCommon(crypto, keyBytes, data);
     decryptedData =
       plaintext.byteLength > PADDING_THRESHOLD ? plaintext : unpad(plaintext);
   } catch (e) {
@@ -243,7 +243,8 @@
   keyHex: string,
 ): Promise<string> {
   const encryptedData = base64DecodeBuffer(encryptedThumbHash);
-  const thumbhashBytes = await AES.decrypt(
+  const thumbhashBytes = await AES.decryptCommon(
+    crypto,
     hexToUintArray(keyHex),
     encryptedData,
   );
diff --git a/web/media/image-utils.js b/web/media/image-utils.js
--- a/web/media/image-utils.js
+++ b/web/media/image-utils.js
@@ -3,6 +3,7 @@
 import EXIF from 'exif-js';
 import { rgbaToThumbHash } from 'thumbhash';
 
+import * as AES from 'lib/media/aes-crypto-utils-common.js';
 import { hexToUintArray } from 'lib/media/data-utils.js';
 import type {
   GetOrientationMediaMissionStep,
@@ -11,7 +12,6 @@
 } from 'lib/types/media-types.js';
 import { getMessageForException } from 'lib/utils/errors.js';
 
-import * as AES from './aes-crypto-utils.js';
 import { preloadImage } from './media-utils.js';
 import { base64EncodeBuffer } from '../utils/base64-utils.js';
 
@@ -103,7 +103,8 @@
 
   if (encryptionKey) {
     try {
-      const encryptedThumbHash = await AES.encrypt(
+      const encryptedThumbHash = await AES.encryptCommon(
+        crypto,
         hexToUintArray(encryptionKey),
         binaryThumbHash,
       );