mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 15:09:33 +01:00
feat: impl libsodium crypto
This commit is contained in:
41
packages/core/utils/__tests__/crypto.test.js
Normal file
41
packages/core/utils/__tests__/crypto.test.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import Crypto from "../crypto";
|
||||
|
||||
test("libsodium should load", async () => {
|
||||
const crypto = new Crypto();
|
||||
await crypto.init();
|
||||
expect(crypto.isReady).toBe(true);
|
||||
});
|
||||
|
||||
test("crypto should throw if init has not been called", () => {
|
||||
const crypto = new Crypto();
|
||||
expect(() =>
|
||||
crypto.encrypt({ password: "i_am_a_password", data: "hello world" })
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("encrypt should encrypt the data", async () => {
|
||||
const crypto = new Crypto();
|
||||
await crypto.init();
|
||||
const result = crypto.encrypt({
|
||||
password: "i_am_a_password",
|
||||
data: "hello world",
|
||||
});
|
||||
expect(result.cipher).not.toBe("hello world");
|
||||
expect(result.iv).toBeDefined();
|
||||
expect(result.salt).toBeDefined();
|
||||
});
|
||||
|
||||
test("decrypt should result in plain text", async () => {
|
||||
const crypto = new Crypto();
|
||||
await crypto.init();
|
||||
const result = crypto.encrypt({
|
||||
password: "i_am_a_password",
|
||||
data: "hello world",
|
||||
});
|
||||
|
||||
const decrypted = crypto.decrypt({
|
||||
password: "i_am_a_password",
|
||||
data: { ...result },
|
||||
});
|
||||
expect(decrypted).toBe("hello world");
|
||||
});
|
||||
87
packages/core/utils/crypto.js
Normal file
87
packages/core/utils/crypto.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import _sodium from "libsodium-wrappers";
|
||||
|
||||
export async function newSodium() {
|
||||
await _sodium.ready;
|
||||
return _sodium;
|
||||
}
|
||||
|
||||
class Crypto {
|
||||
constructor() {
|
||||
this.isReady = false;
|
||||
this.sodium = _sodium;
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.sodium = await newSodium();
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
_throwIfNotReady() {
|
||||
if (!this.isReady)
|
||||
throw new Error("libsodium is not ready yet. Please call init()");
|
||||
}
|
||||
|
||||
_deriveKey(password, keyLength, salt) {
|
||||
this._throwIfNotReady();
|
||||
salt =
|
||||
salt || this.sodium.randombytes_buf(this.sodium.crypto_pwhash_SALTBYTES);
|
||||
const key = this.sodium.crypto_pwhash(
|
||||
keyLength,
|
||||
password,
|
||||
salt,
|
||||
3, // operations limit
|
||||
1024 * 1024 * 4, // memory limit (4MB)
|
||||
this.sodium.crypto_pwhash_ALG_ARGON2I13
|
||||
);
|
||||
const saltHex = this.sodium.to_hex(salt);
|
||||
this.sodium.memzero(salt);
|
||||
return { key, salt: saltHex };
|
||||
}
|
||||
|
||||
encrypt({ password, data }) {
|
||||
this._throwIfNotReady();
|
||||
const { key, salt } = this._deriveKey(
|
||||
password,
|
||||
this.sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES
|
||||
);
|
||||
const nonce = this.sodium.randombytes_buf(
|
||||
this.sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
|
||||
);
|
||||
const cipher = this.sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
data,
|
||||
undefined,
|
||||
undefined,
|
||||
nonce,
|
||||
key,
|
||||
"base64"
|
||||
);
|
||||
const iv = this.sodium.to_hex(nonce);
|
||||
this.sodium.memzero(nonce);
|
||||
this.sodium.memzero(key);
|
||||
return {
|
||||
cipher,
|
||||
iv,
|
||||
salt,
|
||||
};
|
||||
}
|
||||
|
||||
decrypt({ password, data: { salt, iv, cipher } }) {
|
||||
this._throwIfNotReady();
|
||||
const { key } = this._deriveKey(
|
||||
password,
|
||||
this.sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
|
||||
this.sodium.from_hex(salt)
|
||||
);
|
||||
const plainText = this.sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||
undefined,
|
||||
this.sodium.from_base64(cipher),
|
||||
undefined,
|
||||
this.sodium.from_hex(iv),
|
||||
key,
|
||||
"text"
|
||||
);
|
||||
this.sodium.memzero(key);
|
||||
return plainText;
|
||||
}
|
||||
}
|
||||
export default Crypto;
|
||||
Reference in New Issue
Block a user