Skip to main content

py-libtab

Plain-text tables with real cryptography embedded per field — from Python.

py-libtab writes and reads .tab files: ordinary, human-readable text files where an individual field can be hashed (an irreversible digest), signed (tamper-evident, authenticated), or sealed (encrypted — the plaintext can't be read without the key), using audited crypto (BLAKE2b, argon2id, Ed25519, XChaCha20-Poly1305). The crypto travels inside the file — algorithm, parameters, nonce, and salt ride in the cell itself — so a file is self-contained, and the non-secret parts stay fully inspectable with cat, grep, and git diff.

Why this exists

If you want structured data on disk from Python, your options are usually:

  • a database — opaque binary, needs sqlite/a server, can't grep it;
  • JSON/CSV — plain text, but you bolt crypto on by hand and your hash/signature format ends up ad-hoc (and probably subtly wrong);
  • a secrets manager — heavyweight, external service.

There's no lightweight middle: a readable text file where a field can simply be a verified hash, a signed value, or an encrypted secret. That's the gap py-libtab fills.

Three ways to protect a field

Type Guarantees Readable? Use for
HASHED integrity — irreversible digest the digest is checksums, content addresses, "did this change?"
SIGNED authenticity — tamper-evident yes, plaintext + signature audit logs, signed config/release metadata
sealed confidentiality — encrypted no, only the key holder API keys, tokens, anything secret at rest

Good fits: tamper-evident audit logs whose entries you can still read · signed release metadata · content-addressable manifests · API-key / token vaults where the values are sealed but the surrounding table (ids, labels, which key is which) stays greppable.

One caveat worth stating: HASHED and SIGNED cells are readable by design — a signed value's plaintext is right there in the file. Use sealed for anything that must stay secret. (And note a bare password-hash table, even committed as HASHED, is still an offline-cracking target if it leaks — for credentials you'd typically seal the table or keep it out of version control.)

How it works

                              py-libtab
   ══════════════════════════════ WRITE ══════════════════════════════

     your Python code  —  writing a tamper-evident audit log
     ┌──────────────────────────────────────────────────────────────┐
     │ t = NativeTable.create("audit.tab", ...)                      │
     │ t.set(row, "event", "user.delete")   → plain text             │
     │ t.set_hashed(row, "payload", blob)   → BLAKE2b content hash    │
     │ t.set_signed(row, "sig", entry, sk)  → Ed25519 signature      │
     │ t.commit()                                                    │
     └───────────────┬──────────────────────────────────────────────┘
                     │ calls
                     ▼
     ┌──────────────────────────────────────────────────────────────┐
     │  native library   (audited C crypto — text parser ·          │
     │  BLAKE2b · argon2id · Ed25519 · XChaCha20-Poly1305)          │
     └───────────────┬──────────────────────────────────────────────┘
                     │ writes
                     ▼
   ┌─────────────────────────────────────────────────────────────────┐
   │  audit.tab   —  plain text: cat it · grep it · diff it in git    │
   │                                                                   │
   │   schema=audit                                                    │
   │       col=id                                                      │
   │       col=event                                                   │
   │       col=payload type=HASHED                                     │
   │       col=sig     type=SIGNED signer=ci                           │
   │                                                                   │
   │   id=2024-06-01T12:00Z-0001                                       │
   │       event=user.delete                                           │
   │       payload=hashed:AQ8Kf3...      ← self-describing: the algo,  │
   │       sig=signed:aGVsbG8=:3q2+7w...    params & salt ride inline  │
   └─────────────────────────────────────────────────────────────────┘
                     │
   ══════════════════╪═══════════ READ + VERIFY ══════════════════════
                     │ reads
                     ▼
     ┌──────────────────────────────────────────────────────────────┐
     │ t = NativeTable.open("audit.tab")                             │
     │ row = t.search("event", "user.delete")[0]                     │
     │                                                               │
     │ t.verify_hash(row, "payload", blob)  → True / False  (did the │
     │                                        payload change?)        │
     │ t.verify_signed(row, "sig", pubkey)  → entry bytes, or raises │
     │                                        if the entry was forged │
     └──────────────────────────────────────────────────────────────┘

   one representation everywhere:  on disk == on the wire == in a backup
   the file IS the data · the crypto rides inside it · you can still read it

Usage

Write a tamper-evident audit log. Each entry records an event, a content hash of its payload (integrity), and an Ed25519 signature over the entry (provenance):

from libtab import NativeTable, NativeColumn

t = NativeTable.create("audit.tab", "audit", [
    NativeColumn("id"),
    NativeColumn("event"),
    NativeColumn("payload", type="HASHED"),
    NativeColumn("sig", type="SIGNED", signer="ci"),
])
row = t.add_row("id", "2024-06-01T12:00Z-0001")
t.set(row, "event", "user.delete")
t.set_hashed(row, "payload", payload_blob)     # BLAKE2b digest of the payload
t.set_signed(row, "sig", entry_bytes, sk)      # sk = your 32-byte signing key
t.commit()
t.close()

The file on disk is just text — greppable, diffable in git, and every entry carries its own verification data:

schema=audit
	col=id
	col=event
	col=payload type=HASHED
	col=sig type=SIGNED signer=ci

id=2024-06-01T12:00Z-0001
	event=user.delete
	payload=hashed:AQ8Kf3...
	sig=signed:aGVsbG8=:3q2+7w...

Read it back and verify — the hash proves the payload wasn't altered, the signature proves the entry is authentic (and returns the signed bytes, or raises if it was forged):

from libtab import NativeTable

t = NativeTable.open("audit.tab")
row = t.search("event", "user.delete")[0]

t.verify_hash(row, "payload", payload_blob)    # True if the payload matches
entry = t.verify_signed(row, "sig", pubkey)    # signed bytes, or raises
t.close()

Sealing a secret

For values that must stay confidential, set_sealed encrypts the field under a 32-byte key; only the key holder can read it back with get_sealed. The surrounding table stays plain text, so you can still grep for a row — but the sealed value is ciphertext on disk:

import os
from libtab import NativeTable, NativeColumn

key = os.urandom(32)   # keep this out of the file

t = NativeTable.create("vault.tab", "vault", [
    NativeColumn("id"),
    NativeColumn("secret"),        # an ordinary column; sealing is per-value
])
row = t.add_row("id", "stripe-api-key")
t.set_sealed(row, "secret", b"sk-live-abc123", key)
t.commit()
t.close()
# on disk:  secret=sealed:LZ5tJO8m...   (plaintext never present)

t = NativeTable.open("vault.tab")
row = t.search("id", "stripe-api-key")[0]
t.get_sealed(row, "secret", key)              # b"sk-live-abc123"
t.get_sealed(row, "secret", os.urandom(32))   # raises — wrong key or tampered
t.close()

Sealing uses XChaCha20-Poly1305 with a fresh random nonce per value, so it's also tamper-evident: a modified ciphertext fails to decrypt. The raw seal(key, data) / unseal(key, blob) functions are exposed too if you want to encrypt something without a table.

The .tab format

A simple attribute/value text file. The first tuple declares the schema; every later tuple is a row keyed by the first column:

schema=orders
	col=id
	col=item
	col=qty

id=a
	item=widget
	qty=5

id=b
	item=gadget
	qty=3

Columns may be typed HASHED (BLAKE2b or argon2id) or SIGNED (Ed25519); typed cells carry a self-describing <type>:<base64url> tag that includes the algorithm, parameters, and salt, so a cell is verifiable on its own. sealed cells (XChaCha20-Poly1305) use the same tag convention on an ordinary column. Untyped columns are plain text.

The crypto is the real thing — BLAKE2b, argon2id, Ed25519, and XChaCha20-Poly1305 from an audited C implementation, not hand-rolled hashlib calls with guessed-at parameters.

Build & install

Requires a C toolchain (gcc, ar).

cd vendor && ./build.sh          # builds the native library
cd .. && pip install .

The engine is compiled from vendored C sources; pip install links it in. There are no Python runtime dependencies.

Testing

These files carry secrets, integrity data, and provenance people rely on, so py-libtab is tested like the security primitive it is: unit + integration tests, fuzzing of the parser and the decrypt path under AddressSanitizer, crypto known-answer vectors (BLAKE2b/argon2id/Ed25519/XChaCha20-Poly1305 checked against independent references), and mutation testing (0 surviving mutants). One command runs everything:

./run-all-tests.sh          # fast gate
./run-all-tests.sh all      # everything, incl. fuzz + mutation

See TESTING.md for the full breakdown of each layer.

Release files for libtab 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for libtab 0.1.0
File Size Uploaded
libtab-0.1.0.tar.gz 393.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for libtab 0.1.0
File
libtab-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.17+ x86-64 Details
libtab-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ x86-64 Details
libtab-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ x86-64 Details
libtab-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 CPython 3.10 Linux glibc 2.17+ x86-64 Details

Total release size: 749.5 kB

Release files / libtab-0.1.0.tar.gz

Download URL libtab-0.1.0.tar.gz
Size 393.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a83877b2acf39cf1be2c119bbc805fe0101c84c59eb05ac3cd1528bbda03e54b
BLAKE2b-256 checksum
How to use checksums
17f13b055d01030dcced7fb3a003383f53ac9246304462fb907628843994a0a9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.

Transparency log

Release files / libtab-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL libtab-0.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 89.0 kB
Tags CPython 3.13 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
21fbf70393e04fca624b12a529cbc6de1e87ffa0c06ebf5b5f356630aa4ef95b
BLAKE2b-256 checksum
How to use checksums
08f17616cbce515351eb08e4f0ea1f13a0e850268427fc389a0822aa394abc6c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.

Transparency log

Release files / libtab-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL libtab-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 89.0 kB
Tags CPython 3.12 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
b5cafd1638ca08b068678ce1bd41cdb23c9dd1d9ee2e100d0ebfc8dc2f6d0587
BLAKE2b-256 checksum
How to use checksums
8bae053d6a4a5dfb452b4416a414fbfb083f47acd8197311b417cd7549191b70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.

Transparency log

Release files / libtab-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL libtab-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 89.0 kB
Tags CPython 3.11 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
24d2b31426a250ebbd3091e092616f4b7627b204a23e63772376322b3b188d33
BLAKE2b-256 checksum
How to use checksums
99eb56104b0fd759dfaa6e067122fe17a534726cbe4de5c1b6ea16ccc73711d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.

Transparency log

Release files / libtab-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL libtab-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 89.0 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
18bc26b756d44b3b111ad7fb9d88d0a6c87050dbb65c7ed604ed7f274b0a1301
BLAKE2b-256 checksum
How to use checksums
01445c7e9c9e56125a8a5830ad85c235ac6250d9c2f372da66f47906710d7443
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.1

5 release files

0.3.0

5 release files

0.2.0

5 release files

This release

0.1.0 This release

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page