JavaScript 65.5%
Python 17.8%
CSS 13%
HTML 3.7%
1import Base64 from "../base64.js";2import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";34export class RSACipher {5 constructor() {6 this._keyLength = 0;7 this._keyBytes = 0;8 this._n = null;9 this._e = null;10 this._d = null;11 this._nBigInt = null;12 this._eBigInt = null;13 this._dBigInt = null;14 this._extractable = false;15 }1617 get algorithm() {18 return { name: "RSA-PKCS1-v1_5" };19 }2021 _base64urlDecode(data) {22 data = data.replace(/-/g, "+").replace(/_/g, "/");23 data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");24 return Base64.decode(data);25 }2627 _padArray(arr, length) {28 const res = new Uint8Array(length);29 res.set(arr, length - arr.length);30 return res;31 }3233 static async generateKey(algorithm, extractable, _keyUsages) {34 const cipher = new RSACipher;35 await cipher._generateKey(algorithm, extractable);36 return { privateKey: cipher };37 }3839 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(43 {44 name: "RSA-OAEP",45 modulusLength: algorithm.modulusLength,46 publicExponent: algorithm.publicExponent,47 hash: {name: "SHA-256"},48 },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;58 }5960 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");63 }64 const cipher = new RSACipher;65 await cipher._importKey(key, extractable);66 return cipher;67 }6869 async _importKey(key, extractable) {70 const n = key.n;71 const e = key.e;72 if (n.length !== e.length) {73 throw new Error("the sizes of modulus and public exponent do not match");74 }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);79 this._n.set(n);80 this._e.set(e);81 this._nBigInt = u8ArrayToBigInt(this._n);82 this._eBigInt = u8ArrayToBigInt(this._e);83 this._extractable = extractable;84 }8586 async encrypt(_algorithm, message) {87 if (message.length > this._keyBytes - 11) {88 return null;89 }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);94 }95 const em = new Uint8Array(this._keyBytes);96 em[1] = 0x02;97 em.set(ps, 2);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);102 }103104 async decrypt(_algorithm, message) {105 if (message.length !== this._keyBytes) {106 return null;107 }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) {112 return null;113 }114 let i = 2;115 for (; i < em.length; i++) {116 if (em[i] === 0x00) {117 break;118 }119 }120 if (i === em.length) {121 return null;122 }123 return em.slice(i + 1, em.length);124 }125126 async exportKey() {127 if (!this._extractable) {128 throw new Error("key is not extractable");129 }130 return { n: this._n, e: this._e, d: this._d };131 }132}133