| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- import Base64 from "../base64.js";
- import { modPow, bigIntToU8Array, u8ArrayToBigInt } from "./bigint.js";
- export class RSACipher {
- constructor() {
- this._keyLength = 0;
- this._keyBytes = 0;
- this._n = null;
- this._e = null;
- this._d = null;
- this._nBigInt = null;
- this._eBigInt = null;
- this._dBigInt = null;
- this._extractable = false;
- }
- get algorithm() {
- return { name: "RSA-PKCS1-v1_5" };
- }
- _base64urlDecode(data) {
- data = data.replace(/-/g, "+").replace(/_/g, "/");
- data = data.padEnd(Math.ceil(data.length / 4) * 4, "=");
- return Base64.decode(data);
- }
- _padArray(arr, length) {
- const res = new Uint8Array(length);
- res.set(arr, length - arr.length);
- return res;
- }
- static async generateKey(algorithm, extractable, _keyUsages) {
- const cipher = new RSACipher;
- await cipher._generateKey(algorithm, extractable);
- return { privateKey: cipher };
- }
- async _generateKey(algorithm, extractable) {
- this._keyLength = algorithm.modulusLength;
- this._keyBytes = Math.ceil(this._keyLength / 8);
- const key = await window.crypto.subtle.generateKey(
- {
- name: "RSA-OAEP",
- modulusLength: algorithm.modulusLength,
- publicExponent: algorithm.publicExponent,
- hash: {name: "SHA-256"},
- },
- true, ["encrypt", "decrypt"]);
- const privateKey = await window.crypto.subtle.exportKey("jwk", key.privateKey);
- this._n = this._padArray(this._base64urlDecode(privateKey.n), this._keyBytes);
- this._nBigInt = u8ArrayToBigInt(this._n);
- this._e = this._padArray(this._base64urlDecode(privateKey.e), this._keyBytes);
- this._eBigInt = u8ArrayToBigInt(this._e);
- this._d = this._padArray(this._base64urlDecode(privateKey.d), this._keyBytes);
- this._dBigInt = u8ArrayToBigInt(this._d);
- this._extractable = extractable;
- }
- static async importKey(key, _algorithm, extractable, keyUsages) {
- if (keyUsages.length !== 1 || keyUsages[0] !== "encrypt") {
- throw new Error("only support importing RSA public key");
- }
- const cipher = new RSACipher;
- await cipher._importKey(key, extractable);
- return cipher;
- }
- async _importKey(key, extractable) {
- const n = key.n;
- const e = key.e;
- if (n.length !== e.length) {
- throw new Error("the sizes of modulus and public exponent do not match");
- }
- this._keyBytes = n.length;
- this._keyLength = this._keyBytes * 8;
- this._n = new Uint8Array(this._keyBytes);
- this._e = new Uint8Array(this._keyBytes);
- this._n.set(n);
- this._e.set(e);
- this._nBigInt = u8ArrayToBigInt(this._n);
- this._eBigInt = u8ArrayToBigInt(this._e);
- this._extractable = extractable;
- }
- async encrypt(_algorithm, message) {
- if (message.length > this._keyBytes - 11) {
- return null;
- }
- const ps = new Uint8Array(this._keyBytes - message.length - 3);
- window.crypto.getRandomValues(ps);
- for (let i = 0; i < ps.length; i++) {
- ps[i] = Math.floor(ps[i] * 254 / 255 + 1);
- }
- const em = new Uint8Array(this._keyBytes);
- em[1] = 0x02;
- em.set(ps, 2);
- em.set(message, ps.length + 3);
- const emBigInt = u8ArrayToBigInt(em);
- const c = modPow(emBigInt, this._eBigInt, this._nBigInt);
- return bigIntToU8Array(c, this._keyBytes);
- }
- async decrypt(_algorithm, message) {
- if (message.length !== this._keyBytes) {
- return null;
- }
- const msgBigInt = u8ArrayToBigInt(message);
- const emBigInt = modPow(msgBigInt, this._dBigInt, this._nBigInt);
- const em = bigIntToU8Array(emBigInt, this._keyBytes);
- if (em[0] !== 0x00 || em[1] !== 0x02) {
- return null;
- }
- let i = 2;
- for (; i < em.length; i++) {
- if (em[i] === 0x00) {
- break;
- }
- }
- if (i === em.length) {
- return null;
- }
- return em.slice(i + 1, em.length);
- }
- async exportKey() {
- if (!this._extractable) {
- throw new Error("key is not extractable");
- }
- return { n: this._n, e: this._e, d: this._d };
- }
- }
|