encryption.ts 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { ENCRYPTION_KEY_BITS } from "../constants";
  2. import { blobToArrayBuffer } from "./blob";
  3. export const IV_LENGTH_BYTES = 12;
  4. export const createIV = () => {
  5. const arr = new Uint8Array(IV_LENGTH_BYTES);
  6. return window.crypto.getRandomValues(arr);
  7. };
  8. export const generateEncryptionKey = async <
  9. T extends "string" | "cryptoKey" = "string",
  10. >(
  11. returnAs?: T,
  12. ): Promise<T extends "cryptoKey" ? CryptoKey : string> => {
  13. const key = await window.crypto.subtle.generateKey(
  14. {
  15. name: "AES-GCM",
  16. length: ENCRYPTION_KEY_BITS,
  17. },
  18. true, // extractable
  19. ["encrypt", "decrypt"],
  20. );
  21. return (
  22. returnAs === "cryptoKey"
  23. ? key
  24. : (await window.crypto.subtle.exportKey("jwk", key)).k
  25. ) as T extends "cryptoKey" ? CryptoKey : string;
  26. };
  27. export const getCryptoKey = (key: string, usage: KeyUsage) =>
  28. window.crypto.subtle.importKey(
  29. "jwk",
  30. {
  31. alg: "A128GCM",
  32. ext: true,
  33. k: key,
  34. key_ops: ["encrypt", "decrypt"],
  35. kty: "oct",
  36. },
  37. {
  38. name: "AES-GCM",
  39. length: ENCRYPTION_KEY_BITS,
  40. },
  41. false, // extractable
  42. [usage],
  43. );
  44. export const encryptData = async (
  45. key: string | CryptoKey,
  46. data: Uint8Array | ArrayBuffer | Blob | File | string,
  47. ): Promise<{ encryptedBuffer: ArrayBuffer; iv: Uint8Array }> => {
  48. const importedKey =
  49. typeof key === "string" ? await getCryptoKey(key, "encrypt") : key;
  50. const iv = createIV();
  51. const buffer: ArrayBuffer | Uint8Array =
  52. typeof data === "string"
  53. ? new TextEncoder().encode(data)
  54. : data instanceof Uint8Array
  55. ? data
  56. : data instanceof Blob
  57. ? await blobToArrayBuffer(data)
  58. : data;
  59. // We use symmetric encryption. AES-GCM is the recommended algorithm and
  60. // includes checks that the ciphertext has not been modified by an attacker.
  61. const encryptedBuffer = await window.crypto.subtle.encrypt(
  62. {
  63. name: "AES-GCM",
  64. iv,
  65. },
  66. importedKey,
  67. buffer as ArrayBuffer | Uint8Array,
  68. );
  69. return { encryptedBuffer, iv };
  70. };
  71. export const decryptData = async (
  72. iv: Uint8Array,
  73. encrypted: Uint8Array | ArrayBuffer,
  74. privateKey: string,
  75. ): Promise<ArrayBuffer> => {
  76. const key = await getCryptoKey(privateKey, "decrypt");
  77. return window.crypto.subtle.decrypt(
  78. {
  79. name: "AES-GCM",
  80. iv,
  81. },
  82. key,
  83. encrypted,
  84. );
  85. };