@node-cli/secret 1.0.7 → 1.2.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.
package/README.md CHANGED
@@ -2,15 +2,64 @@
2
2
 
3
3
  ![npm](https://img.shields.io/npm/v/@node-cli/secret?label=version&logo=npm)
4
4
 
5
- > Secret is a command line tool that can encode or decode a file with a password.
5
+ > Secret is a command line tool that can encode or decode a file with a password. It also exposes a library to be used in other projects.
6
6
 
7
- ## Installation
7
+ ## Installation as a CLI
8
8
 
9
9
  ```sh
10
10
  > npm install --global @node-cli/secret
11
11
  ```
12
12
 
13
- ## Examples
13
+ ## Installation as a library
14
+
15
+ ```sh
16
+ > cd your-project
17
+ > npm install @node-cli/secret
18
+ ```
19
+
20
+ ## Usage as a library
21
+
22
+ ### Encrypt a string with a password
23
+
24
+ ```js
25
+ import { encrypt } from "@node-cli/secret";
26
+ const encrypted = encrypt("password", "Hello World");
27
+ ```
28
+
29
+ ### Decrypt a string with a password
30
+
31
+ ```js
32
+ import { decrypt } from "@node-cli/secret";
33
+ const decrypted = decrypt("password", encrypted);
34
+ ```
35
+
36
+ ### Hash a password with a default salt
37
+
38
+ ```js
39
+ import { hashPassword } from "@node-cli/secret";
40
+ const hashed = hashPassword("password");
41
+ // hashed === "some-default-salt:some-hex-string
42
+ ```
43
+
44
+ ### Hash a password with a custom salt
45
+
46
+ ```js
47
+ import { createSalt, hashPassword } from "@node-cli/secret";
48
+ const salt = createSalt(42);
49
+ const hashed = hashPassword("password", salt);
50
+ // hashed === "some-salt:some-hex-string
51
+ ```
52
+
53
+ ### Verify a password against a hash
54
+
55
+ ```js
56
+ import { hashPassword, verifyPassword } from "@node-cli/secret";
57
+ const hashed = hashPassword("password");
58
+ const isVerified = verifyPassword("password", hashed);
59
+ // isVerified === true
60
+ ```
61
+
62
+ ## Usage as a CLI
14
63
 
15
64
  NOTE: The password is not stored anywhere, it is only used to encrypt or decrypt the file and will be prompted each time.
16
65
 
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  * @file Automatically generated by barrelsby.
3
3
  */
4
4
 
5
+ export * from "./lib";
5
6
  export * from "./parse";
6
7
  export * from "./secret";
7
8
  export * from "./utilities";
package/dist/lib.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { Logger } from "@node-cli/logger";
2
+ export declare const logger: Logger;
3
+ /**
4
+ * Create an hexadecimal hash from a given string. The default
5
+ * algorithm is md5 but it can be changed to anything that
6
+ * crypto.createHash allows.
7
+ * @param {String} string the string to hash
8
+ * @param {String} [algorithm='md5'] the algorithm to use or hashing
9
+ * @return {String} the hashed string in hexa format
10
+ */
11
+ export declare const createHash: (string: string, algorithm?: string) => string;
12
+ /**
13
+ * Creates a random SALT value using the crypto library.
14
+ * @param {Number} [bytes=256] the number of bytes to generate
15
+ * @return {String} the generated salt in hexa format
16
+ */
17
+ export declare const createSalt: (bytes?: number) => string;
18
+ /**
19
+ * Encrypts a string or a buffer using AES-256-CTR
20
+ * algorithm.
21
+ * @param {String} password a unique password
22
+ * @param {String} data a string to encrypt
23
+ * @return {String} the encrypted data in hexa
24
+ * encoding, followed by a dollar sign ($) and by a
25
+ * unique random initialization vector.
26
+ */
27
+ export declare const encrypt: (password: string, data: string) => string;
28
+ /**
29
+ * Decrypts a string that was encrypted using the
30
+ * AES-256-CRT algorithm via `encrypt`. It expects
31
+ * the encrypted string to have the corresponding
32
+ * initialization vector appended at the end, after
33
+ * a dollar sign ($) - which was done via the
34
+ * corresponding `encrypt` method.
35
+ * @param {String} password a unique password
36
+ * @param {String} data a string to decrypt
37
+ * @return {String} the decrypted data
38
+ */
39
+ export declare const decrypt: (password: string, data: string) => string;
40
+ /**
41
+ * Method to hash a password using the scrypt algorithm.
42
+ *
43
+ * @param {String} password the password to hash
44
+ * @param {String} salt the salt to use
45
+ * @returns {String} the hashed password
46
+ */
47
+ export declare const hashPassword: (password: string, salt?: string) => string;
48
+ /**
49
+ * Method to verify a password against a hash using the scrypt algorithm.
50
+ *
51
+ * @param {String} password the password to verify
52
+ * @param {String} hash the hash to verify against
53
+ * @returns {Boolean} true if the password is correct, false otherwise
54
+ */
55
+ export declare const verifyPassword: (password: string, hash: string) => boolean;
package/dist/lib.js ADDED
@@ -0,0 +1,134 @@
1
+ import crypto from "node:crypto";
2
+ import { Logger } from "@node-cli/logger";
3
+ export const logger = new Logger({
4
+ boring: process.env.NODE_ENV === "test"
5
+ });
6
+ const HEX = "hex";
7
+ const UTF8 = "utf8";
8
+ const DEFAULT_CRYPTO_ALGO = "aes-256-ctr";
9
+ const DEFAULT_HASH_ALGO = "md5";
10
+ const DEFAULT_BYTES_FOR_IV = 16;
11
+ const DEFAULT_BYTES_FOR_SALT = 256;
12
+ const DEFAULT_SALT_SIZE_FOR_HASH = 16;
13
+ /**
14
+ * Create an hexadecimal hash from a given string. The default
15
+ * algorithm is md5 but it can be changed to anything that
16
+ * crypto.createHash allows.
17
+ * @param {String} string the string to hash
18
+ * @param {String} [algorithm='md5'] the algorithm to use or hashing
19
+ * @return {String} the hashed string in hexa format
20
+ */ export const createHash = (string, algorithm = DEFAULT_HASH_ALGO)=>{
21
+ return crypto.createHash(algorithm).update(string, UTF8).digest(HEX);
22
+ };
23
+ /**
24
+ * Creates a random SALT value using the crypto library.
25
+ * @param {Number} [bytes=256] the number of bytes to generate
26
+ * @return {String} the generated salt in hexa format
27
+ */ export const createSalt = (bytes)=>{
28
+ bytes = bytes || DEFAULT_BYTES_FOR_SALT;
29
+ return crypto.randomBytes(bytes).toString(HEX);
30
+ };
31
+ /**
32
+ * Encrypts a string or a buffer using AES-256-CTR
33
+ * algorithm.
34
+ * @param {String} password a unique password
35
+ * @param {String} data a string to encrypt
36
+ * @return {String} the encrypted data in hexa
37
+ * encoding, followed by a dollar sign ($) and by a
38
+ * unique random initialization vector.
39
+ */ export const encrypt = (password, data)=>{
40
+ // Ensure that the initialization vector (IV) is random.
41
+ const iv = crypto.randomBytes(DEFAULT_BYTES_FOR_IV);
42
+ // Hash the given password (result is always the same).
43
+ const key = createHash(password);
44
+ // Create a cipher.
45
+ const cipher = crypto.createCipheriv(DEFAULT_CRYPTO_ALGO, key, iv);
46
+ // Encrypt the data using the newly created cipher.
47
+ const encrypted = cipher.update(data, UTF8, HEX) + cipher.final(HEX);
48
+ /*
49
+ * Append the IV at the end of the encrypted data
50
+ * to reuse it for decryption (IV is not a key,
51
+ * it can be public).
52
+ */ return `${encrypted}$${iv.toString(HEX)}`;
53
+ };
54
+ /**
55
+ * Decrypts a string that was encrypted using the
56
+ * AES-256-CRT algorithm via `encrypt`. It expects
57
+ * the encrypted string to have the corresponding
58
+ * initialization vector appended at the end, after
59
+ * a dollar sign ($) - which was done via the
60
+ * corresponding `encrypt` method.
61
+ * @param {String} password a unique password
62
+ * @param {String} data a string to decrypt
63
+ * @return {String} the decrypted data
64
+ */ export const decrypt = (password, data)=>{
65
+ // Extract encrypted data and initialization vector (IV).
66
+ const [encrypted, ivHex] = data.split("$");
67
+ // Create a buffer out of the raw hex IV
68
+ const iv = Buffer.from(ivHex, HEX);
69
+ // Hash the given password (result is always the same).
70
+ const hash = createHash(password);
71
+ // Create a cipher.
72
+ const decipher = crypto.createDecipheriv(DEFAULT_CRYPTO_ALGO, hash, iv);
73
+ // Return the decrypted data using the newly created cipher.
74
+ return decipher.update(encrypted, HEX, UTF8) + decipher.final("utf8");
75
+ };
76
+ /**
77
+ * Function using the scrypt Password-Based Key Derivation method.
78
+ * It generates a derived key of a given length from the given data
79
+ * and salt.
80
+ *
81
+ * @param {String} data the data to derive the key from
82
+ * @param {String} salt the salt to use
83
+ * @returns {String} the derived key in hex format
84
+ */ function generateScryptKey(data, salt) {
85
+ const encodedData = new TextEncoder().encode(data);
86
+ const encodedSalt = new TextEncoder().encode(salt);
87
+ /**
88
+ * The scryptSync parameters are based on the following recommendations:
89
+ * - The cost parameter should be set as high as possible without causing
90
+ * significant performance degradation. OWASP recommends a cost of 2^17,
91
+ * but this fails on some systems due to the high memory requirements.
92
+ * - The blockSize parameter should be set to 8 at a minimum.
93
+ * - The parallelization parameter should be set to 1.
94
+ * - The size parameter should be set to 64.
95
+ *
96
+ * Reference:
97
+ * https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
98
+ */ const derivedKey = crypto.scryptSync(encodedData, encodedSalt, 64, {
99
+ cost: Math.pow(2, 14),
100
+ blockSize: 8,
101
+ parallelization: 1
102
+ });
103
+ return derivedKey.toString(HEX);
104
+ }
105
+ /**
106
+ * Method to hash a password using the scrypt algorithm.
107
+ *
108
+ * @param {String} password the password to hash
109
+ * @param {String} salt the salt to use
110
+ * @returns {String} the hashed password
111
+ */ export const hashPassword = (password, salt)=>{
112
+ const pepper = salt || createSalt(DEFAULT_SALT_SIZE_FOR_HASH);
113
+ const key = generateScryptKey(password.normalize("NFKC"), pepper);
114
+ return `${pepper}:${key}`;
115
+ };
116
+ /**
117
+ * Method to verify a password against a hash using the scrypt algorithm.
118
+ *
119
+ * @param {String} password the password to verify
120
+ * @param {String} hash the hash to verify against
121
+ * @returns {Boolean} true if the password is correct, false otherwise
122
+ */ export const verifyPassword = (password, hash)=>{
123
+ const [salt, key] = hash.split(":");
124
+ const keyBuffer = Buffer.from(key, HEX);
125
+ const derivedKey = generateScryptKey(password.normalize("NFKC"), salt);
126
+ const derivedKeyBuffer = Buffer.from(derivedKey, HEX);
127
+ try {
128
+ return crypto.timingSafeEqual(keyBuffer, derivedKeyBuffer);
129
+ } catch (_error) {
130
+ return false;
131
+ }
132
+ };
133
+
134
+ //# sourceMappingURL=lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport { Logger } from \"@node-cli/logger\";\n\nexport const logger = new Logger({\n\tboring: process.env.NODE_ENV === \"test\",\n});\n\nconst HEX = \"hex\";\nconst UTF8 = \"utf8\";\n\nconst DEFAULT_CRYPTO_ALGO = \"aes-256-ctr\";\nconst DEFAULT_HASH_ALGO = \"md5\";\nconst DEFAULT_BYTES_FOR_IV = 16;\nconst DEFAULT_BYTES_FOR_SALT = 256;\nconst DEFAULT_SALT_SIZE_FOR_HASH = 16;\n\n/**\n * Create an hexadecimal hash from a given string. The default\n * algorithm is md5 but it can be changed to anything that\n * crypto.createHash allows.\n * @param {String} string the string to hash\n * @param {String} [algorithm='md5'] the algorithm to use or hashing\n * @return {String} the hashed string in hexa format\n */\nexport const createHash = (\n\tstring: string,\n\talgorithm: string = DEFAULT_HASH_ALGO,\n): string => {\n\treturn crypto.createHash(algorithm).update(string, UTF8).digest(HEX);\n};\n\n/**\n * Creates a random SALT value using the crypto library.\n * @param {Number} [bytes=256] the number of bytes to generate\n * @return {String} the generated salt in hexa format\n */\nexport const createSalt = (bytes?: number): string => {\n\tbytes = bytes || DEFAULT_BYTES_FOR_SALT;\n\treturn crypto.randomBytes(bytes).toString(HEX);\n};\n\n/**\n * Encrypts a string or a buffer using AES-256-CTR\n * algorithm.\n * @param {String} password a unique password\n * @param {String} data a string to encrypt\n * @return {String} the encrypted data in hexa\n * encoding, followed by a dollar sign ($) and by a\n * unique random initialization vector.\n */\nexport const encrypt = (password: string, data: string): string => {\n\t// Ensure that the initialization vector (IV) is random.\n\tconst iv = crypto.randomBytes(DEFAULT_BYTES_FOR_IV);\n\t// Hash the given password (result is always the same).\n\tconst key = createHash(password);\n\t// Create a cipher.\n\tconst cipher = crypto.createCipheriv(DEFAULT_CRYPTO_ALGO, key, iv);\n\t// Encrypt the data using the newly created cipher.\n\tconst encrypted = cipher.update(data, UTF8, HEX) + cipher.final(HEX);\n\t/*\n\t * Append the IV at the end of the encrypted data\n\t * to reuse it for decryption (IV is not a key,\n\t * it can be public).\n\t */\n\treturn `${encrypted}$${iv.toString(HEX)}`;\n};\n\n/**\n * Decrypts a string that was encrypted using the\n * AES-256-CRT algorithm via `encrypt`. It expects\n * the encrypted string to have the corresponding\n * initialization vector appended at the end, after\n * a dollar sign ($) - which was done via the\n * corresponding `encrypt` method.\n * @param {String} password a unique password\n * @param {String} data a string to decrypt\n * @return {String} the decrypted data\n */\nexport const decrypt = (password: string, data: string): string => {\n\t// Extract encrypted data and initialization vector (IV).\n\tconst [encrypted, ivHex] = data.split(\"$\");\n\t// Create a buffer out of the raw hex IV\n\tconst iv = Buffer.from(ivHex, HEX);\n\t// Hash the given password (result is always the same).\n\tconst hash = createHash(password);\n\t// Create a cipher.\n\tconst decipher = crypto.createDecipheriv(DEFAULT_CRYPTO_ALGO, hash, iv);\n\t// Return the decrypted data using the newly created cipher.\n\treturn decipher.update(encrypted, HEX, UTF8) + decipher.final(\"utf8\");\n};\n\n/**\n * Function using the scrypt Password-Based Key Derivation method.\n * It generates a derived key of a given length from the given data\n * and salt.\n *\n * @param {String} data the data to derive the key from\n * @param {String} salt the salt to use\n * @returns {String} the derived key in hex format\n */\nfunction generateScryptKey(data: string, salt: string): string {\n\tconst encodedData = new TextEncoder().encode(data);\n\tconst encodedSalt = new TextEncoder().encode(salt);\n\t/**\n\t * The scryptSync parameters are based on the following recommendations:\n\t * - The cost parameter should be set as high as possible without causing\n\t * significant performance degradation. OWASP recommends a cost of 2^17,\n\t * but this fails on some systems due to the high memory requirements.\n\t * - The blockSize parameter should be set to 8 at a minimum.\n\t * - The parallelization parameter should be set to 1.\n\t * - The size parameter should be set to 64.\n\t *\n\t * Reference:\n\t * https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html\n\t */\n\tconst derivedKey = crypto.scryptSync(encodedData, encodedSalt, 64, {\n\t\tcost: Math.pow(2, 14),\n\t\tblockSize: 8,\n\t\tparallelization: 1,\n\t});\n\treturn (derivedKey as Buffer).toString(HEX);\n}\n\n/**\n * Method to hash a password using the scrypt algorithm.\n *\n * @param {String} password the password to hash\n * @param {String} salt the salt to use\n * @returns {String} the hashed password\n */\nexport const hashPassword = (password: string, salt?: string): string => {\n\tconst pepper = salt || createSalt(DEFAULT_SALT_SIZE_FOR_HASH);\n\tconst key = generateScryptKey(password.normalize(\"NFKC\"), pepper);\n\treturn `${pepper}:${key}`;\n};\n\n/**\n * Method to verify a password against a hash using the scrypt algorithm.\n *\n * @param {String} password the password to verify\n * @param {String} hash the hash to verify against\n * @returns {Boolean} true if the password is correct, false otherwise\n */\nexport const verifyPassword = (password: string, hash: string): boolean => {\n\tconst [salt, key] = hash.split(\":\");\n\tconst keyBuffer = Buffer.from(key, HEX);\n\n\tconst derivedKey = generateScryptKey(password.normalize(\"NFKC\"), salt);\n\tconst derivedKeyBuffer = Buffer.from(derivedKey, HEX);\n\n\ttry {\n\t\treturn crypto.timingSafeEqual(keyBuffer, derivedKeyBuffer);\n\t} catch (_error) {\n\t\treturn false;\n\t}\n};\n"],"names":["crypto","Logger","logger","boring","process","env","NODE_ENV","HEX","UTF8","DEFAULT_CRYPTO_ALGO","DEFAULT_HASH_ALGO","DEFAULT_BYTES_FOR_IV","DEFAULT_BYTES_FOR_SALT","DEFAULT_SALT_SIZE_FOR_HASH","createHash","string","algorithm","update","digest","createSalt","bytes","randomBytes","toString","encrypt","password","data","iv","key","cipher","createCipheriv","encrypted","final","decrypt","ivHex","split","Buffer","from","hash","decipher","createDecipheriv","generateScryptKey","salt","encodedData","TextEncoder","encode","encodedSalt","derivedKey","scryptSync","cost","Math","pow","blockSize","parallelization","hashPassword","pepper","normalize","verifyPassword","keyBuffer","derivedKeyBuffer","timingSafeEqual","_error"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
@@ -1,36 +1,5 @@
1
1
  import { Logger } from "@node-cli/logger";
2
2
  export declare const logger: Logger;
3
- /**
4
- * Create an hexadecimal hash from a given string. The default
5
- * algorithm is md5 but it can be changed to anything that
6
- * crypto.createHash allows.
7
- * @param {String} string the string to hash
8
- * @param {String} [algorithm='md5'] the algorithm to use or hashing
9
- * @return {String} the hashed string in hexa format
10
- */
11
- export declare const createHash: (string: string, algorithm?: string) => string;
12
- /**
13
- * Encrypts a string or a buffer using AES-256-CTR
14
- * algorithm.
15
- * @param {String} password a unique password
16
- * @param {String} data a string to encrypt
17
- * @return {String} the encrypted data in hexa
18
- * encoding, followed by a dollar sign ($) and by a
19
- * unique random initialization vector.
20
- */
21
- export declare const encrypt: (password: string, data: string) => string;
22
- /**
23
- * Decrypts a string that was encrypted using the
24
- * AES-256-CRT algorithm via `encrypt`. It expects
25
- * the encrypted string to have the corresponding
26
- * initialization vector appended at the end, after
27
- * a dollar sign ($) - which was done via the
28
- * corresponding `encrypt` method.
29
- * @param {String} password a unique password
30
- * @param {String} data a string to decrypt
31
- * @return {String} the decrypted data
32
- */
33
- export declare const decrypt: (password: string, data: string) => string;
34
3
  /**
35
4
  * Process a file with a given password. The file can be
36
5
  * encoded or decoded depending on the `encode` flag.
package/dist/utilities.js CHANGED
@@ -1,71 +1,12 @@
1
- import crypto from "node:crypto";
2
1
  import { Logger } from "@node-cli/logger";
3
2
  import fs from "fs-extra";
4
3
  import inquirer from "inquirer";
4
+ import { decrypt, encrypt } from "./lib.js";
5
5
  export const logger = new Logger({
6
6
  boring: process.env.NODE_ENV === "test"
7
7
  });
8
- const HEX = "hex";
9
8
  const UTF8 = "utf8";
10
- const DEFAULT_CRYPTO_ALGO = "aes-256-ctr";
11
- const DEFAULT_HASH_ALGO = "md5";
12
- const DEFAULT_BYTES_FOR_IV = 16;
13
9
  const DEFAULT_FILE_ENCODING = UTF8;
14
- /**
15
- * Create an hexadecimal hash from a given string. The default
16
- * algorithm is md5 but it can be changed to anything that
17
- * crypto.createHash allows.
18
- * @param {String} string the string to hash
19
- * @param {String} [algorithm='md5'] the algorithm to use or hashing
20
- * @return {String} the hashed string in hexa format
21
- */ export const createHash = (string, algorithm = DEFAULT_HASH_ALGO)=>{
22
- return crypto.createHash(algorithm).update(string, UTF8).digest(HEX);
23
- };
24
- /**
25
- * Encrypts a string or a buffer using AES-256-CTR
26
- * algorithm.
27
- * @param {String} password a unique password
28
- * @param {String} data a string to encrypt
29
- * @return {String} the encrypted data in hexa
30
- * encoding, followed by a dollar sign ($) and by a
31
- * unique random initialization vector.
32
- */ export const encrypt = (password, data)=>{
33
- // Ensure that the initialization vector (IV) is random.
34
- const iv = crypto.randomBytes(DEFAULT_BYTES_FOR_IV);
35
- // Hash the given password (result is always the same).
36
- const key = createHash(password);
37
- // Create a cipher.
38
- const cipher = crypto.createCipheriv(DEFAULT_CRYPTO_ALGO, key, iv);
39
- // Encrypt the data using the newly created cipher.
40
- const encrypted = cipher.update(data, UTF8, HEX) + cipher.final(HEX);
41
- /*
42
- * Append the IV at the end of the encrypted data
43
- * to reuse it for decryption (IV is not a key,
44
- * it can be public).
45
- */ return `${encrypted}$${iv.toString(HEX)}`;
46
- };
47
- /**
48
- * Decrypts a string that was encrypted using the
49
- * AES-256-CRT algorithm via `encrypt`. It expects
50
- * the encrypted string to have the corresponding
51
- * initialization vector appended at the end, after
52
- * a dollar sign ($) - which was done via the
53
- * corresponding `encrypt` method.
54
- * @param {String} password a unique password
55
- * @param {String} data a string to decrypt
56
- * @return {String} the decrypted data
57
- */ export const decrypt = (password, data)=>{
58
- // Extract encrypted data and initialization vector (IV).
59
- const [encrypted, ivHex] = data.split("$");
60
- // Create a buffer out of the raw hex IV
61
- const iv = Buffer.from(ivHex, HEX);
62
- // Hash the given password (result is always the same).
63
- const hash = createHash(password);
64
- // Create a cipher.
65
- const decipher = crypto.createDecipheriv(DEFAULT_CRYPTO_ALGO, hash, iv);
66
- // Return the decrypted data using the newly created cipher.
67
- return decipher.update(encrypted, HEX, UTF8) + decipher.final("utf8");
68
- };
69
10
  export const processFileWithPassword = async (options)=>{
70
11
  const { encode, input, output, password } = options;
71
12
  const fileProcessor = encode ? encrypt : decrypt;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utilities.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport { Logger } from \"@node-cli/logger\";\nimport fs from \"fs-extra\";\nimport inquirer from \"inquirer\";\n\nexport const logger = new Logger({\n\tboring: process.env.NODE_ENV === \"test\",\n});\n\nconst HEX = \"hex\";\nconst UTF8 = \"utf8\";\n\nconst DEFAULT_CRYPTO_ALGO = \"aes-256-ctr\";\nconst DEFAULT_HASH_ALGO = \"md5\";\nconst DEFAULT_BYTES_FOR_IV = 16;\nconst DEFAULT_FILE_ENCODING = UTF8;\n\n/**\n * Create an hexadecimal hash from a given string. The default\n * algorithm is md5 but it can be changed to anything that\n * crypto.createHash allows.\n * @param {String} string the string to hash\n * @param {String} [algorithm='md5'] the algorithm to use or hashing\n * @return {String} the hashed string in hexa format\n */\nexport const createHash = (\n\tstring: string,\n\talgorithm: string = DEFAULT_HASH_ALGO,\n): string => {\n\treturn crypto.createHash(algorithm).update(string, UTF8).digest(HEX);\n};\n\n/**\n * Encrypts a string or a buffer using AES-256-CTR\n * algorithm.\n * @param {String} password a unique password\n * @param {String} data a string to encrypt\n * @return {String} the encrypted data in hexa\n * encoding, followed by a dollar sign ($) and by a\n * unique random initialization vector.\n */\nexport const encrypt = (password: string, data: string): string => {\n\t// Ensure that the initialization vector (IV) is random.\n\tconst iv = crypto.randomBytes(DEFAULT_BYTES_FOR_IV);\n\t// Hash the given password (result is always the same).\n\tconst key = createHash(password);\n\t// Create a cipher.\n\tconst cipher = crypto.createCipheriv(DEFAULT_CRYPTO_ALGO, key, iv);\n\t// Encrypt the data using the newly created cipher.\n\tconst encrypted = cipher.update(data, UTF8, HEX) + cipher.final(HEX);\n\t/*\n\t * Append the IV at the end of the encrypted data\n\t * to reuse it for decryption (IV is not a key,\n\t * it can be public).\n\t */\n\treturn `${encrypted}$${iv.toString(HEX)}`;\n};\n\n/**\n * Decrypts a string that was encrypted using the\n * AES-256-CRT algorithm via `encrypt`. It expects\n * the encrypted string to have the corresponding\n * initialization vector appended at the end, after\n * a dollar sign ($) - which was done via the\n * corresponding `encrypt` method.\n * @param {String} password a unique password\n * @param {String} data a string to decrypt\n * @return {String} the decrypted data\n */\nexport const decrypt = (password: string, data: string): string => {\n\t// Extract encrypted data and initialization vector (IV).\n\tconst [encrypted, ivHex] = data.split(\"$\");\n\t// Create a buffer out of the raw hex IV\n\tconst iv = Buffer.from(ivHex, HEX);\n\t// Hash the given password (result is always the same).\n\tconst hash = createHash(password);\n\t// Create a cipher.\n\tconst decipher = crypto.createDecipheriv(DEFAULT_CRYPTO_ALGO, hash, iv);\n\t// Return the decrypted data using the newly created cipher.\n\treturn decipher.update(encrypted, HEX, UTF8) + decipher.final(\"utf8\");\n};\n\n/**\n * Process a file with a given password. The file can be\n * encoded or decoded depending on the `encode` flag.\n * @param {Boolean} encode whether to encode or decode the file\n * @param {String} input the input file path\n * @param {String} [output] the output file path\n * @param {String} password the password to use\n * @return {Promise} a promise that resolves when\n * the file has been processed.\n */\nexport type ProcessFileOptions = {\n\tencode: boolean;\n\tinput: string;\n\toutput?: string;\n\tpassword: string;\n};\nexport const processFileWithPassword = async (\n\toptions: ProcessFileOptions,\n): Promise<void> => {\n\tconst { encode, input, output, password } = options;\n\tconst fileProcessor = encode ? encrypt : decrypt;\n\tconst data = await fs.readFile(input, DEFAULT_FILE_ENCODING);\n\n\tif (output) {\n\t\t// Save data to output file\n\t\tawait fs.outputFile(output, fileProcessor(password, data));\n\t} else {\n\t\t// Print to stdout directly\n\t\tlogger.log(fileProcessor(password, data));\n\t}\n};\n\n/* istanbul ignore next */\nexport const displayConfirmation = async (message: string) => {\n\tconst questions = {\n\t\tdefault: true,\n\t\tmessage: message || \"Do you want to continue?\",\n\t\tname: \"goodToGo\",\n\t\ttype: \"confirm\",\n\t};\n\tlogger.log();\n\tconst answers = await inquirer.prompt(questions);\n\treturn answers.goodToGo;\n};\n\n/* istanbul ignore next */\nexport const displayPromptWithPassword = async (message: string) => {\n\tconst questions = {\n\t\tmessage: message,\n\t\tname: \"password\",\n\t\ttype: \"password\",\n\t\tvalidate(value: string) {\n\t\t\tif (!value) {\n\t\t\t\treturn \"Password cannot be empty...\";\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\t};\n\tconst answers = await inquirer.prompt(questions);\n\treturn answers.password;\n};\n\n/* istanbul ignore next */\nexport const shouldContinue = (goodToGo: boolean) => {\n\tif (!goodToGo) {\n\t\tlogger.log(\"\\nBye then!\");\n\t\t// eslint-disable-next-line unicorn/no-process-exit\n\t\tprocess.exit(0);\n\t}\n\treturn true;\n};\n"],"names":["crypto","Logger","fs","inquirer","logger","boring","process","env","NODE_ENV","HEX","UTF8","DEFAULT_CRYPTO_ALGO","DEFAULT_HASH_ALGO","DEFAULT_BYTES_FOR_IV","DEFAULT_FILE_ENCODING","createHash","string","algorithm","update","digest","encrypt","password","data","iv","randomBytes","key","cipher","createCipheriv","encrypted","final","toString","decrypt","ivHex","split","Buffer","from","hash","decipher","createDecipheriv","processFileWithPassword","options","encode","input","output","fileProcessor","readFile","outputFile","log","displayConfirmation","message","questions","default","name","type","answers","prompt","goodToGo","displayPromptWithPassword","validate","value","shouldContinue","exit"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"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"}
1
+ {"version":3,"sources":["../src/utilities.ts"],"sourcesContent":["import { Logger } from \"@node-cli/logger\";\nimport fs from \"fs-extra\";\nimport inquirer from \"inquirer\";\n\nimport { decrypt, encrypt } from \"./lib.js\";\n\nexport const logger = new Logger({\n\tboring: process.env.NODE_ENV === \"test\",\n});\n\nconst UTF8 = \"utf8\";\nconst DEFAULT_FILE_ENCODING = UTF8;\n\n/**\n * Process a file with a given password. The file can be\n * encoded or decoded depending on the `encode` flag.\n * @param {Boolean} encode whether to encode or decode the file\n * @param {String} input the input file path\n * @param {String} [output] the output file path\n * @param {String} password the password to use\n * @return {Promise} a promise that resolves when\n * the file has been processed.\n */\nexport type ProcessFileOptions = {\n\tencode: boolean;\n\tinput: string;\n\toutput?: string;\n\tpassword: string;\n};\nexport const processFileWithPassword = async (\n\toptions: ProcessFileOptions,\n): Promise<void> => {\n\tconst { encode, input, output, password } = options;\n\tconst fileProcessor = encode ? encrypt : decrypt;\n\tconst data = await fs.readFile(input, DEFAULT_FILE_ENCODING);\n\n\tif (output) {\n\t\t// Save data to output file\n\t\tawait fs.outputFile(output, fileProcessor(password, data));\n\t} else {\n\t\t// Print to stdout directly\n\t\tlogger.log(fileProcessor(password, data));\n\t}\n};\n\n/* istanbul ignore next */\nexport const displayConfirmation = async (message: string) => {\n\tconst questions = {\n\t\tdefault: true,\n\t\tmessage: message || \"Do you want to continue?\",\n\t\tname: \"goodToGo\",\n\t\ttype: \"confirm\",\n\t};\n\tlogger.log();\n\tconst answers = await inquirer.prompt(questions);\n\treturn answers.goodToGo;\n};\n\n/* istanbul ignore next */\nexport const displayPromptWithPassword = async (message: string) => {\n\tconst questions = {\n\t\tmessage: message,\n\t\tname: \"password\",\n\t\ttype: \"password\",\n\t\tvalidate(value: string) {\n\t\t\tif (!value) {\n\t\t\t\treturn \"Password cannot be empty...\";\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\t};\n\tconst answers = await inquirer.prompt(questions);\n\treturn answers.password;\n};\n\n/* istanbul ignore next */\nexport const shouldContinue = (goodToGo: boolean) => {\n\tif (!goodToGo) {\n\t\tlogger.log(\"\\nBye then!\");\n\t\t// eslint-disable-next-line unicorn/no-process-exit\n\t\tprocess.exit(0);\n\t}\n\treturn true;\n};\n"],"names":["Logger","fs","inquirer","decrypt","encrypt","logger","boring","process","env","NODE_ENV","UTF8","DEFAULT_FILE_ENCODING","processFileWithPassword","options","encode","input","output","password","fileProcessor","data","readFile","outputFile","log","displayConfirmation","message","questions","default","name","type","answers","prompt","goodToGo","displayPromptWithPassword","validate","value","shouldContinue","exit"],"rangeMappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;","mappings":"AAAA,SAASA,MAAM,QAAQ,mBAAmB;AAC1C,OAAOC,QAAQ,WAAW;AAC1B,OAAOC,cAAc,WAAW;AAEhC,SAASC,OAAO,EAAEC,OAAO,QAAQ,WAAW;AAE5C,OAAO,MAAMC,SAAS,IAAIL,OAAO;IAChCM,QAAQC,QAAQC,GAAG,CAACC,QAAQ,KAAK;AAClC,GAAG;AAEH,MAAMC,OAAO;AACb,MAAMC,wBAAwBD;AAkB9B,OAAO,MAAME,0BAA0B,OACtCC;IAEA,MAAM,EAAEC,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEC,QAAQ,EAAE,GAAGJ;IAC5C,MAAMK,gBAAgBJ,SAASV,UAAUD;IACzC,MAAMgB,OAAO,MAAMlB,GAAGmB,QAAQ,CAACL,OAAOJ;IAEtC,IAAIK,QAAQ;QACX,2BAA2B;QAC3B,MAAMf,GAAGoB,UAAU,CAACL,QAAQE,cAAcD,UAAUE;IACrD,OAAO;QACN,2BAA2B;QAC3Bd,OAAOiB,GAAG,CAACJ,cAAcD,UAAUE;IACpC;AACD,EAAE;AAEF,wBAAwB,GACxB,OAAO,MAAMI,sBAAsB,OAAOC;IACzC,MAAMC,YAAY;QACjBC,SAAS;QACTF,SAASA,WAAW;QACpBG,MAAM;QACNC,MAAM;IACP;IACAvB,OAAOiB,GAAG;IACV,MAAMO,UAAU,MAAM3B,SAAS4B,MAAM,CAACL;IACtC,OAAOI,QAAQE,QAAQ;AACxB,EAAE;AAEF,wBAAwB,GACxB,OAAO,MAAMC,4BAA4B,OAAOR;IAC/C,MAAMC,YAAY;QACjBD,SAASA;QACTG,MAAM;QACNC,MAAM;QACNK,UAASC,KAAa;YACrB,IAAI,CAACA,OAAO;gBACX,OAAO;YACR;YACA,OAAO;QACR;IACD;IACA,MAAML,UAAU,MAAM3B,SAAS4B,MAAM,CAACL;IACtC,OAAOI,QAAQZ,QAAQ;AACxB,EAAE;AAEF,wBAAwB,GACxB,OAAO,MAAMkB,iBAAiB,CAACJ;IAC9B,IAAI,CAACA,UAAU;QACd1B,OAAOiB,GAAG,CAAC;QACX,mDAAmD;QACnDf,QAAQ6B,IAAI,CAAC;IACd;IACA,OAAO;AACR,EAAE"}
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@node-cli/secret",
3
- "version": "1.0.7",
3
+ "version": "1.2.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "description": "Secret is a CLI tool that can encode or decode a file with a password",
7
7
  "type": "module",
8
8
  "types": "./dist/index.d.ts",
9
- "exports": "./dist/secret.js",
9
+ "exports": "./dist/lib.js",
10
10
  "bin": "dist/secret.js",
11
11
  "files": [
12
12
  "dist"
13
13
  ],
14
- "node": ">=16",
14
+ "node": ">=18",
15
15
  "scripts": {
16
16
  "build": "npm-run-all --serial clean build:types build:js build:barrel",
17
17
  "build:barrel": "barrelsby --delete --directory dist --pattern \"**/*.d.ts\" --name \"index.d\"",
@@ -21,6 +21,7 @@
21
21
  "lint": "biome lint src",
22
22
  "test": "cross-env-shell NODE_OPTIONS=--experimental-vm-modules TZ=UTC jest",
23
23
  "test:coverage": "npm run test -- --coverage",
24
+ "test:watch": "npm run test -- --watch",
24
25
  "watch": "swc --strip-leading-paths --watch --out-dir dist src"
25
26
  },
26
27
  "dependencies": {
@@ -32,5 +33,5 @@
32
33
  "publishConfig": {
33
34
  "access": "public"
34
35
  },
35
- "gitHead": "e511453b89f180c52ca60e10bd1dfb3c8675cbec"
36
+ "gitHead": "9afc4bbb67fd628806007b1b4c9be777ed881cc3"
36
37
  }