- Generate 3168‑byte secret key (
secretKey.qkey) & 1568-byte public key (publicKey.qkey) for ML-KEM-1024 post-quantum key encapsulation algorithm. - Encrypt client-side arbitrary files using hybrid cryptography. In this approach, the ML-KEM-1024 securely negotiates a symmetric key between the sides, which is then used by AES-256-GCM to directly encrypt the file data.
- Decrypt
.qenccontainers created by this tool. - Split
.qenccryptocontainers with.qkeyprivate keys into multiple.qcontshards. You choose total shards n and RS data k; threshold t is computed ast = k + (n-k)/2. With fewer than t shards, no information about the original secret can be retrieved. - Restore a
.qenccontainer and the private.qkeyfrom a sufficient number of.qcontshards (>= t). - Verifies file integrity using SHA3-512 hash sum and provides process logs to track operations.
- All cryptographic operations are performed directly in the client's browser, ensuring the confidentiality of user data.
The risk of cryptographic obsolescence is a matter of concern. It is anticipated that classical asymmetric schemes (RSA, ECC) will become vulnerable once sufficiently large fault-tolerant quantum computers are developed. This process gives rise to two distinct risks associated with long-lived data:
- It can be argued that the present is an opportune moment to harvest encrypted data, as this may be decrypted in the future when PQ capabilities are extant.
- In the context of standards transition, it is anticipated that standards bodies will ultimately necessitate or advocate for the utilisation of post-quantum algorithms in novel systems and for archival data. Systems that do not employ PQC will encounter compatibility, compliance and migration expenses.
High-value data must be made available with a high degree of reliability and protection against single-point failures. Distributed threshold storage (Shamir shares across independent storage providers) has been shown to address availability and improve censorship resistance, but it has also been demonstrated to introduce metadata and integrity challenges.
It is imperative that users generate, back up, shard and restore cryptographic containers with minimal chance of loss or misconfiguration.
Threat model (concise):
- Adversary goals: confidentiality breach of stored files now or later; integrity forgeries; denial of recovery by withholding shares.
- Adversary capabilities: network observers, storage provider compromise, passive archive collection (future decryption), host compromise (user device), malicious browser extension, supply-chain compromise of third-party libs.
- Assumptions: attacker cannot simultaneously control threshold number of independent share custodians, user device may be compromised (lossy trust), users will use multiple independent storage locations for shares.
- Post-quantum confidentiality: Ensure that container data confidentiality resists known quantum attacks by using a lattice-based KEM (ML-KEM-1024) to agree symmetric keys and KMAC256 + AES-256-GCM hybrid encryption for payloads.
- Durable distributed storage: Enable secure splitting and reconstruction that provide configurable threshold/availability guarantees without leaking information below threshold.
- Zero-trust server model: All sensitive cryptographic operations and secrets must remain inside the user’s browser; no private material is uploaded or retained by any server.
- Integrity and provenance: Provide robust content integrity checks (SHA3-512) and authenticated metadata so users can detect tampering and identify container format and parameter versions.
- Usability & recoverability: Provide clear UX flows and automation for key generation, secure backups, shard distribution.
- Align algorithms and parameters with authoritative recommendations & auditability.
index.html # Main HTML file
style.css # Main CSS styles file
package.json # Dependencies, downloadable libraries
public/third-party/erasure.js # ErasureCodes library
src/
├── main.js # Application entry point
├── utils.js # Consolidated utility functions
├── core/
├── crypto/ # Core cryptographic modules
│ ├── index.js # Main crypto orchestration
│ ├── constants.js # Storage of permanent variables
│ ├── mlkem.js # ML-KEM-1024 implementation
│ ├── aes.js # AES-256-GCM + KMAC256
│ ├── entropy.js # Enhanced entropy collection
│ ├── qcont/ # .qcont format handling
│ │ ├── build.js # Shard building
│ │ └── restore.js # Shard restoration
│ └── splitting/ # Secret/data splitting
│ └── sss.js # Shamir Secret Sharing
└── features/ # Application features
├── lite-mode.js # Simplified interface
└── ui/ # User interface modules
├── ui.js # Pro mode interface
└── logging.js # Consistent logging
- Post-quantum Module-Lattice-based Key Encapsulation Mechanism: ML-KEM-1024. Used to encapsulate/decapsulate a shared secret.
- Key derivation function: KMAC256. Used to derive the AES encryption key and AES IV's from the shared secret.
- Authenticated Encryption with Associated Data: AES-256-GCM. Used to encrypt the file payload and authenticate the header additional authenticated data (AAD).
- Secret sharing algorithm: Shamir's secret sharing. Used to shard and reconstruct private keys files.
- File sharing algorithm: Reed-Solomon codes. Used to shard and reconstruct containers files.
graph TB
subgraph "🔐 Cryptographic Flow"
I["🔑 ML-KEM-1024 Key Generation"] --> J["🛡 ML-KEM Encapsulation"]
J --> K["🌊 KMAC256 Key Derivation"]
K --> L["🔐 AES-256-GCM Encryption<br/>(Per-chunk or single)"]
H["📂 User Data"] --> L
L --> M["🔀 Shamir Secret Sharing<br/>(Private key)"]
L --> N["📦 Reed-Solomon Encoding<br/>(Encrypted data)"]
M --> O["📄 .qcont Shards"]
N --> O
O --> P["🌍 Distributed Storage"]
end
style H fill:#ffebee
style I fill:#e1f5fe
style J fill:#e8f5e8
style K fill:#f1f8e9
style L fill:#fff3e0
style M fill:#fce4ec
style N fill:#e0f2f1
style O fill:#fff8e1
style P fill:#ffebee
.qencfile format (one file is encrypted container - single-stream or per-chunk AEAD)
| Data | Length | Description |
|---|---|---|
| MAGIC | 4 bytes | ASCII QVv1 |
| keyLen | 4 bytes (Uint32 BE) | length of encapsulatedKey |
| encapsulatedKey | keyLen bytes | ML‑KEM ciphertext |
| containerNonce | 12 bytes | AES‑GCM IV (single-container mode) |
| kdfSalt | 16 bytes | random salt for KMAC |
| metaLen | 2 bytes (Uint16 BE) | length of metaJSON |
| metaJSON | metaLen bytes UTF‑8 | JSON metadata |
| ciphertext | remaining bytes | AES‑GCM ciphertext (single or concatenation) |
.qenc metaJSON (indicative):
{
"KEM":"ML-KEM-1024",
"KDF":"KMAC256",
"AEAD":"AES-256-GCM",
"fmt":"QVv1-3-0",
"aead_mode":"single-container-aead | per-chunk-aead",
"iv_strategy":"single-iv | kmac-derive-v1",
"timestamp":"<ISO8601 time>",
"fileHash":"<SHA3-512 hex of original file>",
"originalLength": 123,
"chunkSize": 8388608,
"chunkCount": 1,
"domainStrings": { "kdf":"quantum-vault:kdf:v1", "iv":"quantum-vault:chunk-iv:v1" }
}.qcontcomposite shard file format (one file contains part of Shamir's splited key + part of RS fragment)
| Data | Length | Description |
|---|---|---|
| MAGIC_SHARD | 4 bytes | ASCII QVC1 |
| metaLen | 2 bytes (Uint16 BE) | |
| metaJSON | metaLen bytes (UTF‑8) | RS params, counts, hashes, etc. |
| encapBlobLen | 4 bytes (Uint32 BE) | |
| encapBlob | encapBlobLen bytes | ML‑KEM ciphertext |
| containerNonce | 12 bytes | from .qenc header |
| kdfSalt | 16 bytes | from .qenc header |
| qencMetaLen | 2 bytes (Uint16 BE) | |
| qencMetaBytes | qencMetaLen bytes (UTF‑8) | original .qenc metadata (dup for convenience) |
| shardIndex | 2 bytes (Uint16 BE) | 0‑based index (0..n‑1) |
| shareLen | 2 bytes (Uint16 BE) | |
| shareBytes | shareLen bytes | one Shamir share |
| fragments stream | … | concatenation of per‑chunk fragments for this shard; |
| each fragment is stored as `[len32 |
.qcont metaJSON (indicative):
{
"containerId":"<SHA3-512 hex of .qenc header>",
"alg":{"KEM":"ML-KEM-1024","KDF":"KMAC256","AEAD":"AES-256-GCM","RS":"ErasureCodes","fmt":"QVqcont-1"},
"aead_mode":"single-container | per-chunk",
"iv_strategy":"single-iv | kmac-derive-v1",
"n":5,"k":3,"m":2,"t":4,
"chunkSize":8388608,
"chunkCount":1,
"containerHash":"<SHA3-512 hex of .qenc file>",
"encapBlobHash":"<SHA3-512 hex of encapsulated blob>",
"privateKeyHash":"<SHA3-512 hex of secretKey.qkey file>",
"originalLength":123,
"ciphertextLength":456,
"domainStrings":{"kdf":"quantum-vault:kdf:v1","iv":"quantum-vault:iv:v1"},
"fragmentFormat":"len32-prefixed",
"perFragmentSize":789,
"timestamp":"<ISO8601 time>"
}AAD:
- Single-container AEAD: entire header from MAGIC through
metaJSON. - Per-chunk AEAD:
AAD_i = header || uint32_be(chunkIndex) || uint32_be(plainLen_i).
Note: when aead_mode is per-chunk, the cipherPayload field usually contains a stream of encrypted chunks, but for convenience of splitting into .qcont, we store per-chunk ciphertext next to it in the qcont step. qenc.metaJSON stores chunkCount, chunk_size, perFragmentSize, and ciphertextLength (total length).
- Receiver generates ML‑KEM‑1024 key pair (public
publicKey.qkey1568 B, privatesecretKey.qkey3168 B). - Sender:
{encapsulatedKey, sharedSecret} = ml_kem1024.encapsulate(publicKey). - Generate
kdfSalt(16 B) andcontainerNonce(12 B). - Derive keys with KMAC256:
Kraw = KMAC256(sharedSecret, (kdfSalt || metaBytes), 32, customization = domainStrings.kdf)Kenc = KMAC256(Kraw, [1], 32, customization='quantum-vault:kenc:v1')Kiv = KMAC256(Kraw, [2], 32, customization='quantum-vault:kiv:v1')- Import
Kencas AES‑GCM key. Per‑chunk IV_i = first 12 bytes ofKMAC256(Kiv, (containerNonce || uint32_be(chunkIndex)), 16, customization=domainStrings.iv).
- Single-container AEAD (small files):
- Generate containerNonce (12B) random; call ciphertext = AES-GCM.encrypt(plaintext, iv=containerNonce, key=aesKey, AAD=headerBytes).
- Produce .qenc with ciphertext and metaJSON. Later split .qenc into shards via RS.
- Per-chunk AEAD (big files):
- Break plaintext into chunks of chunk_size. For each chunk index i compute per-chunk IV via Kiv as key to KMAC256 for IV derivation;
- Build header as specified above.
- For each cipherChunk_i, pad to multiple expected size and then call RS fragments = window.erasure.split(paddedChunk, k, allowedFailures). Append fragments[j] to shard j's fragBlob.
- Read container bytes and ensure length >= minimal header size.
- Parse header fields (MAGIC, keyLen, encapsulatedKey, iv, salt, metaLen, metaJson). Validate
metaLenandkeyLenbounds. - Validate metaJson KDF and KEM.
sharedSecret = ml_kem1024.decapsulate(encapsulatedKey, secretKey); normalize toUint8Array.- Derive AES key with
KMAC256(sharedSecret, salt || metaBytes, 32, { customization }). Import key. ZeroizederivedandsharedSecret. - Decrypt with AES-GCM providing
additionalData = header. If auth fails, raise an error (tampered container or wrong key). - Validate decrypted plaintext by computing
SHA3-512(plaintext)and comparing tometaJson.fileHash. If equal, accept; otherwise, warn about integrity mismatch.
- Split: take the binary container .qenc and run split(containerBytes, N, T) to produce N shards. Each shard is a binary file (.qshard).
- Combine: collect at least T shards and run combine(shards) to produce the original container bytes.
- Lattice-based key encapsulation mechanism, defined in FIPS-203. This algorithm, like other post-quantum algorithms, is designed to be resistant to attacks by quantum computers that could potentially break modern cryptosystems based on factorisation of large numbers or discrete logarithm, such as RSA and ECC. The ML-KEM-1024 provides Category 5 security level (roughly equivalent to AES-256) according to NIST guidelines.
- KMAC is a NIST-approved (SP 800-185) keyed algorithm based on KECCAK for MAC/PRF/KDF tasks. KMAC is considered a direct replacement for HMAC for the SHA-3 family.
- Using audited libraries for hashing and secret-sharing:
noble-hasheswas independently audited by Cure53, andshamir-secret-sharingwas audited by Cure53 and Zellic. Thenoble-post-quantumlibrary has not been independently audited at this time. - Using SHA3-512 for hash sums is in line with post-quantum security recommendations, as quantum computers can reduce hash cracking time from 2^n to 2^n/2 operations. Australian ASD prohibits SHA256 and similar hashes after 2030.
- There is no protection in JavaScript implementations of cryptographic algorithms against side-channel attacks. This is due to the way JIT compilers and rubbish collectors work in JavaScript environments, which makes achieving true runtime constancy extremely difficult. If an attacker can access application memory, they can potentially extract sensitive information.
- ML-KEM (Key Encapsulation Mechanism) does not check who sent the ciphertext. If you decrypt it with the wrong public key, it will simply return a different shared secret, not an error.
- Shamir's algorithm (SSS) provides information-theoretic security, which means that if there are less than a threshold number of shares, no information about the original secret can be obtained, regardless of computational power. Users need to independently ensure the reliability of storing each share.
- Inspired by diceslice and tidecoin.
npm install
npm run devnpm run deployThis project is distributed under the terms of the GNU General Public License v3.0. See the LICENSE file for the full text.
Browser encryption/decryption tool libraries:
- SHA3-512 for hashing and KMAC256 for KDF noble-hashes;
- ML-KEM-1024 for post-quantum key encapsulation used in combination with AES-256-GCM for symmetric file encryption noble-post-quantum;
- Shamir's secret sharing algorithm for splitting shamir-secret-sharing;
- Reed-Solomon erasure codes for splitting ErasureCodes.
The application incorporates the following dependencies that are released under the permissive MIT License and Apache License 2.0.
| Library | Version | Copyright holder | Upstream repository |
|---|---|---|---|
| shamir-secret-sharing | 0.0.4 | Privy | https://github.com/privy-io/shamir-secret-sharing |
| noble-post-quantum | 0.5.1 | Paul Miller | https://github.com/paulmillr/noble-post-quantum |
| noble-hashes | 2.0.0 | Paul Miller | https://github.com/paulmillr/noble-hashes |
| ErasureCodes | 9f937b9 | Dr Ian Preston | https://github.com/ianopolous/ErasureCodes |