feat: impl libsodium crypto

This commit is contained in:
thecodrr
2020-04-11 17:02:11 +05:00
parent 78f8a86181
commit 7dae60e4f0
2 changed files with 128 additions and 0 deletions

View 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");
});

View 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;