quantumsure-qstore 0.0.0-stage → 0.1.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 +13 -2
- package/dist/index.d.mts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +124 -0
- package/dist/index.mjs +96 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# quantumsure-qstore
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Quantum-resistant field-level encryption for client data.
|
|
4
|
+
|
|
5
|
+
Encrypt sensitive fields **before** they are written to your database.
|
|
6
|
+
Decrypt them only when you need the plaintext.
|
|
7
|
+
The plaintext never leaves your environment.
|
|
8
|
+
|
|
9
|
+
Powered by **ML-KEM-1024** (Kyber) + **ChaCha20-Poly1305**.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install quantumsure-qstore
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypt plaintext with a recipient's ML-KEM-1024 public key.
|
|
3
|
+
*
|
|
4
|
+
* Format of the returned blob:
|
|
5
|
+
* kemCiphertext : nonce : ciphertext : authTag
|
|
6
|
+
* (all parts base64, separated by `:`)
|
|
7
|
+
*
|
|
8
|
+
* @param plaintext - The field value to protect
|
|
9
|
+
* @param publicKeyB64 - Recipient's public key (base64)
|
|
10
|
+
* @returns Encrypted blob that is safe to store in any database
|
|
11
|
+
*/
|
|
12
|
+
declare function qstoreEncrypt(plaintext: string, publicKeyB64: string): Promise<string>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Decrypt a blob previously produced by qstoreEncrypt.
|
|
16
|
+
*
|
|
17
|
+
* @param encryptedBlob - The string returned by qstoreEncrypt
|
|
18
|
+
* @param privateKeyB64 - The corresponding ML-KEM-1024 private key (base64)
|
|
19
|
+
* @returns Original plaintext
|
|
20
|
+
*/
|
|
21
|
+
declare function qstoreDecrypt(encryptedBlob: string, privateKeyB64: string): Promise<string>;
|
|
22
|
+
|
|
23
|
+
export { qstoreDecrypt, qstoreEncrypt };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypt plaintext with a recipient's ML-KEM-1024 public key.
|
|
3
|
+
*
|
|
4
|
+
* Format of the returned blob:
|
|
5
|
+
* kemCiphertext : nonce : ciphertext : authTag
|
|
6
|
+
* (all parts base64, separated by `:`)
|
|
7
|
+
*
|
|
8
|
+
* @param plaintext - The field value to protect
|
|
9
|
+
* @param publicKeyB64 - Recipient's public key (base64)
|
|
10
|
+
* @returns Encrypted blob that is safe to store in any database
|
|
11
|
+
*/
|
|
12
|
+
declare function qstoreEncrypt(plaintext: string, publicKeyB64: string): Promise<string>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Decrypt a blob previously produced by qstoreEncrypt.
|
|
16
|
+
*
|
|
17
|
+
* @param encryptedBlob - The string returned by qstoreEncrypt
|
|
18
|
+
* @param privateKeyB64 - The corresponding ML-KEM-1024 private key (base64)
|
|
19
|
+
* @returns Original plaintext
|
|
20
|
+
*/
|
|
21
|
+
declare function qstoreDecrypt(encryptedBlob: string, privateKeyB64: string): Promise<string>;
|
|
22
|
+
|
|
23
|
+
export { qstoreDecrypt, qstoreEncrypt };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
qstoreDecrypt: () => qstoreDecrypt,
|
|
24
|
+
qstoreEncrypt: () => qstoreEncrypt
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/encrypt.ts
|
|
29
|
+
var import_mlkem = require("mlkem");
|
|
30
|
+
var import_chacha = require("@noble/ciphers/chacha.js");
|
|
31
|
+
|
|
32
|
+
// src/utils.ts
|
|
33
|
+
var import_buffer = require("buffer");
|
|
34
|
+
function randomBytes(length) {
|
|
35
|
+
const array = new Uint8Array(length);
|
|
36
|
+
crypto.getRandomValues(array);
|
|
37
|
+
return import_buffer.Buffer.from(array);
|
|
38
|
+
}
|
|
39
|
+
async function computeMac(data, key) {
|
|
40
|
+
const dataBytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
41
|
+
const keyBytes = typeof key === "string" ? new TextEncoder().encode(key) : key;
|
|
42
|
+
const combined = import_buffer.Buffer.concat([
|
|
43
|
+
import_buffer.Buffer.from(dataBytes),
|
|
44
|
+
import_buffer.Buffer.from(keyBytes)
|
|
45
|
+
]);
|
|
46
|
+
const hash = await crypto.subtle.digest("SHA-256", combined);
|
|
47
|
+
return import_buffer.Buffer.from(hash);
|
|
48
|
+
}
|
|
49
|
+
function toBase64(data) {
|
|
50
|
+
return import_buffer.Buffer.from(data).toString("base64");
|
|
51
|
+
}
|
|
52
|
+
function fromBase64(b64) {
|
|
53
|
+
return import_buffer.Buffer.from(b64, "base64");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/encrypt.ts
|
|
57
|
+
async function qstoreEncrypt(plaintext, publicKeyB64) {
|
|
58
|
+
if (!plaintext && plaintext !== "") {
|
|
59
|
+
throw new Error("qstoreEncrypt: plaintext is required");
|
|
60
|
+
}
|
|
61
|
+
if (!publicKeyB64) {
|
|
62
|
+
throw new Error("qstoreEncrypt: publicKeyB64 is required");
|
|
63
|
+
}
|
|
64
|
+
const publicKey = fromBase64(publicKeyB64);
|
|
65
|
+
const kem = new import_mlkem.MlKem1024();
|
|
66
|
+
const [kemCiphertext, sharedSecret] = await kem.encap(publicKey);
|
|
67
|
+
const nonce = randomBytes(12);
|
|
68
|
+
const dataBytes = new TextEncoder().encode(plaintext);
|
|
69
|
+
const chacha = (0, import_chacha.chacha20poly1305)(sharedSecret, nonce);
|
|
70
|
+
const encrypted = chacha.encrypt(dataBytes);
|
|
71
|
+
const combinedForMac = new TextEncoder().encode(
|
|
72
|
+
`${toBase64(nonce)}${toBase64(encrypted)}`
|
|
73
|
+
);
|
|
74
|
+
const authTag = await computeMac(combinedForMac, sharedSecret);
|
|
75
|
+
return [
|
|
76
|
+
toBase64(kemCiphertext),
|
|
77
|
+
toBase64(nonce),
|
|
78
|
+
toBase64(encrypted),
|
|
79
|
+
toBase64(authTag)
|
|
80
|
+
].join(":");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/decrypt.ts
|
|
84
|
+
var import_mlkem2 = require("mlkem");
|
|
85
|
+
var import_chacha2 = require("@noble/ciphers/chacha.js");
|
|
86
|
+
async function qstoreDecrypt(encryptedBlob, privateKeyB64) {
|
|
87
|
+
if (!encryptedBlob) {
|
|
88
|
+
throw new Error("qstoreDecrypt: encryptedBlob is required");
|
|
89
|
+
}
|
|
90
|
+
if (!privateKeyB64) {
|
|
91
|
+
throw new Error("qstoreDecrypt: privateKeyB64 is required");
|
|
92
|
+
}
|
|
93
|
+
const parts = encryptedBlob.split(":");
|
|
94
|
+
if (parts.length !== 4) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
"qstoreDecrypt: invalid encrypted blob format (expected 4 parts)"
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const [kemCiphertextB64, nonceB64, encryptedB64, authTagB64] = parts;
|
|
100
|
+
const privateKey = fromBase64(privateKeyB64);
|
|
101
|
+
const kemCiphertext = fromBase64(kemCiphertextB64);
|
|
102
|
+
const nonce = fromBase64(nonceB64);
|
|
103
|
+
const encrypted = fromBase64(encryptedB64);
|
|
104
|
+
const authTag = fromBase64(authTagB64);
|
|
105
|
+
const kem = new import_mlkem2.MlKem1024();
|
|
106
|
+
const sharedSecret = await kem.decap(kemCiphertext, privateKey);
|
|
107
|
+
const combinedForMac = new TextEncoder().encode(
|
|
108
|
+
`${nonceB64}${encryptedB64}`
|
|
109
|
+
);
|
|
110
|
+
const computedMac = await computeMac(combinedForMac, sharedSecret);
|
|
111
|
+
if (!computedMac.equals(authTag)) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"qstoreDecrypt: invalid MAC \u2013 data may have been tampered with"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
const chacha = (0, import_chacha2.chacha20poly1305)(sharedSecret, nonce);
|
|
117
|
+
const decrypted = chacha.decrypt(encrypted);
|
|
118
|
+
return new TextDecoder().decode(decrypted);
|
|
119
|
+
}
|
|
120
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
121
|
+
0 && (module.exports = {
|
|
122
|
+
qstoreDecrypt,
|
|
123
|
+
qstoreEncrypt
|
|
124
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/encrypt.ts
|
|
2
|
+
import { MlKem1024 } from "mlkem";
|
|
3
|
+
import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
|
|
4
|
+
|
|
5
|
+
// src/utils.ts
|
|
6
|
+
import { Buffer } from "buffer";
|
|
7
|
+
function randomBytes(length) {
|
|
8
|
+
const array = new Uint8Array(length);
|
|
9
|
+
crypto.getRandomValues(array);
|
|
10
|
+
return Buffer.from(array);
|
|
11
|
+
}
|
|
12
|
+
async function computeMac(data, key) {
|
|
13
|
+
const dataBytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
14
|
+
const keyBytes = typeof key === "string" ? new TextEncoder().encode(key) : key;
|
|
15
|
+
const combined = Buffer.concat([
|
|
16
|
+
Buffer.from(dataBytes),
|
|
17
|
+
Buffer.from(keyBytes)
|
|
18
|
+
]);
|
|
19
|
+
const hash = await crypto.subtle.digest("SHA-256", combined);
|
|
20
|
+
return Buffer.from(hash);
|
|
21
|
+
}
|
|
22
|
+
function toBase64(data) {
|
|
23
|
+
return Buffer.from(data).toString("base64");
|
|
24
|
+
}
|
|
25
|
+
function fromBase64(b64) {
|
|
26
|
+
return Buffer.from(b64, "base64");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// src/encrypt.ts
|
|
30
|
+
async function qstoreEncrypt(plaintext, publicKeyB64) {
|
|
31
|
+
if (!plaintext && plaintext !== "") {
|
|
32
|
+
throw new Error("qstoreEncrypt: plaintext is required");
|
|
33
|
+
}
|
|
34
|
+
if (!publicKeyB64) {
|
|
35
|
+
throw new Error("qstoreEncrypt: publicKeyB64 is required");
|
|
36
|
+
}
|
|
37
|
+
const publicKey = fromBase64(publicKeyB64);
|
|
38
|
+
const kem = new MlKem1024();
|
|
39
|
+
const [kemCiphertext, sharedSecret] = await kem.encap(publicKey);
|
|
40
|
+
const nonce = randomBytes(12);
|
|
41
|
+
const dataBytes = new TextEncoder().encode(plaintext);
|
|
42
|
+
const chacha = chacha20poly1305(sharedSecret, nonce);
|
|
43
|
+
const encrypted = chacha.encrypt(dataBytes);
|
|
44
|
+
const combinedForMac = new TextEncoder().encode(
|
|
45
|
+
`${toBase64(nonce)}${toBase64(encrypted)}`
|
|
46
|
+
);
|
|
47
|
+
const authTag = await computeMac(combinedForMac, sharedSecret);
|
|
48
|
+
return [
|
|
49
|
+
toBase64(kemCiphertext),
|
|
50
|
+
toBase64(nonce),
|
|
51
|
+
toBase64(encrypted),
|
|
52
|
+
toBase64(authTag)
|
|
53
|
+
].join(":");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/decrypt.ts
|
|
57
|
+
import { MlKem1024 as MlKem10242 } from "mlkem";
|
|
58
|
+
import { chacha20poly1305 as chacha20poly13052 } from "@noble/ciphers/chacha.js";
|
|
59
|
+
async function qstoreDecrypt(encryptedBlob, privateKeyB64) {
|
|
60
|
+
if (!encryptedBlob) {
|
|
61
|
+
throw new Error("qstoreDecrypt: encryptedBlob is required");
|
|
62
|
+
}
|
|
63
|
+
if (!privateKeyB64) {
|
|
64
|
+
throw new Error("qstoreDecrypt: privateKeyB64 is required");
|
|
65
|
+
}
|
|
66
|
+
const parts = encryptedBlob.split(":");
|
|
67
|
+
if (parts.length !== 4) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"qstoreDecrypt: invalid encrypted blob format (expected 4 parts)"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const [kemCiphertextB64, nonceB64, encryptedB64, authTagB64] = parts;
|
|
73
|
+
const privateKey = fromBase64(privateKeyB64);
|
|
74
|
+
const kemCiphertext = fromBase64(kemCiphertextB64);
|
|
75
|
+
const nonce = fromBase64(nonceB64);
|
|
76
|
+
const encrypted = fromBase64(encryptedB64);
|
|
77
|
+
const authTag = fromBase64(authTagB64);
|
|
78
|
+
const kem = new MlKem10242();
|
|
79
|
+
const sharedSecret = await kem.decap(kemCiphertext, privateKey);
|
|
80
|
+
const combinedForMac = new TextEncoder().encode(
|
|
81
|
+
`${nonceB64}${encryptedB64}`
|
|
82
|
+
);
|
|
83
|
+
const computedMac = await computeMac(combinedForMac, sharedSecret);
|
|
84
|
+
if (!computedMac.equals(authTag)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"qstoreDecrypt: invalid MAC \u2013 data may have been tampered with"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const chacha = chacha20poly13052(sharedSecret, nonce);
|
|
90
|
+
const decrypted = chacha.decrypt(encrypted);
|
|
91
|
+
return new TextDecoder().decode(decrypted);
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
qstoreDecrypt,
|
|
95
|
+
qstoreEncrypt
|
|
96
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quantumsure-qstore",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Quantum-resistant field-level encryption for client data. Encrypt before storage, decrypt on read — plaintext never leaves your environment.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
19
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"quantum",
|
|
24
|
+
"post-quantum",
|
|
25
|
+
"encryption",
|
|
26
|
+
"field-level",
|
|
27
|
+
"ml-kem",
|
|
28
|
+
"kyber",
|
|
29
|
+
"zero-knowledge",
|
|
30
|
+
"quantumsure"
|
|
31
|
+
],
|
|
32
|
+
"author": "QuantumSure",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"mlkem": "^2.3.1",
|
|
36
|
+
"@noble/ciphers": "^1.0.0",
|
|
37
|
+
"buffer": "^6.0.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"typescript": "^5.4.0",
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"@types/node": "^20.0.0"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"sideEffects": false
|
|
48
|
+
}
|