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+ }
0 commit comments