include/qrnsp_deniable.h
/*
* QR-NSP Volcanic Edition — Deniable Encryption
* Module 5: Multi-password KDF + Honey-Encryption
*
* Threat model:
* An adversary captures the device AND coerces the user to reveal
* the password ("rubber-hose cryptanalysis"). The user surrenders
* the DECOY password. The adversary decrypts and finds plausible
* but harmless data. The TRUE payload remains hidden.
*
* Architecture:
*
* ┌─────────────────────────────────────────────────────────┐
* │ Sealed Container │
* ├──────────┬──────────┬────────────┬──────────────────────┤
* │ SALT:32 │ DECOY_CT │ REAL_CT │ HONEY_PAD │
* │ │ (N bytes)│ (M bytes) │ (fills to capacity) │
* ├──────────┴──────────┴────────────┴──────────────────────┤
* │ Entire container looks like random bytes │
* └─────────────────────────────────────────────────────────┘
*
* Password A (decoy) → derives key_A → decrypts DECOY_CT → plausible data
* Password B (real) → derives key_B → decrypts REAL_CT → secret payload
* Any other password → derives key_X → honey-decrypt → valid-looking garbage
*
* Properties:
* 1. Without the correct password, you can't prove REAL_CT even EXISTS
* (it's indistinguishable from HONEY_PAD random bytes)
* 2. Decoy data is user-chosen (e.g., innocent photos, diary entries)
* 3. Honey-encryption ensures brute-force yields plausible plaintexts
* 4. Container size is fixed — no metadata leaks payload sizes
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#ifndef QRNSP_DENIABLE_H
#define QRNSP_DENIABLE_H
#include <stdint.h>
#include <stddef.h>
/* ─────────────────────────────────────────────
* Container parameters
* ───────────────────────────────────────────── */
#define DENY_SALT_BYTES 32
#define DENY_KEY_BYTES 32 /* AES-256 key */
#define DENY_NONCE_BYTES 12 /* AES-GCM nonce */
#define DENY_TAG_BYTES 16 /* AES-GCM auth tag */
#define DENY_KDF_ITERATIONS 100000 /* PBKDF2 iterations (tune for HW) */
/* Per-volume overhead: nonce(12) + length(4) + tag(16) = 32 bytes */
#define DENY_VOLUME_OVERHEAD (DENY_NONCE_BYTES + 4 + DENY_TAG_BYTES)
/* Container header: salt(32) + decoy_offset(4) + real_offset(4) = 40 bytes
* NOTE: offsets are encrypted under a header key derived from a third
* "structure password" to prevent offset metadata from revealing volume layout.
* Without the structure key, offsets are indistinguishable from random. */
#define DENY_HEADER_BYTES (DENY_SALT_BYTES + 8 + DENY_TAG_BYTES) /* 56 */
/* Default container sizes */
#define DENY_CONTAINER_4K 4096
#define DENY_CONTAINER_64K 65536
#define DENY_CONTAINER_1M 1048576
/* Maximum passwords supported */
#define DENY_MAX_PASSWORDS 4
/* ─────────────────────────────────────────────
* Honey-Encryption: Distribution-Transforming Encoder (DTE)
*
* Maps any 256-bit key to a plausible plaintext from a target
* distribution. When an adversary brute-forces the container,
* EVERY attempted key yields something that looks like real data.
*
* Supported honey distributions:
* ───────────────────────────────────────────── */
typedef enum {
HONEY_DIST_ENGLISH_TEXT, /* Markov-chain English prose */
HONEY_DIST_JSON, /* Valid JSON objects */
HONEY_DIST_BINARY, /* Random-looking binary (uniform) */
HONEY_DIST_CODE, /* Source code snippets */
HONEY_DIST_CSV, /* CSV data rows */
} honey_dist_t;
/* ─────────────────────────────────────────────
* Volume descriptor
* ───────────────────────────────────────────── */
typedef struct {
const char *password; /* Null-terminated password */
size_t password_len;
const uint8_t *plaintext; /* Data for this volume */
size_t plaintext_len;
honey_dist_t honey_type; /* Distribution for honey-encryption */
} deny_volume_t;
/* ─────────────────────────────────────────────
* Sealed container (opaque output)
* ───────────────────────────────────────────── */
typedef struct {
uint8_t *data; /* Container bytes (caller allocates) */
size_t capacity; /* Total container size */
size_t used; /* Bytes written so far */
} deny_container_t;
/* ─────────────────────────────────────────────
* Seal result
* ───────────────────────────────────────────── */
typedef enum {
DENY_OK = 0,
DENY_ERR_CAPACITY, /* Container too small for payloads */
DENY_ERR_TOO_MANY_VOLUMES, /* Exceeded MAX_PASSWORDS */
DENY_ERR_KDF, /* Key derivation failed */
DENY_ERR_ENCRYPT, /* AEAD encryption failed */
DENY_ERR_DECRYPT, /* AEAD decryption failed */
DENY_ERR_NO_VOLUME, /* Password doesn't match any volume */
DENY_ERR_HONEY, /* Honey decryption (still returns data)*/
} deny_result_t;
/* ─────────────────────────────────────────────
* API
* ───────────────────────────────────────────── */
/*
* Seal: create a deniable container with multiple volumes.
*
* volumes: array of volume descriptors (1 to MAX_PASSWORDS)
* n_volumes: number of volumes
* container: output container (caller must set .data and .capacity)
*
* The container is filled entirely — unused space is honey-encrypted
* random data, indistinguishable from real volumes.
*
* Returns DENY_OK on success.
*/
deny_result_t deny_seal(const deny_volume_t *volumes, int n_volumes,
deny_container_t *container);
/*
* Open: attempt to decrypt a volume from a container.
*
* container: sealed container
* password: password to try
* pass_len: password length
* out: output buffer for decrypted plaintext
* out_cap: output buffer capacity
* out_len: actual plaintext length (set on success)
*
* Returns:
* DENY_OK — valid volume decrypted
* DENY_ERR_HONEY — no matching volume; honey-decryption returned
* plausible-looking data in out[] (this is BY DESIGN)
*
* CRITICAL: The caller CANNOT distinguish DENY_OK from DENY_ERR_HONEY
* without knowing the true password. This is the deniability property.
* We return different codes only for the API user who knows the truth.
*/
deny_result_t deny_open(const deny_container_t *container,
const char *password, size_t pass_len,
uint8_t *out, size_t out_cap,
size_t *out_len);
/*
* Probe: check if a password matches any volume (without full decrypt).
* Returns 1 if a volume's authentication tag validates, 0 otherwise.
*
* WARNING: timing side-channel — use only when the adversary is not
* watching. For coercion scenarios, always use deny_open() which
* always returns data regardless of password validity.
*/
int deny_probe(const deny_container_t *container,
const char *password, size_t pass_len);
/*
* Compute required container size for given volumes.
*/
size_t deny_required_size(const deny_volume_t *volumes, int n_volumes);
#endif /* QRNSP_DENIABLE_H */