apiro-db 1.0.3 → 1.0.5

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,7 +2,7 @@
2
2
 
3
3
  A lightweight, zero-dependency, encrypted data store for Node.js.
4
4
 
5
- Secure Store provides a simple key-value database similar to Quick.db, while removing all third-party dependencies and keeping your data encrypted at rest using Node’s built-in cryptography.
5
+ No configuration. No secrets. Just import and use.
6
6
 
7
7
  ---
8
8
 
@@ -11,7 +11,8 @@ Secure Store provides a simple key-value database similar to Quick.db, while rem
11
11
  * Zero dependencies
12
12
  * Fully asynchronous API
13
13
  * AES-256-GCM encrypted storage
14
- * User-supplied encryption key
14
+ * Automatic key generation
15
+ * Machine-bound encryption
15
16
  * Human-unreadable data at rest
16
17
  * Simple, familiar database methods
17
18
 
@@ -28,11 +29,10 @@ npm install apiro-db
28
29
  ## Usage
29
30
 
30
31
  ```js
31
- const { SecureStore } = require("apiro-db");
32
+ import { SecureStore } from "apiro-db";
32
33
 
33
34
  const db = new SecureStore({
34
- file: "./data.db",
35
- secret: process.env.DB_SECRET
35
+ file: "./data.db", // Optional
36
36
  });
37
37
 
38
38
  await db.set("coins", 100);
package/lib/crypto.js CHANGED
@@ -2,40 +2,30 @@ const crypto = require("crypto");
2
2
 
3
3
  const ALGO = "aes-256-gcm";
4
4
  const IV_LENGTH = 12;
5
- const SALT_LENGTH = 16;
6
- const TAG_LENGTH = 16;
7
5
 
8
- function deriveKey(secret, salt) {
9
- return crypto.scryptSync(secret, salt, 32);
10
- }
11
-
12
- function encrypt(text, secret) {
6
+ function encrypt(text, key) {
13
7
  const iv = crypto.randomBytes(IV_LENGTH);
14
- const salt = crypto.randomBytes(SALT_LENGTH);
15
- const key = deriveKey(secret, salt);
16
-
17
8
  const cipher = crypto.createCipheriv(ALGO, key, iv);
9
+
18
10
  const encrypted = Buffer.concat([
19
11
  cipher.update(text, "utf8"),
20
12
  cipher.final()
21
13
  ]);
22
14
 
23
- const tag = cipher.getAuthTag();
24
- return Buffer.concat([salt, iv, tag, encrypted]).toString("base64");
15
+ return Buffer.concat([
16
+ iv,
17
+ cipher.getAuthTag(),
18
+ encrypted
19
+ ]).toString("base64");
25
20
  }
26
21
 
27
- function decrypt(payload, secret) {
22
+ function decrypt(payload, key) {
28
23
  const buffer = Buffer.from(payload, "base64");
29
24
 
30
- const salt = buffer.subarray(0, SALT_LENGTH);
31
- const iv = buffer.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
32
- const tag = buffer.subarray(
33
- SALT_LENGTH + IV_LENGTH,
34
- SALT_LENGTH + IV_LENGTH + TAG_LENGTH
35
- );
36
- const encrypted = buffer.subarray(SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
25
+ const iv = buffer.subarray(0, 12);
26
+ const tag = buffer.subarray(12, 28);
27
+ const encrypted = buffer.subarray(28);
37
28
 
38
- const key = deriveKey(secret, salt);
39
29
  const decipher = crypto.createDecipheriv(ALGO, key, iv);
40
30
  decipher.setAuthTag(tag);
41
31
 
@@ -0,0 +1,52 @@
1
+ const crypto = require("crypto");
2
+ const os = require("os");
3
+
4
+ const ALGO = "aes-256-gcm";
5
+
6
+ function getMachineKey() {
7
+ const fingerprint = [
8
+ os.hostname(),
9
+ os.platform(),
10
+ os.arch()
11
+ ].join("|");
12
+
13
+ return crypto.scryptSync(fingerprint, "secure-store", 32);
14
+ }
15
+
16
+ function encryptMasterKey(masterKey) {
17
+ const iv = crypto.randomBytes(12);
18
+ const key = getMachineKey();
19
+
20
+ const cipher = crypto.createCipheriv(ALGO, key, iv);
21
+ const encrypted = Buffer.concat([
22
+ cipher.update(masterKey),
23
+ cipher.final()
24
+ ]);
25
+
26
+ return {
27
+ iv: iv.toString("base64"),
28
+ tag: cipher.getAuthTag().toString("base64"),
29
+ data: encrypted.toString("base64")
30
+ };
31
+ }
32
+
33
+ function decryptMasterKey(payload) {
34
+ const key = getMachineKey();
35
+ const iv = Buffer.from(payload.iv, "base64");
36
+ const tag = Buffer.from(payload.tag, "base64");
37
+ const encrypted = Buffer.from(payload.data, "base64");
38
+
39
+ const decipher = crypto.createDecipheriv(ALGO, key, iv);
40
+ decipher.setAuthTag(tag);
41
+
42
+ return Buffer.concat([
43
+ decipher.update(encrypted),
44
+ decipher.final()
45
+ ]);
46
+ }
47
+
48
+ module.exports = {
49
+ generateMasterKey: () => crypto.randomBytes(32),
50
+ encryptMasterKey,
51
+ decryptMasterKey
52
+ };
package/lib/store.js CHANGED
@@ -1,34 +1,46 @@
1
+ const crypto = require("crypto");
1
2
  const { readFile, writeFile } = require("./file");
2
3
  const { encrypt, decrypt } = require("./crypto");
4
+ const { generateMasterKey, encryptMasterKey, decryptMasterKey } = require("./masterKey");
3
5
 
4
6
  class SecureStore {
5
7
  constructor(options = {}) {
6
- if (!options.secret) {
7
- throw new Error("SecureStore requires a secret key");
8
- }
9
-
10
- this.file = options.file || "./apiro.db";
11
- this.secret = options.secret;
8
+ this.file = options.file || "./secure.db";
12
9
  this.data = {};
13
- this.ready = this._load();
10
+ this.masterKey = null;
11
+ this.ready = this._init();
14
12
  }
15
13
 
16
- async _load() {
17
- const encrypted = await readFile(this.file);
18
- if (!encrypted) return;
14
+ async _init() {
15
+ const existing = await readFile(this.file);
16
+
17
+ if (!existing) {
18
+ this.masterKey = crypto.randomBytes(32);
19
+ const encryptedKey = encryptMasterKey(this.masterKey);
20
+
21
+ const payload = encrypt(JSON.stringify({}), this.masterKey);
22
+
23
+ await writeFile(this.file, JSON.stringify({
24
+ _meta: { key: encryptedKey },
25
+ payload
26
+ }));
19
27
 
20
- try {
21
- const decrypted = decrypt(encrypted, this.secret);
22
- this.data = JSON.parse(decrypted);
23
- } catch {
24
- throw new Error("Failed to decrypt data store. Invalid secret?");
28
+ return;
25
29
  }
30
+
31
+ const parsed = JSON.parse(existing);
32
+ this.masterKey = decryptMasterKey(parsed._meta.key);
33
+ const decrypted = decrypt(parsed.payload, this.masterKey);
34
+ this.data = JSON.parse(decrypted);
26
35
  }
27
36
 
28
37
  async _save() {
29
- const json = JSON.stringify(this.data);
30
- const encrypted = encrypt(json, this.secret);
31
- await writeFile(this.file, encrypted);
38
+ const payload = encrypt(JSON.stringify(this.data), this.masterKey);
39
+
40
+ await writeFile(this.file, JSON.stringify({
41
+ _meta: { key: encryptMasterKey(this.masterKey) },
42
+ payload
43
+ }));
32
44
  }
33
45
 
34
46
  async get(key) {
@@ -53,9 +65,7 @@ class SecureStore {
53
65
 
54
66
  async add(key, amount) {
55
67
  await this.ready;
56
- if (typeof this.data[key] !== "number") {
57
- this.data[key] = 0;
58
- }
68
+ if (typeof this.data[key] !== "number") this.data[key] = 0;
59
69
  this.data[key] += amount;
60
70
  await this._save();
61
71
  return this.data[key];
@@ -67,9 +77,7 @@ class SecureStore {
67
77
 
68
78
  async push(key, value) {
69
79
  await this.ready;
70
- if (!Array.isArray(this.data[key])) {
71
- this.data[key] = [];
72
- }
80
+ if (!Array.isArray(this.data[key])) this.data[key] = [];
73
81
  this.data[key].push(value);
74
82
  await this._save();
75
83
  return this.data[key];
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "apiro-db",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "A lightweight, zero-dependency, encrypted data store for Node.js.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
9
- "keywords": [],
9
+ "keywords": ["database", "storage", "encryption", "secure"],
10
10
  "author": "krispowers",
11
11
  "license": "ISC",
12
12
  "files": [