Skip to content

Commit 9fb6875

Browse files
committed
Adds code
1 parent eac8a2b commit 9fb6875

File tree

1 file changed

+201
-0
lines changed

1 file changed

+201
-0
lines changed

src/index.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { pbkdf2Sync, randomBytes, timingSafeEqual } from "crypto";
2+
3+
/** Defines type of HMAC digest algorythm. */
4+
type HmacDigest =
5+
| "RSA-MD5"
6+
| "RSA-RIPEMD160"
7+
| "RSA-SHA1"
8+
| "RSA-SHA1-2"
9+
| "RSA-SHA224"
10+
| "RSA-SHA256"
11+
| "RSA-SHA3-224"
12+
| "RSA-SHA3-256"
13+
| "RSA-SHA3-384"
14+
| "RSA-SHA3-512"
15+
| "RSA-SHA384"
16+
| "RSA-SHA512"
17+
| "RSA-SHA512/224"
18+
| "RSA-SHA512/256"
19+
| "RSA-SM3"
20+
| "blake2b512"
21+
| "blake2s256"
22+
| "id-rsassa-pkcs1-v1_5-with-sha3-224"
23+
| "id-rsassa-pkcs1-v1_5-with-sha3-256"
24+
| "id-rsassa-pkcs1-v1_5-with-sha3-384"
25+
| "id-rsassa-pkcs1-v1_5-with-sha3-512"
26+
| "md5"
27+
| "md5-sha1"
28+
| "md5WithRSAEncryption"
29+
| "ripemd"
30+
| "ripemd160"
31+
| "ripemd160WithRSA"
32+
| "rmd160"
33+
| "sha1"
34+
| "sha1WithRSAEncryption"
35+
| "sha224"
36+
| "sha224WithRSAEncryption"
37+
| "sha256"
38+
| "sha256WithRSAEncryption"
39+
| "sha3-224"
40+
| "sha3-256"
41+
| "sha3-384"
42+
| "sha3-512"
43+
| "sha384"
44+
| "sha384WithRSAEncryption"
45+
| "sha512"
46+
| "sha512-224"
47+
| "sha512-224WithRSAEncryption"
48+
| "sha512-256"
49+
| "sha512-256WithRSAEncryption"
50+
| "sha512WithRSAEncryption"
51+
| "shake128"
52+
| "shake256"
53+
| "sm3"
54+
| "sm3WithRSAEncryption"
55+
| "ssl3-md5"
56+
| "ssl3-sha1";
57+
58+
/** Represents hashing error. */
59+
export class StringHashError extends Error {
60+
/** Gets inner error. */
61+
public inner?: Error;
62+
63+
/**
64+
* Initializes a new instance of `StringHashError` class.
65+
* @param message Error message.
66+
* @param inner Inner error.
67+
*/
68+
constructor(message?: string, inner?: Error) {
69+
super(message);
70+
71+
this.inner = inner;
72+
}
73+
}
74+
75+
/** Defines properties of hashing options. */
76+
export type StringHashOptions = {
77+
/** Gets hash signature (prefix). */
78+
signature?: string;
79+
80+
/** Gets the HMAC digest algorythm. */
81+
algorythm?: HmacDigest;
82+
83+
/** Gets salt length. */
84+
saltLength?: number;
85+
86+
/** Gets hash length. */
87+
hashLength?: number;
88+
89+
/** Gets the number of iteration. */
90+
iterations?: number;
91+
};
92+
93+
/** Defines properties and methods of `StringHasher`. */
94+
interface IStringHasher {
95+
/** Gets hash signature (prefix). */
96+
signature: string;
97+
98+
/** Gets algorythm */
99+
algorythm: HmacDigest;
100+
101+
/** Gets salt length. */
102+
saltLength: number;
103+
104+
/** Gets hash length. */
105+
hashLength: number;
106+
107+
/** Gets buffer length (`saltLength` - `hashLength` - `signatureLength`). */
108+
buffLength: number;
109+
110+
/** Gets iterations count. */
111+
iterations: number;
112+
113+
/**
114+
* Generates hash of the `value` string.
115+
* @param value String to generate hash of.
116+
* @returns Hash, generated of given string with following structure: `{signature}{hashBuff}{saltBuff}`.
117+
*/
118+
generate(value: string): string;
119+
120+
/**
121+
* Compares a plain-text `value` string to a hashed one.
122+
* @param value Value string.
123+
* @param hash Hash string.
124+
* @returns `true` is hashes converge; `false` otherwise.
125+
*/
126+
validate(value: string, hash: string): boolean;
127+
}
128+
129+
/** Represents string hasher. */
130+
export class StringHasher implements IStringHasher {
131+
public readonly signature: string = "sha512";
132+
public readonly algorythm: HmacDigest = "sha512";
133+
public readonly saltLength: number = 32;
134+
public readonly hashLength: number = 128;
135+
public readonly buffLength: number = 90;
136+
public readonly iterations: number = 100000;
137+
138+
/**
139+
* Initializes a new instance of `StringHasher` class.
140+
* @param options Hashing options.
141+
*/
142+
constructor(options?: StringHashOptions) {
143+
if (!options) return;
144+
145+
// Change field values if options present.
146+
if (options.signature) this.signature = options.signature.trim();
147+
if (options.algorythm) this.algorythm = options.algorythm;
148+
if (options.saltLength) this.saltLength = options.saltLength;
149+
if (options.hashLength) this.hashLength = options.hashLength;
150+
if (options.iterations) this.iterations = options.iterations;
151+
152+
// Validating values.
153+
if (this.saltLength <= 0) throw new StringHashError("Salt length cannot be less than or equals zero.");
154+
if (this.hashLength <= 0) throw new StringHashError("Hash length cannot be less than or equals zero.");
155+
if (this.iterations < 1000) throw new StringHashError("Iterations count cannot be less than 1000.");
156+
if (this.hashLength / 2 < this.saltLength)
157+
throw new StringHashError("Salt length cannot be less than half of hash length.");
158+
if ((this.hashLength / 3) * 2 < this.saltLength + this.signature.length)
159+
throw new StringHashError("Salt length combined with signature length cannot be more than 2/3 of hash length.");
160+
161+
// Calculating raw hash buffer length.
162+
this.buffLength = this.hashLength - this.saltLength - this.signature.length;
163+
}
164+
165+
private generateSalt(): Buffer {
166+
try {
167+
return randomBytes(this.saltLength / 2);
168+
} catch (e) {
169+
throw new StringHashError("Unable to generate salt.", e as Error);
170+
}
171+
}
172+
173+
private generateHash(value: string, salt: Buffer): Buffer {
174+
try {
175+
return pbkdf2Sync(value, salt, this.iterations, this.buffLength / 2, this.algorythm);
176+
} catch (e) {
177+
throw new StringHashError("Unable to generate hash.", e as Error);
178+
}
179+
}
180+
181+
public generate(value: string): string {
182+
const salt = this.generateSalt();
183+
const hash = this.generateHash(value, salt);
184+
185+
return `${this.signature}${Buffer.concat([hash, salt]).toString("hex")}`;
186+
}
187+
188+
public validate(value: string, hash: string): boolean {
189+
if (value.length == 0 || hash.length != this.hashLength) return false;
190+
if (this.signature != hash.substring(0, this.signature.length)) return false;
191+
192+
const hashStr = hash.substring(this.signature.length, this.hashLength - this.saltLength);
193+
const saltStr = hash.substring(this.signature.length + this.buffLength);
194+
195+
const saltBuff = Buffer.from(saltStr, "hex");
196+
const hashBuff = Buffer.from(hashStr, "hex");
197+
const testBuff = this.generateHash(value, saltBuff);
198+
199+
return timingSafeEqual(hashBuff, testBuff);
200+
}
201+
}

0 commit comments

Comments
 (0)