1import Base64 from "../base64.js";
2import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
4export class RSACipher {
14 this._extractable = false;
18 return { name: "RSA-PKCS1-v1_5" };
21 _base64urlDecode(data) {
22 data = data.replace(/-/g, "+").replace(/_/g, "/");
23 data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
24 return Base64.decode(data);
27 _padArray(arr, length) {
28 const res = new Uint8Array(length);
29 res.set(arr, length - arr.length);
33 static async generateKey(algorithm, extractable, _keyUsages) {
34 const cipher = new RSACipher;
35 await cipher._generateKey(algorithm, extractable);
36 return { privateKey: cipher };
39 async _generateKey(algorithm, extractable) {
40 this._keyLength = algorithm.modulusLength;
41 this._keyBytes = Math.ceil(this._keyLength / 8);
42 const key = await window.crypto.subtle.generateKey(
45 modulusLength: algorithm.modulusLength,
46 publicExponent: algorithm.publicExponent,
47 hash: {name: "SHA-256"},
49 true, ["encrypt", "decrypt"]);
50 const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
51 this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
52 this._nBigInt = u8ArrayToBigInt(this._n);
53 this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
54 this._eBigInt = u8ArrayToBigInt(this._e);
55 this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
56 this._dBigInt = u8ArrayToBigInt(this._d);
57 this._extractable = extractable;
60 static async importKey(key, _algorithm, extractable, keyUsages) {
61 if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
62 throw new Error("only support importing RSA public key");
64 const cipher = new RSACipher;
65 await cipher._importKey(key, extractable);
69 async _importKey(key, extractable) {
72 if (n.length !== e.length) {
73 throw new Error("the sizes of modulus and public exponent do not match");
75 this._keyBytes = n.length;
76 this._keyLength = this._keyBytes * 8;
77 this._n = new Uint8Array(this._keyBytes);
78 this._e = new Uint8Array(this._keyBytes);
81 this._nBigInt = u8ArrayToBigInt(this._n);
82 this._eBigInt = u8ArrayToBigInt(this._e);
83 this._extractable = extractable;
86 async encrypt(_algorithm, message) {
87 if (message.length > this._keyBytes - 11) {
90 const ps = new Uint8Array(this._keyBytes - message.length - 3);
91 window.crypto.getRandomValues(ps);
92 for (let i = 0; i < ps.length; i++) {
93 ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
95 const em = new Uint8Array(this._keyBytes);
98 em.set(message, ps.length + 3);
99 const emBigInt = u8ArrayToBigInt(em);
100 const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
101 return bigIntToU8Array(c, this._keyBytes);
104 async decrypt(_algorithm, message) {
105 if (message.length !== this._keyBytes) {
108 const msgBigInt = u8ArrayToBigInt(message);
109 const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
110 const em = bigIntToU8Array(emBigInt, this._keyBytes);
111 if (em[0] !== 0x00 || em[1] !== 0x02) {
115 for (; i < em.length; i++) {
116 if (em[i] === 0x00) {
120 if (i === em.length) {
123 return em.slice(i + 1, em.length);
127 if (!this._extractable) {
128 throw new Error("key is not extractable");
130 return { n: this._n, e: this._e, d: this._d };