Multi-perspective review (reverse engineering, offensive/red-team, DevOps, forensics) and a practical self-audit playbook. Honest assessment, not marketing.
Please report security vulnerabilities privately — do not open a public issue or pull request for a security bug.
Use GitHub Security Advisories: go to the Security tab → "Report a vulnerability". This opens a private channel between you and the maintainer for coordinated disclosure; details stay confidential until a fix is ready.
When reporting, please include:
- the affected version / commit,
- a description of the issue and its impact,
- steps to reproduce (proof-of-concept if possible),
- any suggested fix or mitigation.
What to expect: DGKN is a single-author evaluation/learning project, so there is no formal SLA. The maintainer aims to acknowledge a report within a few days and will work with you on a fix and coordinated disclosure. Please allow reasonable time to address an issue before any public disclosure. There is currently no bug-bounty program — reports are handled on a best-effort, good-faith basis, and credit is gladly given in the advisory.
| Version | Supported |
|---|---|
| 7.x | ✅ (current evaluation preview) |
| < 7.0 | ❌ |
No software is unhackable, and any tool that claims to be is lying. What can be said honestly:
- The cryptographic core is well-designed: authenticated encryption on every layer (XChaCha20-Poly1305), a memory-hard KDF (Argon2id, 256 MiB), real RFC-6238 2FA, full header encryption, and constant-time secret comparisons. The math is sound and built on libsodium (audited) plus a small, test-vector-verified SHA-1/HMAC for TOTP.
- The implementation is hardened: bounds/overflow checks, guaranteed key wipe, RAM locking, ASLR/DEP/CFG/CET binary mitigations, anti-debug self-defense.
- But: it is Windows-only, new, single-author, and has not had an independent external audit. "Military/forensic grade" is an aspiration here, not a certification. Real military/government use requires formal evaluation (e.g. Common Criteria, FIPS 140-3 validated crypto modules) — DGKN does not have these.
Threat model where DGKN is strong: a stolen container file / lost laptop (data at rest). Threat model where ANY user-space tool is weak: a fully compromised, running OS (kernel malware, a live RAM capture while mounted, a hardware keylogger). No user-space encryption tool defends against an attacker who already owns the running machine.
What an RE analyst sees:
| Aspect | Status | Note |
|---|---|---|
| Symbols / function names | RE-friendly. Mitigation: strip symbols / build with /Brepro, ship release PDB-less. |
|
| Strings (error messages) | Reveal program logic but no secrets. Acceptable; could be obfuscated. | |
| Crypto constants | ✅ standard | XChaCha20/Argon2 params visible — by design (Kerckhoffs: security must not rely on hiding the algorithm). |
| Container format | ✅ no plaintext magic | Header is indistinguishable from random; an RE can't tell a .dgkn from random data without the key. |
| Anti-debug | ✅ present | IsDebuggerPresent + CheckRemoteDebuggerPresent → wipe+abort. Defeatable by a determined RE (patch the check), but raises the bar. |
Verdict: The format and crypto resist RE well (no key material or plaintext is recoverable from the binary). The logic is readable — which is fine, because security comes from the keys, not from secrecy of the code. Hardening opportunity: strip release symbols; consider control-flow obfuscation only if you have a specific threat that warrants it (usually not worth the maintenance cost).
Attack paths and how DGKN fares:
| Attack | Result |
|---|---|
| Brute-force the password offline | 🟢 Hard — Argon2id 256 MiB makes each guess expensive; GPU/ASIC-resistant. Strong-password policy enforced. |
| Tamper with a container (flip bytes) | 🟢 Detected — every byte flip in the header is rejected (Poly1305 + sentinel). Tested. |
| Feed a malformed/huge container to crash the app | 🟢 Handled — bounds checks, overflow-safe region math, allocation caps. Tested with fuzz/garbage inputs. |
Path-traversal via a crafted archive (../../) |
🟢 Blocked — archive entries validated, unpack sandboxed to target dir. Tested. |
| Online brute-force / repeated mount attempts | 🟢 Backoff + lockout, now persisted (HMAC-protected file per identity) — survives app restarts. Verified by test. |
| Timing side-channel on password/code | 🟢 Constant-time compares (sodium_memcmp) for sentinel, HMAC, TOTP. |
| Duress / coercion ("give me the password") | 🟢 Hidden volume for plausible deniability + real duress password: entering it is rejected exactly like a wrong password (no tell), and can optionally sanitize all headers (Argon2id-hashed, constant-time compare). Verified by test. |
| Malicious WinFsp mount interaction | 🟡 Mount runs in user context; review FUSE op bounds (done in code) — but a hostile process on the same desktop could read the mounted drive while open. Same limitation as VeraCrypt. |
Verdict: Strong against offline and malformed-input attacks. The realistic gaps are operational: lockout doesn't survive restart, no real duress password, and (like all such tools) zero protection once the volume is mounted on a compromised host.
| Aspect | Status | Hardening |
|---|---|---|
| Dependencies | libsodium, argon2, nlohmann-json, Qt6, WinFsp (all via vcpkg/official) | Pin exact versions; verify hashes; commit a vcpkg.json manifest with a baseline. |
| Reproducible build | 🟢 /Brepro enabled (compiler + linker) — strips timestamps from PE headers. |
Combined with the pinned vcpkg baseline (vcpkg.json), identical source + toolchain produce bit-identical binaries. |
| Dependency pinning | 🟢 vcpkg.json with builtin-baseline SHA |
Exact, reproducible dependency versions. |
| Code signing | 🟡 the released installer is Authenticode-signed with a self-signed cert (RFC-3161 timestamped); the signature proves integrity but the cert root is not publicly trusted, so other machines still show "Unknown Publisher". | Use a CA-issued (ideally EV) code-signing certificate for SmartScreen reputation on third-party machines. |
| CI / automated tests | 🟢 GitHub Actions (Windows) | The 68-test / 301-assertion Catch2 suite + 50k-input fuzz-stress + BinSkim run on every push (.github/workflows/ci.yml). |
| Secrets in repo | ✅ none | Runtime secret artifacts (dgkn_2fa_local.json, 2fa.json, **/security/2fa.json) are git-ignored and were never committed — verified absent from the tree and from the full git history. |
| SBOM | ❌ | Generate a Software Bill of Materials for auditability. |
Verdict: The build is solid but not yet verifiable by others. For "high-assurance", reproducible builds + code signing + CI are the missing pillars.
| Aspect | Status |
|---|---|
| Data at rest | 🟢 Full-container AEAD; header looks random; no plaintext magic to fingerprint. Measured Shannon entropy across a 500 MB container ≈ 7.999 bits/byte everywhere (8.0 = perfect random) — ciphertext and random padding are statistically indistinguishable from random, so a hidden volume's existence cannot be proven. |
| Keys in RAM | 🟢 VirtualLock (no swap) + sodium_memzero on unmount. |
| Plaintext on disk while mounted | 🟢 WinFsp in-RAM filesystem — no plaintext temp folder (unlike the old Python build / Cryptomator). |
| Password lifetime in RAM | 🟢 KDF input copies and our own std::string copies are wiped via secure_wipe_string (sodium_memzero); QLineEdit is cleared after use. Residual: Qt/QString may keep internal copies we can't reach — a custom secure widget would be needed for 100% coverage. |
.txn journal files |
🟢 HMAC-protected, no plaintext payload; cleaned after commit. |
| Crash residue | 🟡 If the process crashes while mounted, RAM contents (incl. keys) could be captured from a memory dump or hibernation file. Mitigation: disable crash dumps for the process; this is an OS-level concern. |
Verdict: Excellent for data at rest. The residual forensic risks are the classic ones for any running encryption tool: live RAM and the OS itself.
A pragmatic order for a single author to gain confidence — cheap/fast first:
# 1. All tests must pass (use the Argon2 test-override for speed)
$env:DGKN_ARGON2_TEST_KIB="64"
cmake --build build-rel; ctest --test-dir build-rel --output-on-failure
# 2. Crypto correctness is anchored to RFC test vectors (TOTP) and round-trip tests.- MSVC
/analyze(built-in static analyzer): add--analyzeto a CI build, fix warnings. - clang-tidy with
bugprone-*,cert-*,clang-analyzer-*checks. - PVS-Studio (free for open source) — catches integer/pointer/lifetime bugs.
- CodeQL (free on GitHub) — query for crypto misuse and taint flows.
- Address/UB Sanitizer build (clang on Windows or a Linux core-only build): run the test suite under ASan+UBSan to catch memory/overflow bugs the fuzz tests don't.
- libFuzzer/AFL++ on the parsers: feed random bytes to
decrypt_and_verify_header,Archive::parse_structure, and the payload parser. These are your highest-risk attack surface (attacker-controlled input). - Application Verifier (Windows) on
dgkn_gui.exeto catch heap/handle misuse.
- Test vectors: you already verify TOTP against RFC 6238. Add known-answer tests (KAT) for XChaCha20-Poly1305 and Argon2id against the libsodium/RFC vectors.
- Nonce-uniqueness proof: assert (already tested) that sector nonces never repeat across index × epoch.
dieharder/enton raw container bytes to confirm ciphertext is statistically random.
- BinSkim (Microsoft) — verifies ASLR/DEP/CFG/CET are actually set in the binary
(you've confirmed this manually with
dumpbin; BinSkim automates it in CI). dumpbin /headers /loadconfig— spot-check mitigations per release.- Pin and hash-verify all vcpkg dependencies; aim for a reproducible build.
- Have a second cryptographer/security engineer read the format + KDF + nonce design.
- For genuine military/government acceptance: formal evaluation (Common Criteria EAL, FIPS 140-3 validated crypto). This is a months-long, paid process — set expectations.
- ✅ Reproducible build (
/Brepro) + dependency pinning (vcpkg.jsonbaseline) + Authenticode-signed release binaries. Remaining: external audit for "high assurance". - ✅ Password wiping —
secure_wipe_stringon all our copies + KDF inputs;QLineEditcleared. (Residual: Qt-internal copies — see note above.) - ✅ Persistent lockout — HMAC-protected, survives restarts. Tested.
- ✅ Real duress/emergency password — silent rejection + optional header sanitization. Tested.
- ✅ Fuzz/stress harness —
dgkn_fuzzruns 50k random inputs through the parsers with zero crashes; a clang/libFuzzer path is documented. Now wired into CI (.github/workflows/ci.yml). - ✅ Optional TPM 2.0 binding — real TBS availability check (
Tbsi_GetDeviceInfo, tpmVersion==2) + machine-bound sealing via DPAPI/LocalMachine with PCR-policy entropy. Sealed secrets don't transfer to another machine. Tested. GUI scope note (v7): the TPM-sealed-secret and device-binding parameters are fully implemented inContainerManager(binding_digest,derive_volume_key) and exercised bytests/test_tpm.cpp, but the v7 GUI callscreate_container/mount_volumewithout these arguments — so the defaults (tpm_sealed_secret = "",bind_to_device = false) apply and containers created via the GUI are bound by password + 2FA only. The TPM/device factors are wired in the core but not yet exposed as a GUI toggle. - ⬜ Independent external audit — the only thing that justifies "high-assurance" claims. (Cannot be done in-house — requires a third party.)
.github/workflows/ci.yml runs on every push: hardened build → full Catch2 suite →
50k-input fuzz stress → BinSkim mitigation verification. This gives continuous,
reproducible assurance.
Rather than hand-crafting raw TPM 2.0 command byte-streams (hundreds of error-prone lines,
untestable without a physical TPM), DGKN binds secrets to the machine via DPAPI with the
LOCAL_MACHINE scope. On TPM-equipped systems Windows backs the DPAPI master-key chain with
hardware-bound keys, so the sealed blob is effectively device-bound. The presence of a real
TPM 2.0 is reported separately (is_tpm_native_available). For a future hardware-only seal
(no DPAPI), raw TBS command sealing would be the next step.
- Auto-lock (10 min idle): a global event filter tracks input; on timeout the app calls
emergency_wipe— mounts closed, keys zeroed. Defends a walked-away-from session. - Clipboard auto-clear: copied TOTP secrets are wiped from the clipboard after 15 s (only if the clipboard still holds our value), limiting clipboard-sniffing exposure.
- Tamper-evident audit log (
core::AuditLog): HMAC-SHA256 hash-chain. Each entry signs the previous one, so any modification, insertion or deletion breaks the chain.verify()reports the first broken line; the GUI warns on startup if the log was tampered with. Tested (edit/delete detection).- Key derivation (hardened, F-D 2026-06-01): the HMAC key is
SHA256(constant ‖ MachineGuid ‖ per-install-random-secret). The 32-byte random secret is generated on first use and stored ACL-restricted in<logpath>.key(current user + SYSTEM + Admins). Previously the key was derivable from the binary + MachineGuid alone; now an attacker must additionally read the protected.keyfile to forge the log. - Honest limit: this remains tamper-evident, not tamper-proof. An attacker who can
read both the binary and the
.keyfile on the same machine (e.g. with the user's own privileges or root) can recompute the chain. Real tamper-proofing would need a TPM/HSM-sealed key or remote log shipping (out of scope for a local, offline tool). The log defends against accidental edits and casual after-the-fact tampering — not against a fully privileged on-host attacker.
- Key derivation (hardened, F-D 2026-06-01): the HMAC key is
- No plaintext secrets on disk: the legacy
dgkn_2fa_local.json(which stored a TOTP secret in clear text) has been removed and git-ignored. The App-Login 2FA secret is now persisted only encrypted (XChaCha20-Poly1305, key via Argon2id from a separate master password) in%LOCALAPPDATA%\DGKN\security\2fa.json; per-operation container secrets are still entered each time and wiped from RAM after use. See "2FA secret store" below. - Audit log location: moved from world-readable
%TEMP%to the per-user%APPDATA%\DGKN@Labs\DGKN Crypto Suite\(not auto-cleaned, user-scoped ACL). - App manifest (
gui/dgkn.manifest, embedded):heapType=SegmentHeap(hardened heap backend), per-monitor-v2 DPI awareness,asInvokerexecution level (no silent elevation; run elevated only when TPM/CPU-temp readout is desired). Verified embedded viamt.exe.
The App-Login TOTP secret is stored in %LOCALAPPDATA%\DGKN\security\2fa.json, encrypted
with XChaCha20-Poly1305; the key is derived via Argon2id (256 MiB, hardened) from a separate
master password (≥16 chars, ≥3 character classes — enforced via validate_password_strength).
This combination is quantum-resistant: XChaCha20 (256-bit) and Argon2id remain practical
under Grover. A dedicated post-quantum layer (ML-KEM/Kyber) would add no real benefit here —
there is no asymmetric key exchange — and is deliberately omitted. The file is restricted via
a Windows ACL to the current user (+SYSTEM/Admins) and written atomically (temp + replace).
Recovery codes: 10 one-time codes are generated at setup. Each is stored only as an
Argon2id hash with its own 16-byte salt — the plaintext codes leave the app exactly once (the
one-time display) and are never persisted. A code substitutes once for the TOTP code at login;
used codes are marked and rejected on reuse. Verification runs in constant time (sodium_memcmp,
no early exit). verify_password / regenerate_recovery_codes confirm the master password
without ever materialising the decrypted secret to the caller.
Known residual weakness (GUI input): passwords and secrets are entered through Qt widgets
(QLineEdit/QString). Active buffers are best-effort overwritten immediately after use
(wipeQString, secure_wipe_string, sodium_mlock for derived keys). Qt can nonetheless leave
copies at old heap addresses via implicit sharing / reallocations that we cannot reliably reach.
Full control would require a non-Qt input path (not implemented). This is stated honestly rather
than hidden.
A full white-box read of the security-critical modules (CryptoUtils, Crypto, Manager, Archive, Totp, TwoFactorStore, TPMUtils, AuditLog, SecureMemory, FsHelpers, VirtualVolume). No cryptographic break and no memory-corruption/RCE bug found. Findings and their resolution:
| ID | Severity | Finding | Resolution |
|---|---|---|---|
| F-A | info | Totp::code dynamic-truncation offset dig[off..off+3] — confirmed always in-bounds (off ≤ 15, buffer 20 B). |
No change needed (verified safe). |
| F-B | 🟠 medium | change_password: if the re-encrypted payload didn't fit the data area, the write was silently skipped but the header was still rewritten with the new key → silent data loss. (In practice unreachable: XChaCha20 is length-preserving, so the re-encrypted payload is the same size — but it was an unhandled path.) |
Fixed: now returns an error and leaves the container unchanged (still openable with the old password) instead of continuing. |
| F-C | 🟡 low | dgkn::crypto::{derive_hidden_seed,choose_hidden_offset,layout_hidden_volume} is dead in production (the live hidden-volume path is ContainerManager::scan_hidden); only tests call it. Risk: a future reader could mistake it for the real path. |
Fixed (documented): explicit "NOT the production path" notes added in core/Crypto.hpp and the test. |
| F-D | 🟡 low | Audit-log HMAC key was derivable from binary + MachineGuid (forgeable by an on-host attacker). | Hardened: per-install random secret in an ACL-restricted .key file (see "Tamper-evident audit log" above); honest limit documented. |
| F-E | 🟡 low | Totp::is_valid_secret decoded before the cheap length check. |
Fixed: length checked first. |
All fixes are covered by the existing Catch2 suite (round-trip + tamper tests).
The GUI shows a top status bar (via core::SystemStatus): debugger attached,
real-time AV active, admin/elevation. It is an honest posture indicator, not a malware
scanner — a green bar does not mean the machine is malware-free.
Bottom line: DGKN@Labs Crypto Suite v7 has a genuinely strong, hardened design that matches or exceeds common tools on several axes. Calling it "military/forensic grade" is a goal to earn through external validation, not a property you can self-declare.