crypto: add support for bulk decryption

This commit is contained in:
Abdullah Atta
2023-08-23 12:01:35 +05:00
committed by Abdullah Atta
parent 84004c4d73
commit f8ce23aa11
7 changed files with 97 additions and 39 deletions

View File

@@ -23,7 +23,8 @@ import {
OutputFormat,
Cipher,
EncryptionKey,
INNCrypto
INNCrypto,
Output
} from "@notesnook/crypto";
import { NNCryptoWorkerModule } from "./src/worker";
import { wrap } from "comlink";
@@ -38,6 +39,8 @@ export class NNCryptoWorker implements INNCrypto {
if (!this.worker) throw new Error("worker cannot be undefined.");
if (this.isReady) return;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.workermodule = wrap<NNCryptoWorkerModule>(this.worker);
// this.workermodule = await spawn<NNCryptoWorkerModule>(this.worker);
this.isReady = true;
@@ -45,7 +48,7 @@ export class NNCryptoWorker implements INNCrypto {
async encrypt(
key: SerializedKey,
plaintext: Plaintext,
plaintext: Plaintext<OutputFormat>,
outputFormat: OutputFormat = "uint8array"
): Promise<Cipher> {
await this.init();
@@ -54,17 +57,28 @@ export class NNCryptoWorker implements INNCrypto {
return this.workermodule.encrypt(key, plaintext, outputFormat);
}
async decrypt(
async decrypt<TOutputFormat extends OutputFormat>(
key: SerializedKey,
cipherData: Cipher,
outputFormat: OutputFormat = "text"
): Promise<Plaintext> {
outputFormat: TOutputFormat = "text" as TOutputFormat
): Promise<Output<TOutputFormat>> {
await this.init();
if (!this.workermodule) throw new Error("Worker module is not ready.");
return this.workermodule.decrypt(key, cipherData, outputFormat);
}
async decryptMulti<TOutputFormat extends OutputFormat>(
key: SerializedKey,
items: Cipher[],
outputFormat: TOutputFormat = "text" as TOutputFormat
): Promise<Output<TOutputFormat>[]> {
await this.init();
if (!this.workermodule) throw new Error("Worker module is not ready.");
return this.workermodule.decryptMulti(key, items, outputFormat);
}
async hash(password: string, salt: string): Promise<string> {
await this.init();
if (!this.workermodule) throw new Error("Worker module is not ready.");

View File

@@ -24,15 +24,23 @@ import {
SerializedKey
} from "@notesnook/crypto/dist/src/types";
import { expose, transfer } from "comlink";
import { NNCrypto } from "@notesnook/crypto";
import type { Decryption, NNCrypto, Output } from "@notesnook/crypto";
let crypto: NNCrypto | null = null;
let decryption: typeof Decryption | null = null;
async function loadNNCrypto(): Promise<NNCrypto> {
if (crypto) return crypto;
const { NNCrypto } = await import("@notesnook/crypto");
return (crypto = new NNCrypto());
}
async function loadDecryptionModule(): Promise<typeof Decryption> {
if (decryption) return decryption;
const { Decryption } = await import("@notesnook/crypto");
return (decryption = Decryption);
}
const module = {
exportKey: async function (password: string, salt?: string) {
const crypto = await loadNNCrypto();
@@ -48,19 +56,31 @@ const module = {
},
encrypt: async function (
key: SerializedKey,
plaintext: Plaintext,
plaintext: Plaintext<OutputFormat>,
outputFormat?: OutputFormat
) {
const crypto = await loadNNCrypto();
return crypto.encrypt(key, plaintext, outputFormat);
},
decrypt: async function (
decrypt: async function <TOutputFormat extends OutputFormat>(
key: SerializedKey,
cipherData: Cipher,
outputFormat?: OutputFormat
outputFormat: TOutputFormat = "text" as TOutputFormat
) {
const crypto = await loadNNCrypto();
return crypto.decrypt(key, cipherData, outputFormat);
const decryption = await loadDecryptionModule();
return decryption.decrypt(key, cipherData, outputFormat);
},
decryptMulti: async function <TOutputFormat extends OutputFormat>(
key: SerializedKey,
items: Cipher[],
outputFormat: TOutputFormat = "text" as TOutputFormat
) {
const decryption = await loadDecryptionModule();
const decryptedItems: Output<TOutputFormat>[] = [];
for (const cipherData of items) {
decryptedItems.push(decryption.decrypt(key, cipherData, outputFormat));
}
return decryptedItems;
},
createEncryptionStream: async function (key: SerializedKey) {
const crypto = await loadNNCrypto();

View File

@@ -26,6 +26,7 @@ import Password from "./src/password";
import {
Cipher,
EncryptionKey,
Output,
OutputFormat,
Plaintext,
SerializedKey
@@ -40,24 +41,37 @@ export class NNCrypto implements INNCrypto {
this.isReady = true;
}
async encrypt(
async encrypt<TFormat extends OutputFormat>(
key: SerializedKey,
plaintext: Plaintext,
plaintext: Plaintext<TFormat>,
outputFormat: OutputFormat = "uint8array"
): Promise<Cipher> {
await this.init();
return Encryption.encrypt(key, plaintext, outputFormat);
}
async decrypt(
async decrypt<TOutputFormat extends OutputFormat>(
key: SerializedKey,
cipherData: Cipher,
outputFormat: OutputFormat = "text"
): Promise<Plaintext> {
outputFormat: TOutputFormat = "text" as TOutputFormat
): Promise<Output<TOutputFormat>> {
await this.init();
return Decryption.decrypt(key, cipherData, outputFormat);
}
async decryptMulti<TOutputFormat extends OutputFormat>(
key: SerializedKey,
items: Cipher[],
outputFormat: TOutputFormat = "text" as TOutputFormat
): Promise<Output<TOutputFormat>[]> {
await this.init();
const decryptedItems: Output<TOutputFormat>[] = [];
for (const cipherData of items) {
decryptedItems.push(Decryption.decrypt(key, cipherData, outputFormat));
}
return decryptedItems;
}
async hash(password: string, salt: string): Promise<string> {
await this.init();
return Password.hash(password, salt);
@@ -139,3 +153,4 @@ export class NNCrypto implements INNCrypto {
export * from "./src/types";
export * from "./src/interfaces";
export { Decryption };

View File

@@ -29,7 +29,7 @@ import {
from_hex
} from "@notesnook/sodium";
import KeyUtils from "./keyutils";
import { Cipher, OutputFormat, Plaintext, SerializedKey } from "./types";
import { Cipher, Output, OutputFormat, SerializedKey } from "./types";
export default class Decryption {
private static transformInput(cipherData: Cipher): Uint8Array {
@@ -54,11 +54,11 @@ export default class Decryption {
return input;
}
static decrypt(
static decrypt<TOutputFormat extends OutputFormat>(
key: SerializedKey,
cipherData: Cipher,
outputFormat: OutputFormat = "text"
): Plaintext {
outputFormat: TOutputFormat = "text" as TOutputFormat
): Output<TOutputFormat> {
if (!key.salt && cipherData.salt) key.salt = cipherData.salt;
const encryptionKey = KeyUtils.transform(key);
@@ -71,15 +71,13 @@ export default class Decryption {
encryptionKey.key
);
return {
format: outputFormat,
data:
outputFormat === "base64"
? to_base64(plaintext, base64_variants.ORIGINAL)
: outputFormat === "text"
? to_string(plaintext)
: plaintext
};
return (
outputFormat === "base64"
? to_base64(plaintext, base64_variants.ORIGINAL)
: outputFormat === "text"
? to_string(plaintext)
: plaintext
) as Output<TOutputFormat>;
}
static createStream(

View File

@@ -34,7 +34,9 @@ import { Chunk, Cipher, OutputFormat, Plaintext, SerializedKey } from "./types";
const encoder = new TextEncoder();
export default class Encryption {
private static transformInput(plaintext: Plaintext): Uint8Array {
private static transformInput(
plaintext: Plaintext<OutputFormat>
): Uint8Array {
let data: Uint8Array | null = null;
if (typeof plaintext.data === "string" && plaintext.format === "base64") {
data = from_base64(plaintext.data, base64_variants.ORIGINAL);
@@ -49,7 +51,7 @@ export default class Encryption {
static encrypt(
key: SerializedKey,
plaintext: Plaintext,
plaintext: Plaintext<OutputFormat>,
outputFormat: OutputFormat = "uint8array"
): Cipher {
const encryptionKey = KeyUtils.transform(key);

View File

@@ -23,7 +23,8 @@ import {
OutputFormat,
Plaintext,
SerializedKey,
Chunk
Chunk,
Output
} from "./types";
export interface IStreamable {
@@ -34,15 +35,21 @@ export interface IStreamable {
export interface INNCrypto {
encrypt(
key: SerializedKey,
plaintext: Plaintext,
plaintext: Plaintext<OutputFormat>,
outputFormat?: OutputFormat
): Promise<Cipher>;
decrypt(
decrypt<TOutputFormat extends OutputFormat>(
key: SerializedKey,
cipherData: Cipher,
outputFormat?: OutputFormat
): Promise<Plaintext>;
outputFormat?: TOutputFormat
): Promise<Output<TOutputFormat>>;
decryptMulti<TOutputFormat extends OutputFormat>(
key: SerializedKey,
cipherData: Cipher[],
outputFormat?: TOutputFormat
): Promise<Output<TOutputFormat>[]>;
hash(password: string, salt: string): Promise<string>;

View File

@@ -30,10 +30,12 @@ export type Cipher = {
length: number;
};
export type Plaintext = {
format: OutputFormat;
data: string | Uint8Array;
export type Plaintext<TFormat extends OutputFormat> = {
format: TFormat;
data: TFormat extends StringOutputFormat ? string : Uint8Array;
};
export type Output<TFormat extends OutputFormat> =
TFormat extends StringOutputFormat ? string : Uint8Array;
export type SerializedKey = {
password?: string;