Skip to content

Repository files navigation

GBC — Geometric Book Cipher (v1.0.0)

Status: proof of concept, under active development. This is not a production-grade secure messaging system. It has not been independently audited, and several of its design goals (see Known limitations below) are not fully met yet. Do not use it to protect anything where a real compromise would hurt you.

What this is

GBC turns a short text message into a 3D point cloud. The points that actually encode the message ("wheat") are mixed in with random-looking decoy points ("chaff", "decoy", "entropy") so that, without the right keys, every point in the cloud looks equally meaningless. The real points are placed using a keyed pseudo-random function (HKDF/HMAC-SHA256), and a short piece of metadata (nonce, message length, an integrity digest) is hidden redundantly inside a few of the decoy-looking points using RSA-OAEP, so the receiver can find it by trial decryption instead of by position.

In short: it's a steganographic packaging layer on top of standard crypto primitives (HKDF, HMAC, RSA-OAEP). The primitives do the actual security work; the geometry's job is obfuscation, deniability, and — frankly — being a fun format to look at.

Project layout

gbc_core.py       -- the protocol itself: geometry, HKDF key schedule,
                      RSA-OAEP envelope, HMAC integrity, encode_message()
                      / decode_message(). Single source of truth; both
                      CLI scripts below just call into this.
gbc_encode.py      -- CLI: reads input.json, writes public/ + private/
gbc_decode.py      -- CLI: reads public/message.json + private/secret.json
tests/             -- pytest suite (round-trip, tamper detection, edge
                      cases, CLI integration) -- see "Running the tests"
.github/workflows/ -- CI: runs the test suite on push/PR
requirements.txt, requirements-dev.txt, pytest.ini, LICENSE, CHANGELOG.md

How a message moves

A message round-trip produces exactly two artifacts:

public/message.json   -- the point cloud + receiver's public RSA key.
                          Safe to publish / share over an open channel.
private/secret.json   -- the shared master key + receiver's private RSA
                          key. Must travel over a separate, confidential
                          channel (USB, encrypted email, Signal, etc.)

Both files carry a matching id field so the decoder can catch "wrong secret.json for this message.json" as an explicit error instead of a confusing crypto failure.

pip install -r requirements.txt

python gbc_encode.py   # reads input.json, writes public/ and private/
python gbc_decode.py   # reads public/message.json + private/secret.json

input.json:

{ "message": "hello world", "language": "en" }

Running the tests

pip install -r requirements-dev.txt
pytest                  # full suite
pytest -m "not slow"    # skip the real-subprocess smoke test, ~2x faster

The suite covers: round-trips (including every character in the alphabet, one at a time and all together), negative paths (wrong master key, wrong RSA private key, tampered HMAC, tampered points, tampered entropy nodes, truncated clouds, malformed input), boundary conditions (empty message, single character, the length-field ceiling), and the CLI layer itself (input.json parsing, file layout, the id-match check). See tests/ for the individual cases.

Where this could actually be useful

  • Teaching / demoing protocol design — it's a compact example of combining HKDF domain separation, a keyed-PRF steganographic layout, a hybrid RSA-OAEP metadata envelope, and whole-cloud HMAC integrity in one project.
  • CTF challenges / ARGs / puzzle boxes — "this file is a meaningless 3D point cloud... or is it?" is a natural fit for capture-the-flag or alternate-reality-game content.
  • Steganographic / artistic message delivery — the cloud can be rendered as an actual 3D asset (point-cloud file, mesh viewer, etc.) and shared as if it were just an odd piece of generative art. Deniability is a genuine, working property here: without both keys, an observer cannot tell which points (if any) are real.
  • Learning material for the "self-destructing message" problem — GBC is a good jumping-off point for discussing why client-checked expiry doesn't work (see below) and what actually does (Ephemerizer-style threshold key release, delete-on-first-fetch hosting, etc.).

Where this is not the right tool

If you need actual secure messaging, use an established, audited tool (age, Signal, PGP/GPG, etc.). GBC's geometric packaging does not add cryptographic strength beyond its underlying primitives, and it adds real complexity and attack surface that a plain authenticated-encryption scheme (e.g. XChaCha20-Poly1305) wouldn't have.

Known limitations

This list is deliberately honest, not a list of "todos we'll casually get to" — treat every point here as a real gap in the current design:

  • master_key is per-message, not a shared long-term secret. Every run of gbc_encode.py generates a brand new, random master_key (secrets.token_bytes(32)) and ships it inside that message's own private/secret.json — it is never reused across messages. This is a genuinely good property: compromising one message's secret.json exposes only that one message, not any others. The one thing to still watch for: if you manually reuse a secret.json/master_key across more than one message — the scripts don't do this, but nothing stops a user from copy-pasting one — you'd be reintroducing exactly the risk a per-message key is meant to avoid. Treat each secret.json as single-message, single-use.
  • No sender authentication / non-repudiation. Anyone holding master_key can produce a message that looks legitimate. There's no separate signing identity for "who actually sent this."
  • Message length is not fully hidden. The published point count is dominated by the number of "wheat" points, which is exactly 3 × message_length. Chaff/decoy counts are log-scaled specifically to avoid compounding this leak, but they do not close it — an observer can still estimate roughly how long the plaintext is just from the size of message.json, even without any keys. (This is documented in the code as a known, structural consequence of the space-sizing formula, not something the log-scaling "fixes.")
  • RSA-OAEP is not post-quantum secure. If "harvest now, decrypt later" matters for your threat model, this needs a hybrid PQC KEM instead of (or alongside) RSA.
  • No forward-looking access control beyond key possession. Once private/secret.json is compromised, the message is fully readable — there is no second factor (e.g. a passphrase) required in addition to the files themselves.
  • Alphabet is restricted. The character set is Turkish + English letters, digits, basic punctuation, space, and newline — no arbitrary binary data without an extra encoding layer (e.g. hex).
  • Poor scalability. Point count grows with message length; this is workable for short messages (a sentence or two) and impractical for anything paragraph-length or longer.
  • Character matching is not constant-time. decode_message() tries alphabet characters in order and stops at the first match, so wall-clock decode time varies slightly with which character matched. The receiver already holds every secret needed to decode locally, so this isn't an oracle against a remote attacker today -- but it would become one if this function were ever exposed as a network service without also being made constant-time.

On "self-destructing" / time-limited messages

There is no TTL, expiry, or "self-destruct" mechanism in this protocol. A public/message.json remains fully decodable for as long as it exists and the matching private/secret.json is available — there is no built-in way to make it stop being readable after some deadline.

If you need a message that can only be read once, or only within a window, that property has to be enforced by whoever hosts public/message.json — e.g. a Privnote/Yopass/OneTimeSecret-style service that deletes its copy the moment it's first fetched — not by this project. Even then, be aware this is fundamentally a best-effort guarantee: once a legitimate receiver has decoded the message, nothing stops them from copying the plaintext (screenshot, copy-paste, etc.). This is the same "analog hole" every self-destructing-message product (Snapchat included) runs into — it's a UX/policy layer, not a cryptographic one.

License / disclaimer

MIT licensed (see LICENSE) -- proof-of-concept code, provided as-is, for learning and experimentation. No warranty, no audit, no guarantees about confidentiality, integrity, or availability of anything encoded with it.

About

GBC is a steganographic packaging layer on top of standard crypto primitives (HKDF, HMAC, RSA-OAEP). The primitives do the actual security work; the geometry's job is obfuscation, deniability, and — frankly — being a fun format to look at.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages