aes.js 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.encrypt = encrypt;
  4. exports.decrypt = decrypt;
  5. const crypto_1 = require("@noble/hashes/crypto");
  6. const utils_js_1 = require("./utils.js");
  7. const crypto = { web: crypto_1.crypto };
  8. function validateOpt(key, iv, mode) {
  9. if (!mode.startsWith("aes-")) {
  10. throw new Error(`AES submodule doesn't support mode ${mode}`);
  11. }
  12. if (iv.length !== 16) {
  13. throw new Error("AES: wrong IV length");
  14. }
  15. if ((mode.startsWith("aes-128") && key.length !== 16) ||
  16. (mode.startsWith("aes-256") && key.length !== 32)) {
  17. throw new Error("AES: wrong key length");
  18. }
  19. }
  20. async function getBrowserKey(mode, key, iv) {
  21. if (!crypto.web) {
  22. throw new Error("Browser crypto not available.");
  23. }
  24. let keyMode;
  25. if (["aes-128-cbc", "aes-256-cbc"].includes(mode)) {
  26. keyMode = "cbc";
  27. }
  28. if (["aes-128-ctr", "aes-256-ctr"].includes(mode)) {
  29. keyMode = "ctr";
  30. }
  31. if (!keyMode) {
  32. throw new Error("AES: unsupported mode");
  33. }
  34. const wKey = await crypto.web.subtle.importKey("raw", key, { name: `AES-${keyMode.toUpperCase()}`, length: key.length * 8 }, true, ["encrypt", "decrypt"]);
  35. // node.js uses whole 128 bit as a counter, without nonce, instead of 64 bit
  36. // recommended by NIST SP800-38A
  37. return [wKey, { name: `aes-${keyMode}`, iv, counter: iv, length: 128 }];
  38. }
  39. async function encrypt(msg, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) {
  40. validateOpt(key, iv, mode);
  41. if (crypto.web) {
  42. const [wKey, wOpt] = await getBrowserKey(mode, key, iv);
  43. const cipher = await crypto.web.subtle.encrypt(wOpt, wKey, msg);
  44. // Remove PKCS7 padding on cbc mode by stripping end of message
  45. let res = new Uint8Array(cipher);
  46. if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc" && !(msg.length % 16)) {
  47. res = res.slice(0, -16);
  48. }
  49. return res;
  50. }
  51. else if (crypto.node) {
  52. const cipher = crypto.node.createCipheriv(mode, key, iv);
  53. cipher.setAutoPadding(pkcs7PaddingEnabled);
  54. return (0, utils_js_1.concatBytes)(cipher.update(msg), cipher.final());
  55. }
  56. else {
  57. throw new Error("The environment doesn't have AES module");
  58. }
  59. }
  60. async function getPadding(cypherText, key, iv, mode) {
  61. const lastBlock = cypherText.slice(-16);
  62. for (let i = 0; i < 16; i++) {
  63. // Undo xor of iv and fill with lastBlock ^ padding (16)
  64. lastBlock[i] ^= iv[i] ^ 16;
  65. }
  66. const res = await encrypt(lastBlock, key, iv, mode);
  67. return res.slice(0, 16);
  68. }
  69. async function decrypt(cypherText, key, iv, mode = "aes-128-ctr", pkcs7PaddingEnabled = true) {
  70. validateOpt(key, iv, mode);
  71. if (crypto.web) {
  72. const [wKey, wOpt] = await getBrowserKey(mode, key, iv);
  73. // Add empty padding so Chrome will correctly decrypt message
  74. if (!pkcs7PaddingEnabled && wOpt.name === "aes-cbc") {
  75. const padding = await getPadding(cypherText, key, iv, mode);
  76. cypherText = (0, utils_js_1.concatBytes)(cypherText, padding);
  77. }
  78. const msg = await crypto.web.subtle.decrypt(wOpt, wKey, cypherText);
  79. const msgBytes = new Uint8Array(msg);
  80. // Safari always ignores padding (if no padding -> broken message)
  81. if (wOpt.name === "aes-cbc") {
  82. const encrypted = await encrypt(msgBytes, key, iv, mode);
  83. if (!(0, utils_js_1.equalsBytes)(encrypted, cypherText)) {
  84. throw new Error("AES: wrong padding");
  85. }
  86. }
  87. return msgBytes;
  88. }
  89. else if (crypto.node) {
  90. const decipher = crypto.node.createDecipheriv(mode, key, iv);
  91. decipher.setAutoPadding(pkcs7PaddingEnabled);
  92. return (0, utils_js_1.concatBytes)(decipher.update(cypherText), decipher.final());
  93. }
  94. else {
  95. throw new Error("The environment doesn't have AES module");
  96. }
  97. }