rsa.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import Base64 from "../base64.js";
  2. import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
  3. export class RSACipher {
  4. constructor() {
  5. this._keyLength = 0;
  6. this._keyBytes = 0;
  7. this._n = null;
  8. this._e = null;
  9. this._d = null;
  10. this._nBigInt = null;
  11. this._eBigInt = null;
  12. this._dBigInt = null;
  13. this._extractable = false;
  14. }
  15. get algorithm() {
  16. return { name: "RSA-PKCS1-v1_5" };
  17. }
  18. _base64urlDecode(data) {
  19. data = data.replace(/-/g, "+").replace(/_/g, "/");
  20. data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
  21. return Base64.decode(data);
  22. }
  23. _padArray(arr, length) {
  24. const res = new Uint8Array(length);
  25. res.set(arr, length - arr.length);
  26. return res;
  27. }
  28. static async generateKey(algorithm, extractable, _keyUsages) {
  29. const cipher = new RSACipher;
  30. await cipher._generateKey(algorithm, extractable);
  31. return { privateKey: cipher };
  32. }
  33. async _generateKey(algorithm, extractable) {
  34. this._keyLength = algorithm.modulusLength;
  35. this._keyBytes = Math.ceil(this._keyLength / 8);
  36. const key = await window.crypto.subtle.generateKey(
  37. {
  38. name: "RSA-OAEP",
  39. modulusLength: algorithm.modulusLength,
  40. publicExponent: algorithm.publicExponent,
  41. hash: {name: "SHA-256"},
  42. },
  43. true, ["encrypt", "decrypt"]);
  44. const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
  45. this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
  46. this._nBigInt = u8ArrayToBigInt(this._n);
  47. this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
  48. this._eBigInt = u8ArrayToBigInt(this._e);
  49. this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
  50. this._dBigInt = u8ArrayToBigInt(this._d);
  51. this._extractable = extractable;
  52. }
  53. static async importKey(key, _algorithm, extractable, keyUsages) {
  54. if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
  55. throw new Error("only support importing RSA public key");
  56. }
  57. const cipher = new RSACipher;
  58. await cipher._importKey(key, extractable);
  59. return cipher;
  60. }
  61. async _importKey(key, extractable) {
  62. const n = key.n;
  63. const e = key.e;
  64. if (n.length !== e.length) {
  65. throw new Error("the sizes of modulus and public exponent do not match");
  66. }
  67. this._keyBytes = n.length;
  68. this._keyLength = this._keyBytes * 8;
  69. this._n = new Uint8Array(this._keyBytes);
  70. this._e = new Uint8Array(this._keyBytes);
  71. this._n.set(n);
  72. this._e.set(e);
  73. this._nBigInt = u8ArrayToBigInt(this._n);
  74. this._eBigInt = u8ArrayToBigInt(this._e);
  75. this._extractable = extractable;
  76. }
  77. async encrypt(_algorithm, message) {
  78. if (message.length > this._keyBytes - 11) {
  79. return null;
  80. }
  81. const ps = new Uint8Array(this._keyBytes - message.length - 3);
  82. window.crypto.getRandomValues(ps);
  83. for (let i = 0; i < ps.length; i++) {
  84. ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
  85. }
  86. const em = new Uint8Array(this._keyBytes);
  87. em[1] = 0x02;
  88. em.set(ps, 2);
  89. em.set(message, ps.length + 3);
  90. const emBigInt = u8ArrayToBigInt(em);
  91. const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
  92. return bigIntToU8Array(c, this._keyBytes);
  93. }
  94. async decrypt(_algorithm, message) {
  95. if (message.length !== this._keyBytes) {
  96. return null;
  97. }
  98. const msgBigInt = u8ArrayToBigInt(message);
  99. const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
  100. const em = bigIntToU8Array(emBigInt, this._keyBytes);
  101. if (em[0] !== 0x00 || em[1] !== 0x02) {
  102. return null;
  103. }
  104. let i = 2;
  105. for (; i < em.length; i++) {
  106. if (em[i] === 0x00) {
  107. break;
  108. }
  109. }
  110. if (i === em.length) {
  111. return null;
  112. }
  113. return em.slice(i + 1, em.length);
  114. }
  115. async exportKey() {
  116. if (!this._extractable) {
  117. throw new Error("key is not extractable");
  118. }
  119. return { n: this._n, e: this._e, d: this._d };
  120. }
  121. }