Skip to content

Commit c072d89

Browse files
author
Collin K. Cusce
committed
added cryptographic functions to slopes
1 parent 3892088 commit c072d89

File tree

4 files changed

+239
-1
lines changed

4 files changed

+239
-1
lines changed

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@types/bn.js": "^4.11.6",
3030
"@types/create-hash": "^1.2.2",
3131
"@types/jest": "^24.0.25",
32+
"@types/libsodium-wrappers": "^0.7.7",
3233
"@types/node": "^12.12.24",
3334
"clean-webpack-plugin": "^3.0.0",
3435
"expose-loader": "^0.7.5",
@@ -58,7 +59,7 @@
5859
"buffer": "^5.4.3",
5960
"create-hash": "^1.2.0",
6061
"elliptic": "^6.5.2",
61-
"libsodium": "^0.7.6",
62+
"libsodium-wrappers": "^0.7.6",
6263
"secp256k1": "^4.0.0",
6364
"store2": "^2.10.0"
6465
}

src/utils/crypto.ts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* @module Utils
3+
*/
4+
import { Buffer } from "buffer/";
5+
import libsodiumWrapper from 'libsodium-wrappers';
6+
import createHash from "create-hash";
7+
8+
/**
9+
* Helper utility for encryption and password hashing, browser-safe.
10+
* Encryption is using XChaCha20Poly1305 with a random public nonce.
11+
*/
12+
export class CryptoHelpers {
13+
14+
protected memlimit:number = 524288000;
15+
protected opslimit:number = 3;
16+
protected libsodium:typeof libsodiumWrapper;
17+
18+
/**
19+
* Retrieves the memory limit that can be spent generating password-safe hashes.
20+
*/
21+
getMemoryLimit():number {
22+
return this.memlimit;
23+
}
24+
25+
/**
26+
* Sets the memory limit that can be spent generating password-safe hashes.
27+
*
28+
* @param memlimit The number representing the memory limit, in bytes, that the password-safe algorithm can use
29+
*/
30+
setMemoryLimit(memlimit:number) {
31+
this.memlimit = memlimit;
32+
}
33+
34+
/**
35+
* Retrieves the cpu limit that can be spent generating password-safe hashes.
36+
*/
37+
getOpsLimit():number {
38+
return this.opslimit;
39+
}
40+
41+
/**
42+
* Retrieves the cpu limit that can be spent generating password-safe hashes.
43+
*
44+
* @param opslimit The number representing the cpu limit that the password-safe algorithm can use. Lower is faster.
45+
*/
46+
setOpsLimit(opslimit:number) {
47+
this.opslimit = opslimit;
48+
}
49+
50+
/**
51+
* @ignore
52+
*/
53+
protected async sodium(): Promise<typeof libsodiumWrapper> {
54+
if(!this.libsodium) this.libsodium = (libsodiumWrapper as typeof libsodiumWrapper);
55+
await this.libsodium.ready;
56+
57+
return this.libsodium;
58+
}
59+
60+
61+
/**
62+
* Internal-intended function for cleaning passwords.
63+
*
64+
* @param password
65+
* @param salt
66+
*/
67+
async _pwcleaner(password:string,salt:Uint8Array):Promise<Uint8Array> {
68+
let sodium:typeof libsodiumWrapper = await this.sodium();
69+
let pw:Buffer = Buffer.from(password, 'utf8');
70+
let slt:Buffer;
71+
if(typeof salt === "undefined"){
72+
slt = Buffer.from(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES));
73+
} else {
74+
slt = Buffer.from(salt);
75+
}
76+
let sha:Buffer = this.sha256(Buffer.concat([pw, slt]));
77+
let pwsalted:Uint8Array = Uint8Array.from(sha);
78+
return sodium.crypto_generichash(sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES, pwsalted);
79+
}
80+
81+
/**
82+
* A SHA256 helper function.
83+
*
84+
* @param message The message to hash
85+
*
86+
* @returns A {@link https://github.com/feross/buffer|Buffer} containing the SHA256 hash of the message
87+
*/
88+
sha256(message:string | Buffer | Uint8Array):Buffer {
89+
let buff:Buffer;
90+
if(typeof message === "string"){
91+
buff = Buffer.from(message, "utf8");
92+
} else {
93+
buff = Buffer.from(message);
94+
}
95+
return Buffer.from(createHash('sha256').update(buff).digest()); // ensures correct Buffer class is used
96+
}
97+
98+
/**
99+
* Generates a randomized {@link https://github.com/feross/buffer|Buffer} to be used as a salt
100+
*/
101+
async makeSalt():Promise<Buffer> {
102+
let sodium:typeof libsodiumWrapper = await this.sodium();
103+
return Buffer.from(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES));
104+
}
105+
106+
/**
107+
* Produces a password-safe hash.
108+
*
109+
* @param password A string for the password
110+
* @param salt An optional {@link https://github.com/feross/buffer|Buffer} containing a salt used in the password hash
111+
*
112+
* @returns An object containing the "salt" and the "hash" produced by this function, both as {@link https://github.com/feross/buffer|Buffer}.
113+
*/
114+
async pwhash(password:string, salt:Buffer):Promise< {salt:Buffer; hash:Buffer} > {
115+
let sodium:typeof libsodiumWrapper = await this.sodium();
116+
117+
let slt:Uint8Array;
118+
if(typeof salt === "undefined"){
119+
slt = await this.makeSalt();
120+
} else {
121+
slt = Uint8Array.from(salt);
122+
}
123+
let hash:Uint8Array = sodium.crypto_pwhash(32, password, slt, this.opslimit, this.memlimit, sodium.crypto_pwhash_ALG_DEFAULT);
124+
return {salt:Buffer.from(slt), hash:Buffer.from(hash)};
125+
}
126+
127+
/**
128+
* Encrypts plaintext with the provided password using XChaCha20Poly1305.
129+
*
130+
* @param password A string for the password
131+
* @param plaintext The plaintext to encrypt
132+
* @param salt An optional {@link https://github.com/feross/buffer|Buffer} for the salt to use in the encryption process
133+
*
134+
* @returns An object containing the "salt", "nonce", and "ciphertext", all as {@link https://github.com/feross/buffer|Buffer}.
135+
*/
136+
async encrypt(password:string, plaintext:Buffer | string, salt:Buffer = undefined):Promise< {salt:Buffer; nonce:Buffer; ciphertext:Buffer} > {
137+
let sodium:typeof libsodiumWrapper = await this.sodium();
138+
let slt:Uint8Array;
139+
if(typeof salt === "undefined"){
140+
slt = await this.makeSalt();
141+
} else {
142+
slt = Uint8Array.from(salt);
143+
}
144+
145+
let pt:Uint8Array;
146+
if(plaintext instanceof Buffer){
147+
pt = Uint8Array.from(plaintext);
148+
} else {
149+
pt = Uint8Array.from(Buffer.from(plaintext, "utf8"));
150+
}
151+
let nonce:Uint8Array = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
152+
let pkey:Uint8Array = await this._pwcleaner(password, slt);
153+
let ciphertext:Uint8Array = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(pt, "AVALANCHE", null, nonce, pkey);
154+
155+
return {
156+
salt:Buffer.from(slt),
157+
nonce:Buffer.from(nonce),
158+
ciphertext:Buffer.from(ciphertext)
159+
}
160+
161+
}
162+
163+
/**
164+
* Decrypts ciphertext with the provided password, nonce, ans salt.
165+
*
166+
* @param password A string for the password
167+
* @param ciphertext A {@link https://github.com/feross/buffer|Buffer} for the ciphertext
168+
* @param salt A {@link https://github.com/feross/buffer|Buffer} for the salt
169+
* @param nonce A {@link https://github.com/feross/buffer|Buffer} for the nonce
170+
*/
171+
async decrypt(password:string, ciphertext:Buffer, salt:Buffer, nonce:Buffer):Promise<Buffer> {
172+
let sodium:typeof libsodiumWrapper = await this.sodium();
173+
let pkey:Uint8Array = await this._pwcleaner(password, Uint8Array.from(salt));
174+
let pt:Uint8Array = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(null, Uint8Array.from(ciphertext), "AVALANCHE", Uint8Array.from(nonce), pkey);
175+
return Buffer.from(pt);
176+
}
177+
178+
constructor() {}
179+
}

tests/utils/crypto.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Buffer } from "buffer/";
2+
import {CryptoHelpers} from "src/utils/crypto";
3+
4+
describe("CryptoHelpers", () => {
5+
test("instantiation and vars", () => {
6+
let ch:CryptoHelpers = new CryptoHelpers();
7+
8+
let ml = ch.getMemoryLimit();
9+
ch.setMemoryLimit(400);
10+
let mlupdated = ch.getMemoryLimit();
11+
12+
expect(ml).not.toBe(mlupdated);
13+
14+
let ol = ch.getOpsLimit();
15+
ch.setOpsLimit(1);
16+
let olupdated = ch.getOpsLimit();
17+
18+
expect(ol).not.toBe(olupdated);
19+
});
20+
test("encrypt and decrypt", async () => {
21+
let pw:string = "password12345";
22+
let ch:CryptoHelpers = new CryptoHelpers();
23+
24+
let msg:string = "I am the very model of a modern major general";
25+
let resp:{salt: Buffer; nonce: Buffer; ciphertext: Buffer} = await ch.encrypt(pw, msg);
26+
let pt1:Buffer = await ch.decrypt(pw, resp.ciphertext, resp.salt, resp.nonce);
27+
expect(pt1.toString("utf8")).toBe(msg);
28+
29+
let resp2:{salt: Buffer; nonce: Buffer; ciphertext: Buffer} = await ch.encrypt(pw, msg, resp.salt);
30+
let pt2:Buffer = await ch.decrypt(pw, resp2.ciphertext, resp2.salt, resp2.nonce);
31+
expect(pt2.toString("utf8")).toBe(msg);
32+
});
33+
34+
test("pwhash", async () => {
35+
let pw:string = "password12345";
36+
let ch:CryptoHelpers = new CryptoHelpers();
37+
let salt:Buffer = await ch.makeSalt();
38+
39+
let ph1:{salt: Buffer; hash: Buffer} = await ch.pwhash(pw, salt);
40+
41+
let sha:Buffer = Buffer.from(await ch._pwcleaner(pw, salt));
42+
expect(sha.toString("hex")).not.toBe(ph1.hash.toString("hex"))
43+
});
44+
});

0 commit comments

Comments
 (0)