@naturalcycles/nodejs-lib 12.104.0 → 13.0.0

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.
@@ -6,8 +6,8 @@ const colors_1 = require("../colors/colors");
6
6
  const runScript_1 = require("../script/runScript");
7
7
  const secrets_decrypt_util_1 = require("../secret/secrets-decrypt.util");
8
8
  (0, runScript_1.runScript)(() => {
9
- const { dir, file, encKey, del, jsonMode } = getDecryptCLIOptions();
10
- (0, secrets_decrypt_util_1.secretsDecrypt)(dir, file, encKey, del, jsonMode);
9
+ const { dir, file, encKeyBuffer, del, jsonMode } = getDecryptCLIOptions();
10
+ (0, secrets_decrypt_util_1.secretsDecrypt)(dir, file, encKeyBuffer, del, jsonMode);
11
11
  });
12
12
  function getDecryptCLIOptions() {
13
13
  require('dotenv').config();
@@ -24,7 +24,7 @@ function getDecryptCLIOptions() {
24
24
  },
25
25
  encKey: {
26
26
  type: 'string',
27
- desc: 'Encryption key',
27
+ desc: 'Encryption key as base64 encoded string',
28
28
  // demandOption: true,
29
29
  // default: process.env.SECRET_ENCRYPTION_KEY!,
30
30
  },
@@ -56,6 +56,7 @@ function getDecryptCLIOptions() {
56
56
  throw new Error(`encKey is required. Can be provided as --encKey or env.SECRET_ENCRYPTION_KEY (see readme.md)`);
57
57
  }
58
58
  }
59
+ const encKeyBuffer = Buffer.from(encKey, 'base64');
59
60
  // `as any` because @types/yargs can't handle string[] type properly
60
- return { dir: dir, file, encKey, del, jsonMode };
61
+ return { dir: dir, file, encKeyBuffer, del, jsonMode };
61
62
  }
@@ -6,8 +6,8 @@ const colors_1 = require("../colors/colors");
6
6
  const runScript_1 = require("../script/runScript");
7
7
  const secrets_encrypt_util_1 = require("../secret/secrets-encrypt.util");
8
8
  (0, runScript_1.runScript)(() => {
9
- const { pattern, file, encKey, del, jsonMode } = getEncryptCLIOptions();
10
- (0, secrets_encrypt_util_1.secretsEncrypt)(pattern, file, encKey, del, jsonMode);
9
+ const { pattern, file, encKeyBuffer, del, jsonMode } = getEncryptCLIOptions();
10
+ (0, secrets_encrypt_util_1.secretsEncrypt)(pattern, file, encKeyBuffer, del, jsonMode);
11
11
  });
12
12
  function getEncryptCLIOptions() {
13
13
  require('dotenv').config();
@@ -15,7 +15,7 @@ function getEncryptCLIOptions() {
15
15
  pattern: {
16
16
  type: 'string',
17
17
  array: true,
18
- desc: 'Globby pattern for secrets. Can be many.',
18
+ desc: 'Globby pattern for secrets. Can be multiple.',
19
19
  // demandOption: true,
20
20
  default: './secret/**',
21
21
  },
@@ -25,7 +25,7 @@ function getEncryptCLIOptions() {
25
25
  },
26
26
  encKey: {
27
27
  type: 'string',
28
- desc: 'Encryption key',
28
+ desc: 'Encryption key as base64 encoded string',
29
29
  // demandOption: true,
30
30
  // default: process.env.SECRET_ENCRYPTION_KEY!,
31
31
  },
@@ -58,6 +58,7 @@ function getEncryptCLIOptions() {
58
58
  throw new Error(`encKey is required. Can be provided as --encKey or env.SECRET_ENCRYPTION_KEY (see readme.md)`);
59
59
  }
60
60
  }
61
+ const encKeyBuffer = Buffer.from(encKey, 'base64');
61
62
  // `as any` because @types/yargs can't handle string[] type properly
62
- return { pattern: pattern, file, encKey, del, jsonMode };
63
+ return { pattern: pattern, file, encKeyBuffer, del, jsonMode };
63
64
  }
@@ -1,7 +1,8 @@
1
+ /// <reference types="node" />
1
2
  export interface DecryptCLIOptions {
2
3
  dir: string[];
3
4
  file?: string;
4
- encKey: string;
5
+ encKeyBuffer: Buffer;
5
6
  del?: boolean;
6
7
  jsonMode?: boolean;
7
8
  }
@@ -9,4 +10,4 @@ export interface DecryptCLIOptions {
9
10
  * Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
10
11
  * Using provided encKey.
11
12
  */
12
- export declare function secretsDecrypt(dir: string[], file: string | undefined, encKey: string, del?: boolean, jsonMode?: boolean): void;
13
+ export declare function secretsDecrypt(dir: string[], file: string | undefined, encKeyBuffer: Buffer, del?: boolean, jsonMode?: boolean): void;
@@ -8,13 +8,15 @@ const colors_1 = require("../colors/colors");
8
8
  const index_1 = require("../index");
9
9
  const crypto_util_1 = require("../security/crypto.util");
10
10
  // Debug it like this:
11
- // yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
12
11
  // yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets2.plain.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
12
+ // yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
13
+ // yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets.json -encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
14
+ // yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets.json.enc -encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
13
15
  /**
14
16
  * Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
15
17
  * Using provided encKey.
16
18
  */
17
- function secretsDecrypt(dir, file, encKey, del = false, jsonMode = false) {
19
+ function secretsDecrypt(dir, file, encKeyBuffer, del = false, jsonMode = false) {
18
20
  // If `file` is provided - only this one file is used
19
21
  const patterns = file ? [file] : dir.map(d => `${d}/**/*.enc`);
20
22
  const filenames = index_1.fastGlob.sync(patterns);
@@ -24,12 +26,12 @@ function secretsDecrypt(dir, file, encKey, del = false, jsonMode = false) {
24
26
  (0, js_lib_1._assert)(filename.endsWith('.json'), `${path.basename(filename)} MUST end with '.json'`);
25
27
  (0, js_lib_1._assert)(!filename.endsWith('.plain.json'), `${path.basename(filename)} MUST NOT end with '.plain.json'`);
26
28
  plainFilename = filename.replace('.json', '.plain.json');
27
- const json = (0, crypto_util_1.decryptObject)((0, index_1._readJsonSync)(filename), encKey);
29
+ const json = (0, crypto_util_1.decryptObject)((0, index_1._readJsonSync)(filename), encKeyBuffer);
28
30
  (0, index_1._writeJsonSync)(plainFilename, json, { spaces: 2 });
29
31
  }
30
32
  else {
31
33
  const enc = fs.readFileSync(filename);
32
- const plain = (0, crypto_util_1.decryptRandomIVBuffer)(enc, encKey);
34
+ const plain = (0, crypto_util_1.decryptRandomIVBuffer)(enc, encKeyBuffer);
33
35
  plainFilename = filename.slice(0, filename.length - '.enc'.length);
34
36
  fs.writeFileSync(plainFilename, plain);
35
37
  }
@@ -1,7 +1,8 @@
1
+ /// <reference types="node" />
1
2
  export interface EncryptCLIOptions {
2
3
  pattern: string[];
3
4
  file?: string;
4
- encKey: string;
5
+ encKeyBuffer: Buffer;
5
6
  del?: boolean;
6
7
  jsonMode?: boolean;
7
8
  }
@@ -9,4 +10,4 @@ export interface EncryptCLIOptions {
9
10
  * Encrypts all files in given directory (except *.enc), saves encrypted versions as filename.ext.enc.
10
11
  * Using provided encKey.
11
12
  */
12
- export declare function secretsEncrypt(pattern: string[], file: string | undefined, encKey: string, del?: boolean, jsonMode?: boolean): void;
13
+ export declare function secretsEncrypt(pattern: string[], file: string | undefined, encKeyBuffer: Buffer, del?: boolean, jsonMode?: boolean): void;
@@ -11,7 +11,7 @@ const crypto_util_1 = require("../security/crypto.util");
11
11
  * Encrypts all files in given directory (except *.enc), saves encrypted versions as filename.ext.enc.
12
12
  * Using provided encKey.
13
13
  */
14
- function secretsEncrypt(pattern, file, encKey, del = false, jsonMode = false) {
14
+ function secretsEncrypt(pattern, file, encKeyBuffer, del = false, jsonMode = false) {
15
15
  const patterns = file
16
16
  ? [file]
17
17
  : [
@@ -24,12 +24,12 @@ function secretsEncrypt(pattern, file, encKey, del = false, jsonMode = false) {
24
24
  if (jsonMode) {
25
25
  (0, js_lib_1._assert)(filename.endsWith('.plain.json'), `${path.basename(filename)} MUST end with '.plain.json'`);
26
26
  encFilename = filename.replace('.plain', '');
27
- const json = (0, crypto_util_1.encryptObject)((0, index_1._readJsonSync)(filename), encKey);
27
+ const json = (0, crypto_util_1.encryptObject)((0, index_1._readJsonSync)(filename), encKeyBuffer);
28
28
  (0, index_1._writeJsonSync)(encFilename, json, { spaces: 2 });
29
29
  }
30
30
  else {
31
31
  const plain = fs.readFileSync(filename);
32
- const enc = (0, crypto_util_1.encryptRandomIVBuffer)(plain, encKey);
32
+ const enc = (0, crypto_util_1.encryptRandomIVBuffer)(plain, encKeyBuffer);
33
33
  encFilename = `${filename}.enc`;
34
34
  fs.writeFileSync(encFilename, enc);
35
35
  }
@@ -1,24 +1,34 @@
1
1
  /// <reference types="node" />
2
2
  import { Base64String, StringMap } from '@naturalcycles/js-lib';
3
3
  /**
4
- * Using aes-256-cbc
4
+ * Using aes-256-cbc.
5
5
  */
6
- export declare function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: Base64String): Buffer;
6
+ export declare function encryptRandomIVBuffer(input: Buffer, secretKeyBuffer: Buffer): Buffer;
7
7
  /**
8
- * Using aes-256-cbc
8
+ * Using aes-256-cbc.
9
9
  */
10
- export declare function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: Base64String): Buffer;
10
+ export declare function decryptRandomIVBuffer(input: Buffer, secretKeyBuffer: Buffer): Buffer;
11
11
  /**
12
- * Decrypts all object values.
13
- * Returns object with decrypted values.
12
+ * Decrypts all object values (base64 strings).
13
+ * Returns object with decrypted values (utf8 strings).
14
14
  */
15
- export declare function decryptObject(obj: StringMap<Base64String>, secretKey: string): StringMap;
16
- export declare function encryptObject(obj: StringMap, secretKey: string): StringMap<Base64String>;
15
+ export declare function decryptObject(obj: StringMap<Base64String>, secretKeyBuffer: Buffer): StringMap;
17
16
  /**
18
- * Using aes-256-cbc
17
+ * Encrypts all object values (utf8 strings).
18
+ * Returns object with encrypted values (base64 strings).
19
19
  */
20
- export declare function decryptString(str: Base64String, secretKey: string): string;
20
+ export declare function encryptObject(obj: StringMap, secretKeyBuffer: Buffer): StringMap<Base64String>;
21
21
  /**
22
- * Using aes-256-cbc
22
+ * Using aes-256-cbc.
23
+ *
24
+ * Input is base64 string.
25
+ * Output is utf8 string.
23
26
  */
24
- export declare function encryptString(str: string, secretKey: string): Base64String;
27
+ export declare function decryptString(str: Base64String, secretKeyBuffer: Buffer): string;
28
+ /**
29
+ * Using aes-256-cbc.
30
+ *
31
+ * Input is utf8 string.
32
+ * Output is base64 string.
33
+ */
34
+ export declare function encryptString(str: string, secretKeyBuffer: Buffer): Base64String;
@@ -6,11 +6,11 @@ const js_lib_1 = require("@naturalcycles/js-lib");
6
6
  const hash_util_1 = require("./hash.util");
7
7
  const algorithm = 'aes-256-cbc';
8
8
  /**
9
- * Using aes-256-cbc
9
+ * Using aes-256-cbc.
10
10
  */
11
- function encryptRandomIVBuffer(input, secretKeyBase64) {
12
- // md5 to match aes-256 key length of 32 bytes
13
- const key = (0, hash_util_1.md5)(Buffer.from(secretKeyBase64, 'base64'));
11
+ function encryptRandomIVBuffer(input, secretKeyBuffer) {
12
+ // sha256 to match aes-256 key length
13
+ const key = (0, hash_util_1.sha256AsBuffer)(secretKeyBuffer);
14
14
  // Random iv to achieve non-deterministic encryption (but deterministic decryption)
15
15
  const iv = crypto.randomBytes(16);
16
16
  const cipher = crypto.createCipheriv(algorithm, key, iv);
@@ -18,11 +18,11 @@ function encryptRandomIVBuffer(input, secretKeyBase64) {
18
18
  }
19
19
  exports.encryptRandomIVBuffer = encryptRandomIVBuffer;
20
20
  /**
21
- * Using aes-256-cbc
21
+ * Using aes-256-cbc.
22
22
  */
23
- function decryptRandomIVBuffer(input, secretKeyBase64) {
24
- // md5 to match aes-256 key length of 32 bytes
25
- const key = (0, hash_util_1.md5)(Buffer.from(secretKeyBase64, 'base64'));
23
+ function decryptRandomIVBuffer(input, secretKeyBuffer) {
24
+ // sha256 to match aes-256 key length
25
+ const key = (0, hash_util_1.sha256AsBuffer)(secretKeyBuffer);
26
26
  // iv is first 16 bytes of encrypted buffer, the rest is payload
27
27
  const iv = input.subarray(0, 16);
28
28
  const payload = input.subarray(16);
@@ -31,11 +31,11 @@ function decryptRandomIVBuffer(input, secretKeyBase64) {
31
31
  }
32
32
  exports.decryptRandomIVBuffer = decryptRandomIVBuffer;
33
33
  /**
34
- * Decrypts all object values.
35
- * Returns object with decrypted values.
34
+ * Decrypts all object values (base64 strings).
35
+ * Returns object with decrypted values (utf8 strings).
36
36
  */
37
- function decryptObject(obj, secretKey) {
38
- const { key, iv } = getCryptoParams(secretKey);
37
+ function decryptObject(obj, secretKeyBuffer) {
38
+ const { key, iv } = getCryptoParams(secretKeyBuffer);
39
39
  const r = {};
40
40
  (0, js_lib_1._stringMapEntries)(obj).forEach(([k, v]) => {
41
41
  const decipher = crypto.createDecipheriv(algorithm, key, iv);
@@ -44,8 +44,12 @@ function decryptObject(obj, secretKey) {
44
44
  return r;
45
45
  }
46
46
  exports.decryptObject = decryptObject;
47
- function encryptObject(obj, secretKey) {
48
- const { key, iv } = getCryptoParams(secretKey);
47
+ /**
48
+ * Encrypts all object values (utf8 strings).
49
+ * Returns object with encrypted values (base64 strings).
50
+ */
51
+ function encryptObject(obj, secretKeyBuffer) {
52
+ const { key, iv } = getCryptoParams(secretKeyBuffer);
49
53
  const r = {};
50
54
  (0, js_lib_1._stringMapEntries)(obj).forEach(([k, v]) => {
51
55
  const cipher = crypto.createCipheriv(algorithm, key, iv);
@@ -55,25 +59,31 @@ function encryptObject(obj, secretKey) {
55
59
  }
56
60
  exports.encryptObject = encryptObject;
57
61
  /**
58
- * Using aes-256-cbc
62
+ * Using aes-256-cbc.
63
+ *
64
+ * Input is base64 string.
65
+ * Output is utf8 string.
59
66
  */
60
- function decryptString(str, secretKey) {
61
- const { key, iv } = getCryptoParams(secretKey);
67
+ function decryptString(str, secretKeyBuffer) {
68
+ const { key, iv } = getCryptoParams(secretKeyBuffer);
62
69
  const decipher = crypto.createDecipheriv(algorithm, key, iv);
63
70
  return decipher.update(str, 'base64', 'utf8') + decipher.final('utf8');
64
71
  }
65
72
  exports.decryptString = decryptString;
66
73
  /**
67
- * Using aes-256-cbc
74
+ * Using aes-256-cbc.
75
+ *
76
+ * Input is utf8 string.
77
+ * Output is base64 string.
68
78
  */
69
- function encryptString(str, secretKey) {
70
- const { key, iv } = getCryptoParams(secretKey);
79
+ function encryptString(str, secretKeyBuffer) {
80
+ const { key, iv } = getCryptoParams(secretKeyBuffer);
71
81
  const cipher = crypto.createCipheriv(algorithm, key, iv);
72
82
  return cipher.update(str, 'utf8', 'base64') + cipher.final('base64');
73
83
  }
74
84
  exports.encryptString = encryptString;
75
- function getCryptoParams(secretKey) {
76
- const key = (0, hash_util_1.md5)(secretKey);
77
- const iv = (0, hash_util_1.md5)(secretKey + key).slice(0, 16);
85
+ function getCryptoParams(secretKeyBuffer) {
86
+ const key = (0, hash_util_1.sha256AsBuffer)(secretKeyBuffer);
87
+ const iv = (0, hash_util_1.md5AsBuffer)(Buffer.concat([secretKeyBuffer, key]));
78
88
  return { key, iv };
79
89
  }
@@ -48,7 +48,8 @@ function loadSecretsFromEncryptedJsonFile(filePath, secretEncryptionKey) {
48
48
  let secrets;
49
49
  if (secretEncryptionKey) {
50
50
  const buf = fs.readFileSync(filePath);
51
- const plain = (0, crypto_util_1.decryptRandomIVBuffer)(buf, secretEncryptionKey).toString('utf8');
51
+ const encKeyBuffer = Buffer.from(secretEncryptionKey, 'base64');
52
+ const plain = (0, crypto_util_1.decryptRandomIVBuffer)(buf, encKeyBuffer).toString('utf8');
52
53
  secrets = JSON.parse(plain);
53
54
  }
54
55
  else {
@@ -69,7 +70,8 @@ function loadSecretsFromEncryptedJsonFileValues(filePath, secretEncryptionKey) {
69
70
  (0, js_lib_1._assert)(fs.existsSync(filePath), `loadSecretsFromEncryptedJsonFileValues() cannot load from path: ${filePath}`);
70
71
  let secrets = JSON.parse(fs.readFileSync(filePath, 'utf8'));
71
72
  if (secretEncryptionKey) {
72
- secrets = (0, crypto_util_1.decryptObject)(secrets, secretEncryptionKey);
73
+ const encKeyBuffer = Buffer.from(secretEncryptionKey, 'base64');
74
+ secrets = (0, crypto_util_1.decryptObject)(secrets, encKeyBuffer);
73
75
  }
74
76
  Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v));
75
77
  loaded = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "12.104.0",
3
+ "version": "13.0.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "docs-serve": "vuepress dev docs",
package/readme.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![Maintainability](https://api.codeclimate.com/v1/badges/119a3b4735c4ed81cf84/maintainability)](https://codeclimate.com/github/NaturalCycles/nodejs-lib/maintainability)
9
9
  [![Test Coverage](https://api.codeclimate.com/v1/badges/119a3b4735c4ed81cf84/test_coverage)](https://codeclimate.com/github/NaturalCycles/nodejs-lib/test_coverage)
10
10
  [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)
11
- [![Actions](https://github.com/NaturalCycles/nodejs-lib/workflows/default/badge.svg)](https://github.com/NaturalCycles/nodejs-lib/actions)
11
+ [![Actions](https://github.com/NaturalCycles/nodejs-lib/workflows/ci/badge.svg)](https://github.com/NaturalCycles/nodejs-lib/actions)
12
12
 
13
13
  # [Documentation](https://naturalcycles.github.io/nodejs-lib/)
14
14
 
@@ -6,9 +6,9 @@ import { runScript } from '../script/runScript'
6
6
  import { DecryptCLIOptions, secretsDecrypt } from '../secret/secrets-decrypt.util'
7
7
 
8
8
  runScript(() => {
9
- const { dir, file, encKey, del, jsonMode } = getDecryptCLIOptions()
9
+ const { dir, file, encKeyBuffer, del, jsonMode } = getDecryptCLIOptions()
10
10
 
11
- secretsDecrypt(dir, file, encKey, del, jsonMode)
11
+ secretsDecrypt(dir, file, encKeyBuffer, del, jsonMode)
12
12
  })
13
13
 
14
14
  function getDecryptCLIOptions(): DecryptCLIOptions {
@@ -27,7 +27,7 @@ function getDecryptCLIOptions(): DecryptCLIOptions {
27
27
  },
28
28
  encKey: {
29
29
  type: 'string',
30
- desc: 'Encryption key',
30
+ desc: 'Encryption key as base64 encoded string',
31
31
  // demandOption: true,
32
32
  // default: process.env.SECRET_ENCRYPTION_KEY!,
33
33
  },
@@ -63,6 +63,8 @@ function getDecryptCLIOptions(): DecryptCLIOptions {
63
63
  }
64
64
  }
65
65
 
66
+ const encKeyBuffer = Buffer.from(encKey, 'base64')
67
+
66
68
  // `as any` because @types/yargs can't handle string[] type properly
67
- return { dir: dir as any, file, encKey, del, jsonMode }
69
+ return { dir: dir as any, file, encKeyBuffer, del, jsonMode }
68
70
  }
@@ -6,9 +6,9 @@ import { runScript } from '../script/runScript'
6
6
  import { EncryptCLIOptions, secretsEncrypt } from '../secret/secrets-encrypt.util'
7
7
 
8
8
  runScript(() => {
9
- const { pattern, file, encKey, del, jsonMode } = getEncryptCLIOptions()
9
+ const { pattern, file, encKeyBuffer, del, jsonMode } = getEncryptCLIOptions()
10
10
 
11
- secretsEncrypt(pattern, file, encKey, del, jsonMode)
11
+ secretsEncrypt(pattern, file, encKeyBuffer, del, jsonMode)
12
12
  })
13
13
 
14
14
  function getEncryptCLIOptions(): EncryptCLIOptions {
@@ -18,7 +18,7 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
18
18
  pattern: {
19
19
  type: 'string',
20
20
  array: true,
21
- desc: 'Globby pattern for secrets. Can be many.',
21
+ desc: 'Globby pattern for secrets. Can be multiple.',
22
22
  // demandOption: true,
23
23
  default: './secret/**',
24
24
  },
@@ -28,7 +28,7 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
28
28
  },
29
29
  encKey: {
30
30
  type: 'string',
31
- desc: 'Encryption key',
31
+ desc: 'Encryption key as base64 encoded string',
32
32
  // demandOption: true,
33
33
  // default: process.env.SECRET_ENCRYPTION_KEY!,
34
34
  },
@@ -65,6 +65,8 @@ function getEncryptCLIOptions(): EncryptCLIOptions {
65
65
  }
66
66
  }
67
67
 
68
+ const encKeyBuffer = Buffer.from(encKey, 'base64')
69
+
68
70
  // `as any` because @types/yargs can't handle string[] type properly
69
- return { pattern: pattern as any, file, encKey, del, jsonMode }
71
+ return { pattern: pattern as any, file, encKeyBuffer, del, jsonMode }
70
72
  }
@@ -8,14 +8,17 @@ import { decryptObject, decryptRandomIVBuffer } from '../security/crypto.util'
8
8
  export interface DecryptCLIOptions {
9
9
  dir: string[]
10
10
  file?: string
11
- encKey: string
11
+ encKeyBuffer: Buffer
12
12
  del?: boolean
13
13
  jsonMode?: boolean
14
14
  }
15
15
 
16
16
  // Debug it like this:
17
- // yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
18
17
  // yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets2.plain.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
18
+ // yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets2.json --jsonMode --encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
19
+
20
+ // yarn tsn ./src/bin/secrets-encrypt.ts --file ./src/test/secrets.json -encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
21
+ // yarn tsn ./src/bin/secrets-decrypt.ts --file ./src/test/secrets.json.enc -encKey MPd/30v0Zcce4I5mfwF4NSXrpTYD9OO4/fIqw6rjNiWp2b1GN9Xm8nQZqr7c9kKSsATqtwe0HkJFDUGzDSow44GDgDICgB1u1rGa5eNqtxnOVGRR+lIinCvN/1OnpjzeoJy2bStXPj1DKx8anMqgA8SoOZdlWRNSkEeZlolru8Ey0ujZo22dfwMyRIEniLcqvBm/iMiAkV82fn/TxYw05GarAoJcrfPeDBvuOXsARnMCyX18qTFL0iojxeTU8JHxr8TX3eXDq9cJJmridEKlwRIAzADwtetI4ttlP8lwJj1pmgsBIN3iqYssZYCkZ3HMV6BoEc7LTI5z/45rKrAT1A==
19
22
 
20
23
  /**
21
24
  * Decrypts all files in given directory (*.enc), saves decrypted versions without ending `.enc`.
@@ -24,7 +27,7 @@ export interface DecryptCLIOptions {
24
27
  export function secretsDecrypt(
25
28
  dir: string[],
26
29
  file: string | undefined,
27
- encKey: string,
30
+ encKeyBuffer: Buffer,
28
31
  del = false,
29
32
  jsonMode = false,
30
33
  ): void {
@@ -44,12 +47,12 @@ export function secretsDecrypt(
44
47
  )
45
48
  plainFilename = filename.replace('.json', '.plain.json')
46
49
 
47
- const json = decryptObject(_readJsonSync(filename), encKey)
50
+ const json = decryptObject(_readJsonSync(filename), encKeyBuffer)
48
51
 
49
52
  _writeJsonSync(plainFilename, json, { spaces: 2 })
50
53
  } else {
51
54
  const enc = fs.readFileSync(filename)
52
- const plain = decryptRandomIVBuffer(enc, encKey)
55
+ const plain = decryptRandomIVBuffer(enc, encKeyBuffer)
53
56
  plainFilename = filename.slice(0, filename.length - '.enc'.length)
54
57
  fs.writeFileSync(plainFilename, plain)
55
58
  }
@@ -8,7 +8,7 @@ import { encryptObject, encryptRandomIVBuffer } from '../security/crypto.util'
8
8
  export interface EncryptCLIOptions {
9
9
  pattern: string[]
10
10
  file?: string
11
- encKey: string
11
+ encKeyBuffer: Buffer
12
12
  del?: boolean
13
13
  jsonMode?: boolean
14
14
  }
@@ -20,7 +20,7 @@ export interface EncryptCLIOptions {
20
20
  export function secretsEncrypt(
21
21
  pattern: string[],
22
22
  file: string | undefined,
23
- encKey: string,
23
+ encKeyBuffer: Buffer,
24
24
  del = false,
25
25
  jsonMode = false,
26
26
  ): void {
@@ -41,12 +41,12 @@ export function secretsEncrypt(
41
41
  )
42
42
  encFilename = filename.replace('.plain', '')
43
43
 
44
- const json = encryptObject(_readJsonSync(filename), encKey)
44
+ const json = encryptObject(_readJsonSync(filename), encKeyBuffer)
45
45
 
46
46
  _writeJsonSync(encFilename, json, { spaces: 2 })
47
47
  } else {
48
48
  const plain = fs.readFileSync(filename)
49
- const enc = encryptRandomIVBuffer(plain, encKey)
49
+ const enc = encryptRandomIVBuffer(plain, encKeyBuffer)
50
50
  encFilename = `${filename}.enc`
51
51
  fs.writeFileSync(encFilename, enc)
52
52
  }
@@ -1,15 +1,15 @@
1
1
  import * as crypto from 'node:crypto'
2
2
  import { _stringMapEntries, Base64String, StringMap } from '@naturalcycles/js-lib'
3
- import { md5 } from './hash.util'
3
+ import { md5AsBuffer, sha256AsBuffer } from './hash.util'
4
4
 
5
5
  const algorithm = 'aes-256-cbc'
6
6
 
7
7
  /**
8
- * Using aes-256-cbc
8
+ * Using aes-256-cbc.
9
9
  */
10
- export function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: Base64String): Buffer {
11
- // md5 to match aes-256 key length of 32 bytes
12
- const key = md5(Buffer.from(secretKeyBase64, 'base64'))
10
+ export function encryptRandomIVBuffer(input: Buffer, secretKeyBuffer: Buffer): Buffer {
11
+ // sha256 to match aes-256 key length
12
+ const key = sha256AsBuffer(secretKeyBuffer)
13
13
 
14
14
  // Random iv to achieve non-deterministic encryption (but deterministic decryption)
15
15
  const iv = crypto.randomBytes(16)
@@ -19,11 +19,11 @@ export function encryptRandomIVBuffer(input: Buffer, secretKeyBase64: Base64Stri
19
19
  }
20
20
 
21
21
  /**
22
- * Using aes-256-cbc
22
+ * Using aes-256-cbc.
23
23
  */
24
- export function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: Base64String): Buffer {
25
- // md5 to match aes-256 key length of 32 bytes
26
- const key = md5(Buffer.from(secretKeyBase64, 'base64'))
24
+ export function decryptRandomIVBuffer(input: Buffer, secretKeyBuffer: Buffer): Buffer {
25
+ // sha256 to match aes-256 key length
26
+ const key = sha256AsBuffer(secretKeyBuffer)
27
27
 
28
28
  // iv is first 16 bytes of encrypted buffer, the rest is payload
29
29
  const iv = input.subarray(0, 16)
@@ -35,11 +35,11 @@ export function decryptRandomIVBuffer(input: Buffer, secretKeyBase64: Base64Stri
35
35
  }
36
36
 
37
37
  /**
38
- * Decrypts all object values.
39
- * Returns object with decrypted values.
38
+ * Decrypts all object values (base64 strings).
39
+ * Returns object with decrypted values (utf8 strings).
40
40
  */
41
- export function decryptObject(obj: StringMap<Base64String>, secretKey: string): StringMap {
42
- const { key, iv } = getCryptoParams(secretKey)
41
+ export function decryptObject(obj: StringMap<Base64String>, secretKeyBuffer: Buffer): StringMap {
42
+ const { key, iv } = getCryptoParams(secretKeyBuffer)
43
43
 
44
44
  const r: StringMap = {}
45
45
  _stringMapEntries(obj).forEach(([k, v]) => {
@@ -49,8 +49,12 @@ export function decryptObject(obj: StringMap<Base64String>, secretKey: string):
49
49
  return r
50
50
  }
51
51
 
52
- export function encryptObject(obj: StringMap, secretKey: string): StringMap<Base64String> {
53
- const { key, iv } = getCryptoParams(secretKey)
52
+ /**
53
+ * Encrypts all object values (utf8 strings).
54
+ * Returns object with encrypted values (base64 strings).
55
+ */
56
+ export function encryptObject(obj: StringMap, secretKeyBuffer: Buffer): StringMap<Base64String> {
57
+ const { key, iv } = getCryptoParams(secretKeyBuffer)
54
58
 
55
59
  const r: StringMap = {}
56
60
  _stringMapEntries(obj).forEach(([k, v]) => {
@@ -61,25 +65,31 @@ export function encryptObject(obj: StringMap, secretKey: string): StringMap<Base
61
65
  }
62
66
 
63
67
  /**
64
- * Using aes-256-cbc
68
+ * Using aes-256-cbc.
69
+ *
70
+ * Input is base64 string.
71
+ * Output is utf8 string.
65
72
  */
66
- export function decryptString(str: Base64String, secretKey: string): string {
67
- const { key, iv } = getCryptoParams(secretKey)
73
+ export function decryptString(str: Base64String, secretKeyBuffer: Buffer): string {
74
+ const { key, iv } = getCryptoParams(secretKeyBuffer)
68
75
  const decipher = crypto.createDecipheriv(algorithm, key, iv)
69
76
  return decipher.update(str, 'base64', 'utf8') + decipher.final('utf8')
70
77
  }
71
78
 
72
79
  /**
73
- * Using aes-256-cbc
80
+ * Using aes-256-cbc.
81
+ *
82
+ * Input is utf8 string.
83
+ * Output is base64 string.
74
84
  */
75
- export function encryptString(str: string, secretKey: string): Base64String {
76
- const { key, iv } = getCryptoParams(secretKey)
85
+ export function encryptString(str: string, secretKeyBuffer: Buffer): Base64String {
86
+ const { key, iv } = getCryptoParams(secretKeyBuffer)
77
87
  const cipher = crypto.createCipheriv(algorithm, key, iv)
78
88
  return cipher.update(str, 'utf8', 'base64') + cipher.final('base64')
79
89
  }
80
90
 
81
- function getCryptoParams(secretKey: string): { key: string; iv: string } {
82
- const key = md5(secretKey)
83
- const iv = md5(secretKey + key).slice(0, 16)
91
+ function getCryptoParams(secretKeyBuffer: Buffer): { key: Buffer; iv: Buffer } {
92
+ const key = sha256AsBuffer(secretKeyBuffer)
93
+ const iv = md5AsBuffer(Buffer.concat([secretKeyBuffer, key]))
84
94
  return { key, iv }
85
95
  }
@@ -62,7 +62,8 @@ export function loadSecretsFromEncryptedJsonFile(
62
62
 
63
63
  if (secretEncryptionKey) {
64
64
  const buf = fs.readFileSync(filePath)
65
- const plain = decryptRandomIVBuffer(buf, secretEncryptionKey).toString('utf8')
65
+ const encKeyBuffer = Buffer.from(secretEncryptionKey, 'base64')
66
+ const plain = decryptRandomIVBuffer(buf, encKeyBuffer).toString('utf8')
66
67
  secrets = JSON.parse(plain)
67
68
  } else {
68
69
  secrets = JSON.parse(fs.readFileSync(filePath, 'utf8'))
@@ -94,7 +95,8 @@ export function loadSecretsFromEncryptedJsonFileValues(
94
95
  let secrets: StringMap = JSON.parse(fs.readFileSync(filePath, 'utf8'))
95
96
 
96
97
  if (secretEncryptionKey) {
97
- secrets = decryptObject(secrets, secretEncryptionKey)
98
+ const encKeyBuffer = Buffer.from(secretEncryptionKey, 'base64')
99
+ secrets = decryptObject(secrets, encKeyBuffer)
98
100
  }
99
101
 
100
102
  Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))