@nivinjoseph/n-sec 6.0.3 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarn/releases/yarn-4.14.1.cjs +940 -0
- package/.yarnrc.yml +6 -1
- package/dist/api-security/claim.js +2 -0
- package/dist/api-security/claim.js.map +1 -1
- package/dist/api-security/claims-identity.js +1 -0
- package/dist/api-security/claims-identity.js.map +1 -1
- package/dist/api-security/expired-token-exception.js +1 -0
- package/dist/api-security/expired-token-exception.js.map +1 -1
- package/dist/api-security/invalid-token-exception.js +2 -0
- package/dist/api-security/invalid-token-exception.js.map +1 -1
- package/dist/api-security/json-web-token.d.ts +1 -1
- package/dist/api-security/json-web-token.d.ts.map +1 -1
- package/dist/api-security/json-web-token.js +35 -10
- package/dist/api-security/json-web-token.js.map +1 -1
- package/dist/api-security/security-token.js +2 -0
- package/dist/api-security/security-token.js.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/crypto/hash.d.ts +40 -0
- package/dist/crypto/hash.d.ts.map +1 -1
- package/dist/crypto/hash.js +60 -1
- package/dist/crypto/hash.js.map +1 -1
- package/dist/crypto/symmetric-encryption.d.ts +51 -3
- package/dist/crypto/symmetric-encryption.d.ts.map +1 -1
- package/dist/crypto/symmetric-encryption.js +90 -44
- package/dist/crypto/symmetric-encryption.js.map +1 -1
- package/dist/tsconfig.json +1 -0
- package/eslint.config.js +5 -5
- package/package.json +15 -15
- package/src/api-security/json-web-token.ts +37 -12
- package/src/bin.ts +1 -1
- package/src/crypto/hash.ts +66 -1
- package/src/crypto/symmetric-encryption.ts +98 -55
- package/test/hash.test.ts +89 -0
- package/test/hmac.test.ts +12 -12
- package/test/json-web-token.test.ts +76 -10
- package/test/symmetric-encryption.test.ts +51 -12
- package/tsconfig.json +5 -2
- package/.yarn/releases/yarn-4.0.2.cjs +0 -893
package/.yarnrc.yml
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claim.js","sourceRoot":"","sources":["../../src/api-security/claim.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGjD,SAAS;AACT,MAAM,OAAO,KAAK;
|
|
1
|
+
{"version":3,"file":"claim.js","sourceRoot":"","sources":["../../src/api-security/claim.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGjD,SAAS;AACT,MAAM,OAAO,KAAK;IAEG,KAAK,CAAS;IACd,MAAM,CAAU;IAGjC,IAAW,IAAI,KAAa,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,IAAW,KAAK,KAAc,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAGnD,YAAmB,IAAY,EAAE,KAAc;QAE3C,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,cAAc,EAAE,CAAC,cAAc,EAAE,CAAC;QAEtD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACxB,CAAC;IAGM,MAAM,CAAC,KAAY;QAEtB,uEAAuE;QACvE,IAAI,KAAK,IAAI,IAAI;YACb,OAAO,KAAK,CAAC;QAEjB,IAAI,KAAK,KAAK,IAAI;YACd,OAAO,IAAI,CAAC;QAEhB,OAAO,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC;IAClE,CAAC;CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claims-identity.js","sourceRoot":"","sources":["../../src/api-security/claims-identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAIjD,SAAS;AACT,MAAM,OAAO,cAAc;
|
|
1
|
+
{"version":3,"file":"claims-identity.js","sourceRoot":"","sources":["../../src/api-security/claims-identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAIjD,SAAS;AACT,MAAM,OAAO,cAAc;IAEN,OAAO,CAAe;IAGvC,IAAW,MAAM,KAA2B,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAGlE,YAAmB,MAA4B;QAE3C,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,cAAc,EAAE,CAAC,aAAa,EAAE,CAAC;QAEzD,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;IAC/B,CAAC;IAGM,QAAQ,CAAC,KAAY;QAExB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACnD,CAAC;CACJ"}
|
|
@@ -2,6 +2,7 @@ import { Exception } from "@nivinjoseph/n-exception";
|
|
|
2
2
|
import { given } from "@nivinjoseph/n-defensive";
|
|
3
3
|
// public
|
|
4
4
|
export class ExpiredTokenException extends Exception {
|
|
5
|
+
_token;
|
|
5
6
|
get token() { return this._token; }
|
|
6
7
|
constructor(token) {
|
|
7
8
|
given(token, "token").ensureHasValue().ensureIsString();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expired-token-exception.js","sourceRoot":"","sources":["../../src/api-security/expired-token-exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGjD,SAAS;AACT,MAAM,OAAO,qBAAsB,SAAQ,SAAS;
|
|
1
|
+
{"version":3,"file":"expired-token-exception.js","sourceRoot":"","sources":["../../src/api-security/expired-token-exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGjD,SAAS;AACT,MAAM,OAAO,qBAAsB,SAAQ,SAAS;IAE/B,MAAM,CAAS;IAGhC,IAAW,KAAK,KAAa,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAGlD,YAAmB,KAAa;QAE5B,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC,cAAc,EAAE,CAAC;QACxD,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,CAAC,UAAU,KAAK,eAAe,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACxB,CAAC;CACJ"}
|
|
@@ -2,6 +2,8 @@ import { Exception } from "@nivinjoseph/n-exception";
|
|
|
2
2
|
import { given } from "@nivinjoseph/n-defensive";
|
|
3
3
|
// public
|
|
4
4
|
export class InvalidTokenException extends Exception {
|
|
5
|
+
_token;
|
|
6
|
+
_reason;
|
|
5
7
|
get token() { return this._token; }
|
|
6
8
|
get reason() { return this._reason; }
|
|
7
9
|
constructor(token, reason) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invalid-token-exception.js","sourceRoot":"","sources":["../../src/api-security/invalid-token-exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGjD,SAAS;AACT,MAAM,OAAO,qBAAsB,SAAQ,SAAS;
|
|
1
|
+
{"version":3,"file":"invalid-token-exception.js","sourceRoot":"","sources":["../../src/api-security/invalid-token-exception.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGjD,SAAS;AACT,MAAM,OAAO,qBAAsB,SAAQ,SAAS;IAE/B,MAAM,CAAS;IACf,OAAO,CAAS;IAGjC,IAAW,KAAK,KAAa,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAClD,IAAW,MAAM,KAAa,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAGpD,YAAmB,KAAa,EAAE,MAAc;QAE5C,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC,cAAc,EAAE,CAAC;QACxD,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,cAAc,EAAE,CAAC,cAAc,EAAE,CAAC;QAE1D,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,CAAC,UAAU,KAAK,wBAAwB,MAAM,GAAG,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IAC1B,CAAC;CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-web-token.d.ts","sourceRoot":"","sources":["../../src/api-security/json-web-token.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-web-token.d.ts","sourceRoot":"","sources":["../../src/api-security/json-web-token.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAOnC,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAGvC,IAAW,MAAM,IAAI,MAAM,CAAyB;IACpD,IAAW,OAAO,IAAI,OAAO,CAA0B;IACvD,IAAW,GAAG,IAAI,MAAM,CAAsB;IAC9C,IAAW,gBAAgB,IAAI,OAAO,CAA4B;IAClE,IAAW,MAAM,IAAI,MAAM,CAAyB;IACpD,IAAW,SAAS,IAAI,OAAO,CAAuC;IACtE,IAAW,MAAM,IAAI,aAAa,CAAC,KAAK,CAAC,CAAyB;IAGlE,OAAO;WAmBO,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAClF,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,YAAY;WAKzB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY;IAkGnG,OAAO,CAAC,MAAM,CAAC,SAAS;IAMjB,aAAa,IAAI,MAAM;IA0B9B,OAAO,CAAC,MAAM;CAMjB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { given } from "@nivinjoseph/n-defensive";
|
|
2
2
|
import { InvalidOperationException } from "@nivinjoseph/n-exception";
|
|
3
|
+
import { timingSafeEqual } from "node:crypto";
|
|
3
4
|
import { Hmac } from "./../crypto/hmac.js";
|
|
4
5
|
import { AlgType } from "./alg-type.js";
|
|
5
6
|
import { Claim } from "./claim.js";
|
|
@@ -8,10 +9,16 @@ import { InvalidTokenException } from "./invalid-token-exception.js";
|
|
|
8
9
|
import { ExpiredTokenException } from "./expired-token-exception.js";
|
|
9
10
|
// public
|
|
10
11
|
export class JsonWebToken {
|
|
12
|
+
_issuer;
|
|
13
|
+
_algType;
|
|
14
|
+
_key;
|
|
15
|
+
_isFullKey;
|
|
16
|
+
_expiry;
|
|
17
|
+
_claims;
|
|
11
18
|
get issuer() { return this._issuer; }
|
|
12
19
|
get algType() { return this._algType; }
|
|
13
20
|
get key() { return this._key; }
|
|
14
|
-
get canGenerateToken() { return this.
|
|
21
|
+
get canGenerateToken() { return this._isFullKey; }
|
|
15
22
|
get expiry() { return this._expiry; }
|
|
16
23
|
get isExpired() { return this._expiry <= Date.now(); }
|
|
17
24
|
get claims() { return this._claims; }
|
|
@@ -26,7 +33,7 @@ export class JsonWebToken {
|
|
|
26
33
|
this._issuer = issuer.trim();
|
|
27
34
|
this._algType = algType;
|
|
28
35
|
this._key = key.trim();
|
|
29
|
-
this.
|
|
36
|
+
this._isFullKey = isFullKey;
|
|
30
37
|
this._expiry = expiry;
|
|
31
38
|
this._claims = [...claims];
|
|
32
39
|
}
|
|
@@ -47,8 +54,21 @@ export class JsonWebToken {
|
|
|
47
54
|
const headerString = tokenSplitted[0];
|
|
48
55
|
const bodyString = tokenSplitted[1];
|
|
49
56
|
const signature = tokenSplitted[2];
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
let parsedHeader;
|
|
58
|
+
let parsedBody;
|
|
59
|
+
try {
|
|
60
|
+
parsedHeader = JsonWebToken._toObject(headerString);
|
|
61
|
+
parsedBody = JsonWebToken._toObject(bodyString);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new InvalidTokenException(token, "header or body could not be parsed");
|
|
65
|
+
}
|
|
66
|
+
if (parsedHeader == null || typeof parsedHeader !== "object" || Array.isArray(parsedHeader))
|
|
67
|
+
throw new InvalidTokenException(token, "header is not an object");
|
|
68
|
+
if (parsedBody == null || typeof parsedBody !== "object" || Array.isArray(parsedBody))
|
|
69
|
+
throw new InvalidTokenException(token, "body is not an object");
|
|
70
|
+
const header = parsedHeader;
|
|
71
|
+
const body = parsedBody;
|
|
52
72
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
53
73
|
if (header.iss === undefined || header.iss === null)
|
|
54
74
|
throw new InvalidTokenException(token, "iss was not present");
|
|
@@ -80,20 +100,25 @@ export class JsonWebToken {
|
|
|
80
100
|
// throw new InvalidTokenException(token, "signature could not be verified");
|
|
81
101
|
// }
|
|
82
102
|
const computedSignature = Hmac.create(key, headerString + "." + bodyString);
|
|
83
|
-
|
|
103
|
+
const expected = Buffer.from(computedSignature, "utf8");
|
|
104
|
+
const provided = Buffer.from(signature, "utf8");
|
|
105
|
+
if (expected.length !== provided.length || !timingSafeEqual(expected, provided))
|
|
84
106
|
throw new InvalidTokenException(token, "signature could not be verified");
|
|
107
|
+
const invalidBodyKeys = new Set(["__proto__", "constructor", "prototype"]);
|
|
85
108
|
const claims = new Array();
|
|
86
|
-
for (const
|
|
87
|
-
|
|
109
|
+
for (const [type, value] of Object.entries(body)) {
|
|
110
|
+
if (invalidBodyKeys.has(type))
|
|
111
|
+
throw new InvalidTokenException(token, `body contains invalid key '${type}'`);
|
|
112
|
+
claims.push(new Claim(type, value));
|
|
113
|
+
}
|
|
88
114
|
return new JsonWebToken(issuer, algType, key, false, header.exp, claims);
|
|
89
115
|
}
|
|
90
116
|
static _toObject(hex) {
|
|
91
117
|
const json = Buffer.from(hex.toLowerCase(), "hex").toString("utf8");
|
|
92
|
-
|
|
93
|
-
return obj;
|
|
118
|
+
return JSON.parse(json);
|
|
94
119
|
}
|
|
95
120
|
generateToken() {
|
|
96
|
-
if (!this.
|
|
121
|
+
if (!this._isFullKey)
|
|
97
122
|
throw new InvalidOperationException("generating token using an instance created from token");
|
|
98
123
|
const header = {
|
|
99
124
|
iss: this._issuer,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-web-token.js","sourceRoot":"","sources":["../../src/api-security/json-web-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,uEAAuE;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAGrE,SAAS;AACT,MAAM,OAAO,YAAY;
|
|
1
|
+
{"version":3,"file":"json-web-token.js","sourceRoot":"","sources":["../../src/api-security/json-web-token.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-token.js","sourceRoot":"","sources":["../../src/api-security/security-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEjD,MAAM,OAAO,aAAa;
|
|
1
|
+
{"version":3,"file":"security-token.js","sourceRoot":"","sources":["../../src/api-security/security-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEjD,MAAM,OAAO,aAAa;IAEL,OAAO,CAAS;IAChB,MAAM,CAAS;IAGhC,IAAW,MAAM,KAAa,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACpD,IAAW,KAAK,KAAa,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAGlD,YAAmB,MAAc,EAAE,KAAa;QAE5C,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,cAAc,EAAE,CAAC,cAAc,EAAE;aACpD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,sBAAsB,CAAC,CAAC;QAC3D,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QAEtB,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC,cAAc,EAAE;aAClD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,sBAAsB,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACxB,CAAC;IAGM,QAAQ;QAEX,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5C,CAAC;CACJ"}
|
package/dist/bin.js
CHANGED
|
@@ -13,7 +13,7 @@ async function executeCommand(command) {
|
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
14
14
|
case SupportedCommands.generateSymmetricKey:
|
|
15
15
|
{
|
|
16
|
-
const key =
|
|
16
|
+
const key = SymmetricEncryption.generateKey();
|
|
17
17
|
console.log("SYMMETRIC KEY => ", key);
|
|
18
18
|
break;
|
|
19
19
|
}
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAEA,oDAAoD;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,IAAK,iBAGJ;AAHD,WAAK,iBAAiB;IAElB,oEAA+C,CAAA;AACnD,CAAC,EAHI,iBAAiB,KAAjB,iBAAiB,QAGrB;AAED,MAAM,uBAAuB,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9G,KAAK,UAAU,cAAc,CAAC,OAA0B;IAEpD,+DAA+D;IAE/D,QAAQ,OAAO,EACf,CAAC;QACG,uEAAuE;QACvE,KAAK,iBAAiB,CAAC,oBAAoB;YACvC,CAAC;gBACG,MAAM,GAAG,GAAG,
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAEA,oDAAoD;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,IAAK,iBAGJ;AAHD,WAAK,iBAAiB;IAElB,oEAA+C,CAAA;AACnD,CAAC,EAHI,iBAAiB,KAAjB,iBAAiB,QAGrB;AAED,MAAM,uBAAuB,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9G,KAAK,UAAU,cAAc,CAAC,OAA0B;IAEpD,+DAA+D;IAE/D,QAAQ,OAAO,EACf,CAAC;QACG,uEAAuE;QACvE,KAAK,iBAAiB,CAAC,oBAAoB;YACvC,CAAC;gBACG,MAAM,GAAG,GAAG,mBAAmB,CAAC,WAAW,EAAE,CAAC;gBAC9C,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;gBACtC,MAAM;YACV,CAAC;QACL;YACI,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,6BAA6B,uBAAuB,GAAG,CAAC,CAAC;IAC1G,CAAC;AACL,CAAC;AAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EACrB,CAAC;IACG,OAAO,CAAC,KAAK,CAAC,yDAAyD,uBAAuB,GAAG,CAAC,CAAC;IACnG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB;AACxC,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAsB,CAAC;AAE7C,cAAc,CAAC,OAAO,CAAC;KAClB,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;KAC3B,KAAK,CAAC,CAAC,CAAC,EAAE;IAEP,OAAO,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
|
package/dist/crypto/hash.d.ts
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
export declare class Hash {
|
|
2
2
|
private constructor();
|
|
3
3
|
static create(value: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Unsafe for password storage. Uses a single round of
|
|
6
|
+
* SHA-512, which a GPU can compute at billions of hashes/second —
|
|
7
|
+
* leaked hashes can be brute-forced rapidly against any wordlist.
|
|
8
|
+
* Additionally trims leading/trailing whitespace from both `value`
|
|
9
|
+
* and `salt`, so inputs differing only in whitespace collide.
|
|
10
|
+
*
|
|
11
|
+
* For password hashing, use {@link createForPassword} +
|
|
12
|
+
* {@link verifyPassword} instead.
|
|
13
|
+
*/
|
|
4
14
|
static createUsingSalt(value: string, salt: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Derives a 64-byte hash from `password` and `salt` using scrypt with
|
|
17
|
+
* parameters N=2^15, r=8, p=1 (RFC 7914 recommended defaults). Suitable
|
|
18
|
+
* for password storage: slow and memory-hard, so leaked hashes resist
|
|
19
|
+
* GPU brute-force attacks far better than a plain SHA-512.
|
|
20
|
+
*
|
|
21
|
+
* Inputs are NOT trimmed — the exact bytes of `password` and `salt` are
|
|
22
|
+
* hashed. Two passwords differing only in whitespace produce different
|
|
23
|
+
* outputs.
|
|
24
|
+
*
|
|
25
|
+
* @param password - The password to hash. Hashed as UTF-8.
|
|
26
|
+
* @param salt - A per-user random value (recommended: ≥16 random bytes,
|
|
27
|
+
* e.g. `randomBytes(16).toString("hex")`). Must be stored alongside
|
|
28
|
+
* the hash so the same value can be passed on verification.
|
|
29
|
+
* @returns A 128-character uppercase hex string (64 bytes).
|
|
30
|
+
*/
|
|
31
|
+
static createForPassword(password: string, salt: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Verifies a password against a hash previously produced by
|
|
34
|
+
* {@link createForPassword}. The comparison is constant-time, so
|
|
35
|
+
* timing cannot be used to learn how many leading bytes matched.
|
|
36
|
+
*
|
|
37
|
+
* @param password - The candidate password to verify. Hashed as UTF-8.
|
|
38
|
+
* @param salt - The same salt that was passed to
|
|
39
|
+
* {@link createForPassword} when the stored hash was created.
|
|
40
|
+
* @param expectedHash - The previously-stored hash (hex string, any case).
|
|
41
|
+
* @returns `true` if the candidate matches; `false` if it doesn't,
|
|
42
|
+
* if `expectedHash` is not valid hex, or if lengths differ.
|
|
43
|
+
*/
|
|
44
|
+
static verifyPassword(password: string, salt: string, expectedHash: string): boolean;
|
|
5
45
|
}
|
|
6
46
|
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/crypto/hash.ts"],"names":[],"mappings":"AAKA,qBAAa,IAAI;IAEb,OAAO;WAGO,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/crypto/hash.ts"],"names":[],"mappings":"AAKA,qBAAa,IAAI;IAEb,OAAO;WAGO,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAU3C;;;;;;;;;OASG;WACW,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IA0BlE;;;;;;;;;;;;;;;OAeG;WACW,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;IAcvE;;;;;;;;;;;OAWG;WACW,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;CAY9F"}
|
package/dist/crypto/hash.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { given } from "@nivinjoseph/n-defensive";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
2
|
+
import { createHash, scryptSync, timingSafeEqual } from "node:crypto";
|
|
3
3
|
// public
|
|
4
4
|
export class Hash {
|
|
5
5
|
constructor() { }
|
|
@@ -10,6 +10,16 @@ export class Hash {
|
|
|
10
10
|
hash.update(value, "utf8");
|
|
11
11
|
return hash.digest("hex").toUpperCase();
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Unsafe for password storage. Uses a single round of
|
|
15
|
+
* SHA-512, which a GPU can compute at billions of hashes/second —
|
|
16
|
+
* leaked hashes can be brute-forced rapidly against any wordlist.
|
|
17
|
+
* Additionally trims leading/trailing whitespace from both `value`
|
|
18
|
+
* and `salt`, so inputs differing only in whitespace collide.
|
|
19
|
+
*
|
|
20
|
+
* For password hashing, use {@link createForPassword} +
|
|
21
|
+
* {@link verifyPassword} instead.
|
|
22
|
+
*/
|
|
13
23
|
static createUsingSalt(value, salt) {
|
|
14
24
|
given(value, "value").ensureHasValue().ensureIsString();
|
|
15
25
|
given(salt, "salt").ensureHasValue().ensureIsString();
|
|
@@ -27,5 +37,54 @@ export class Hash {
|
|
|
27
37
|
const saltedValue = `${salt}${value}${valueReverse}${salt}${saltReverse}${salt}${valueReverse}`;
|
|
28
38
|
return Hash.create(saltedValue);
|
|
29
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Derives a 64-byte hash from `password` and `salt` using scrypt with
|
|
42
|
+
* parameters N=2^15, r=8, p=1 (RFC 7914 recommended defaults). Suitable
|
|
43
|
+
* for password storage: slow and memory-hard, so leaked hashes resist
|
|
44
|
+
* GPU brute-force attacks far better than a plain SHA-512.
|
|
45
|
+
*
|
|
46
|
+
* Inputs are NOT trimmed — the exact bytes of `password` and `salt` are
|
|
47
|
+
* hashed. Two passwords differing only in whitespace produce different
|
|
48
|
+
* outputs.
|
|
49
|
+
*
|
|
50
|
+
* @param password - The password to hash. Hashed as UTF-8.
|
|
51
|
+
* @param salt - A per-user random value (recommended: ≥16 random bytes,
|
|
52
|
+
* e.g. `randomBytes(16).toString("hex")`). Must be stored alongside
|
|
53
|
+
* the hash so the same value can be passed on verification.
|
|
54
|
+
* @returns A 128-character uppercase hex string (64 bytes).
|
|
55
|
+
*/
|
|
56
|
+
static createForPassword(password, salt) {
|
|
57
|
+
given(password, "password").ensureHasValue().ensureIsString();
|
|
58
|
+
given(salt, "salt").ensureHasValue().ensureIsString();
|
|
59
|
+
const derived = scryptSync(password, salt, 64, {
|
|
60
|
+
N: 1 << 15,
|
|
61
|
+
r: 8,
|
|
62
|
+
p: 1,
|
|
63
|
+
maxmem: 64 * 1024 * 1024
|
|
64
|
+
});
|
|
65
|
+
return derived.toString("hex").toUpperCase();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Verifies a password against a hash previously produced by
|
|
69
|
+
* {@link createForPassword}. The comparison is constant-time, so
|
|
70
|
+
* timing cannot be used to learn how many leading bytes matched.
|
|
71
|
+
*
|
|
72
|
+
* @param password - The candidate password to verify. Hashed as UTF-8.
|
|
73
|
+
* @param salt - The same salt that was passed to
|
|
74
|
+
* {@link createForPassword} when the stored hash was created.
|
|
75
|
+
* @param expectedHash - The previously-stored hash (hex string, any case).
|
|
76
|
+
* @returns `true` if the candidate matches; `false` if it doesn't,
|
|
77
|
+
* if `expectedHash` is not valid hex, or if lengths differ.
|
|
78
|
+
*/
|
|
79
|
+
static verifyPassword(password, salt, expectedHash) {
|
|
80
|
+
given(password, "password").ensureHasValue().ensureIsString();
|
|
81
|
+
given(salt, "salt").ensureHasValue().ensureIsString();
|
|
82
|
+
given(expectedHash, "expectedHash").ensureHasValue().ensureIsString();
|
|
83
|
+
const computed = Buffer.from(Hash.createForPassword(password, salt), "hex");
|
|
84
|
+
const expected = Buffer.from(expectedHash, "hex");
|
|
85
|
+
if (computed.length !== expected.length)
|
|
86
|
+
return false;
|
|
87
|
+
return timingSafeEqual(computed, expected);
|
|
88
|
+
}
|
|
30
89
|
}
|
|
31
90
|
//# sourceMappingURL=hash.js.map
|
package/dist/crypto/hash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/crypto/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/crypto/hash.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,55 @@
|
|
|
1
1
|
export declare class SymmetricEncryption {
|
|
2
2
|
private constructor();
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Generates a cryptographically random 256-bit key suitable for use with
|
|
5
|
+
* {@link encrypt} and {@link decrypt}.
|
|
6
|
+
*
|
|
7
|
+
* @returns A 64-character uppercase hex string (32 bytes) sourced from the
|
|
8
|
+
* platform CSPRNG via Node's `crypto.randomBytes`.
|
|
9
|
+
*/
|
|
10
|
+
static generateKey(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Encrypts a UTF-8 string using AES-256-GCM with a fresh random 96-bit IV.
|
|
13
|
+
*
|
|
14
|
+
* The result is an authenticated ciphertext: any bit-flip, truncation, or
|
|
15
|
+
* substitution will cause {@link decrypt} to throw a {@link CryptoException}.
|
|
16
|
+
*
|
|
17
|
+
* @param key - A 64-character hex string (32 bytes) as produced by
|
|
18
|
+
* {@link generateKey}. Case-insensitive.
|
|
19
|
+
* @param value - The plaintext to encrypt. Interpreted as UTF-8.
|
|
20
|
+
* @param aad - Optional Additional Authenticated Data. When supplied, its
|
|
21
|
+
* UTF-8 bytes are bound into the auth tag but not stored in the output;
|
|
22
|
+
* the exact same `aad` must be passed to {@link decrypt}, otherwise
|
|
23
|
+
* decryption will fail. Use this to bind a ciphertext to its context
|
|
24
|
+
* (e.g. `` `user:${userId}` ``) so a valid ciphertext from one record
|
|
25
|
+
* cannot be substituted into another.
|
|
26
|
+
* @returns An uppercase hex string in the form `IV.CIPHERTEXT.TAG`, where
|
|
27
|
+
* `IV` is 24 hex chars (12 bytes), `TAG` is 32 hex chars (16 bytes), and
|
|
28
|
+
* `CIPHERTEXT` is the encrypted payload.
|
|
29
|
+
* @throws {CryptoException} If `key` is not exactly 64 hex characters.
|
|
30
|
+
*/
|
|
31
|
+
static encrypt(key: string, value: string, aad?: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Decrypts a ciphertext produced by {@link encrypt} and returns the
|
|
34
|
+
* original UTF-8 plaintext. Integrity is verified via the GCM auth tag
|
|
35
|
+
* before the plaintext is returned — if verification fails, nothing is
|
|
36
|
+
* returned.
|
|
37
|
+
*
|
|
38
|
+
* @param key - The same 64-character hex key that was used to encrypt.
|
|
39
|
+
* Case-insensitive.
|
|
40
|
+
* @param value - The ciphertext string in `IV.CIPHERTEXT.TAG` form as
|
|
41
|
+
* produced by {@link encrypt}.
|
|
42
|
+
* @param aad - The same Additional Authenticated Data that was supplied
|
|
43
|
+
* to {@link encrypt}, or omitted if none was supplied. Any mismatch
|
|
44
|
+
* (wrong value, supplied here but not at encrypt, or vice versa) will
|
|
45
|
+
* cause the auth tag check to fail.
|
|
46
|
+
* @returns The original plaintext decoded as UTF-8.
|
|
47
|
+
* @throws {CryptoException} If `key` is not exactly 64 hex characters,
|
|
48
|
+
* if `value` is not in the expected `IV.CIPHERTEXT.TAG` format with
|
|
49
|
+
* correct component lengths, or if the auth tag verification fails
|
|
50
|
+
* (wrong key, tampered ciphertext, or mismatched `aad`).
|
|
51
|
+
*/
|
|
52
|
+
static decrypt(key: string, value: string, aad?: string): string;
|
|
53
|
+
private static _decodeKey;
|
|
6
54
|
}
|
|
7
55
|
//# sourceMappingURL=symmetric-encryption.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symmetric-encryption.d.ts","sourceRoot":"","sources":["../../src/crypto/symmetric-encryption.ts"],"names":[],"mappings":"AAMA,qBAAa,mBAAmB;IAE5B,OAAO;
|
|
1
|
+
{"version":3,"file":"symmetric-encryption.d.ts","sourceRoot":"","sources":["../../src/crypto/symmetric-encryption.ts"],"names":[],"mappings":"AAMA,qBAAa,mBAAmB;IAE5B,OAAO;IAGP;;;;;;OAMG;WACW,WAAW,IAAI,MAAM;IAKnC;;;;;;;;;;;;;;;;;;;OAmBG;WACW,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM;IAoBvE;;;;;;;;;;;;;;;;;;;OAmBG;WACW,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM;IAkCvE,OAAO,CAAC,MAAM,CAAC,UAAU;CAO5B"}
|
|
@@ -4,55 +4,101 @@ import { CryptoException } from "./crypto-exception.js";
|
|
|
4
4
|
// public
|
|
5
5
|
export class SymmetricEncryption {
|
|
6
6
|
constructor() { }
|
|
7
|
+
/**
|
|
8
|
+
* Generates a cryptographically random 256-bit key suitable for use with
|
|
9
|
+
* {@link encrypt} and {@link decrypt}.
|
|
10
|
+
*
|
|
11
|
+
* @returns A 64-character uppercase hex string (32 bytes) sourced from the
|
|
12
|
+
* platform CSPRNG via Node's `crypto.randomBytes`.
|
|
13
|
+
*/
|
|
7
14
|
static generateKey() {
|
|
8
|
-
return
|
|
9
|
-
randomBytes(32, (err, buf) => {
|
|
10
|
-
if (err) {
|
|
11
|
-
reject(err);
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
resolve(buf.toString("hex").toUpperCase());
|
|
15
|
-
});
|
|
16
|
-
});
|
|
15
|
+
return randomBytes(32).toString("hex").toUpperCase();
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Encrypts a UTF-8 string using AES-256-GCM with a fresh random 96-bit IV.
|
|
19
|
+
*
|
|
20
|
+
* The result is an authenticated ciphertext: any bit-flip, truncation, or
|
|
21
|
+
* substitution will cause {@link decrypt} to throw a {@link CryptoException}.
|
|
22
|
+
*
|
|
23
|
+
* @param key - A 64-character hex string (32 bytes) as produced by
|
|
24
|
+
* {@link generateKey}. Case-insensitive.
|
|
25
|
+
* @param value - The plaintext to encrypt. Interpreted as UTF-8.
|
|
26
|
+
* @param aad - Optional Additional Authenticated Data. When supplied, its
|
|
27
|
+
* UTF-8 bytes are bound into the auth tag but not stored in the output;
|
|
28
|
+
* the exact same `aad` must be passed to {@link decrypt}, otherwise
|
|
29
|
+
* decryption will fail. Use this to bind a ciphertext to its context
|
|
30
|
+
* (e.g. `` `user:${userId}` ``) so a valid ciphertext from one record
|
|
31
|
+
* cannot be substituted into another.
|
|
32
|
+
* @returns An uppercase hex string in the form `IV.CIPHERTEXT.TAG`, where
|
|
33
|
+
* `IV` is 24 hex chars (12 bytes), `TAG` is 32 hex chars (16 bytes), and
|
|
34
|
+
* `CIPHERTEXT` is the encrypted payload.
|
|
35
|
+
* @throws {CryptoException} If `key` is not exactly 64 hex characters.
|
|
36
|
+
*/
|
|
37
|
+
static encrypt(key, value, aad) {
|
|
38
|
+
given(key, "key").ensureHasValue().ensureIsString();
|
|
39
|
+
given(value, "value").ensureHasValue().ensureIsString();
|
|
40
|
+
if (aad != null)
|
|
41
|
+
given(aad, "aad").ensureIsString();
|
|
42
|
+
const keyBuf = SymmetricEncryption._decodeKey(key);
|
|
43
|
+
const iv = randomBytes(12);
|
|
44
|
+
const cipher = createCipheriv("aes-256-gcm", keyBuf, iv);
|
|
45
|
+
if (aad != null && aad.length > 0)
|
|
46
|
+
cipher.setAAD(Buffer.from(aad, "utf8"));
|
|
47
|
+
const ct = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
48
|
+
const tag = cipher.getAuthTag();
|
|
49
|
+
return [iv.toString("hex"), ct.toString("hex"), tag.toString("hex")]
|
|
50
|
+
.join(".").toUpperCase();
|
|
42
51
|
}
|
|
43
|
-
|
|
52
|
+
/**
|
|
53
|
+
* Decrypts a ciphertext produced by {@link encrypt} and returns the
|
|
54
|
+
* original UTF-8 plaintext. Integrity is verified via the GCM auth tag
|
|
55
|
+
* before the plaintext is returned — if verification fails, nothing is
|
|
56
|
+
* returned.
|
|
57
|
+
*
|
|
58
|
+
* @param key - The same 64-character hex key that was used to encrypt.
|
|
59
|
+
* Case-insensitive.
|
|
60
|
+
* @param value - The ciphertext string in `IV.CIPHERTEXT.TAG` form as
|
|
61
|
+
* produced by {@link encrypt}.
|
|
62
|
+
* @param aad - The same Additional Authenticated Data that was supplied
|
|
63
|
+
* to {@link encrypt}, or omitted if none was supplied. Any mismatch
|
|
64
|
+
* (wrong value, supplied here but not at encrypt, or vice versa) will
|
|
65
|
+
* cause the auth tag check to fail.
|
|
66
|
+
* @returns The original plaintext decoded as UTF-8.
|
|
67
|
+
* @throws {CryptoException} If `key` is not exactly 64 hex characters,
|
|
68
|
+
* if `value` is not in the expected `IV.CIPHERTEXT.TAG` format with
|
|
69
|
+
* correct component lengths, or if the auth tag verification fails
|
|
70
|
+
* (wrong key, tampered ciphertext, or mismatched `aad`).
|
|
71
|
+
*/
|
|
72
|
+
static decrypt(key, value, aad) {
|
|
44
73
|
given(key, "key").ensureHasValue().ensureIsString();
|
|
45
74
|
given(value, "value").ensureHasValue().ensureIsString();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
if (aad != null)
|
|
76
|
+
given(aad, "aad").ensureIsString();
|
|
77
|
+
const keyBuf = SymmetricEncryption._decodeKey(key);
|
|
78
|
+
const parts = value.split(".");
|
|
79
|
+
if (parts.length !== 3)
|
|
80
|
+
throw new CryptoException("Malformed ciphertext.");
|
|
81
|
+
const iv = Buffer.from(parts[0], "hex");
|
|
82
|
+
const ct = Buffer.from(parts[1], "hex");
|
|
83
|
+
const tag = Buffer.from(parts[2], "hex");
|
|
84
|
+
if (iv.length !== 12 || ct.length === 0 || tag.length !== 16)
|
|
85
|
+
throw new CryptoException("Malformed ciphertext.");
|
|
86
|
+
try {
|
|
87
|
+
const decipher = createDecipheriv("aes-256-gcm", keyBuf, iv);
|
|
88
|
+
if (aad != null && aad.length > 0)
|
|
89
|
+
decipher.setAAD(Buffer.from(aad, "utf8"));
|
|
90
|
+
decipher.setAuthTag(tag);
|
|
91
|
+
return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new CryptoException("Integrity check failed.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
static _decodeKey(key) {
|
|
98
|
+
const buf = Buffer.from(key, "hex");
|
|
99
|
+
if (buf.length !== 32 || buf.toString("hex").toUpperCase() !== key.toUpperCase())
|
|
100
|
+
throw new CryptoException("Key must be 64 hex characters (32 bytes).");
|
|
101
|
+
return buf;
|
|
56
102
|
}
|
|
57
103
|
}
|
|
58
104
|
//# sourceMappingURL=symmetric-encryption.js.map
|