Native deniable encryption, four languages.
The Encrypt pillar of the deniability infrastructure. Native deniable encryption in TypeScript, Rust, Go, and Python. Apache 2.0, cross-compatible wire format, zero copyleft. npm install deny-sh, cargo add deny-sh, pip install deny-sh, or go get github.com/deny-sh-crypto/deny-go/v2. No HTTP dependency.
Node.js / TypeScript
The reference implementation. The SDK runtime imports only node:crypto (built-in) and ships as a single 8.4 KB module with zero third-party runtime dependencies on the encrypt / decrypt / deny code path.
# Install
$ npm install deny-sh
# Usage
import { encrypt, decrypt, generateDeniableControl } from 'deny-sh';
const ct = encrypt(plaintext, { password1, password2, controlData });
const msg = decrypt(ct.ciphertext, { password1, password2, controlData });
const fake = generateDeniableControl(ct.ciphertext, password1, password2, fakePlaintext);
Note on the npm package layout. The deny-sh npm package ships the SDK plus optional CLI, Telegram bot, and server tooling in one repository so that npx deny-sh works from a fresh install. Those optional sub-packages have their own dependencies (see the dependencies field in package.json); they are loaded only when you run the CLI or bot binary, not when you import the SDK from your application code. Tree-shaking bundlers (esbuild, Vite, webpack) will leave them out of your final bundle.
Python
pycryptodome for AES, argon2-cffi for Argon2id. Algorithm-compatible with the TypeScript SDK. 749 collected tests.
# Install
$ pip install deny-sh
# Source: github.com/deny-sh-crypto/deny-python
# Usage
from deny_sh import encrypt, decrypt, generate_deniable_control
ct, control = encrypt(b"seed phrase", "pw1", "pw2")
msg = decrypt(ct, "pw1", "pw2", control)
fake_ctrl = generate_deniable_control(ct, "pw1", "pw2", b"decoy seed")
Go
golang.org/x/crypto/argon2 for Argon2id, stdlib crypto/aes for AES-CTR. Algorithm-compatible with the TypeScript and Python SDKs. 36 tests.
# Install
$ go get github.com/deny-sh-crypto/deny-go/v2
# Source: github.com/deny-sh-crypto/deny-go
# Usage
import denysh "github.com/deny-sh-crypto/deny-go/v2"
ct, ctrl, _ := denysh.Encrypt([]byte("seed phrase"), "pw1", "pw2", nil)
msg, _ := denysh.Decrypt(ct, "pw1", "pw2", ctrl)
fakeCtrl, _ := denysh.GenerateDeniableControl(ct, "pw1", "pw2", []byte("decoy seed"))
Rust
aes, ctr, argon2, and sha2 crates. No unsafe code. Algorithm-compatible with the TypeScript SDK. 38 tests.
# Install
[dependencies]
deny-sh = "2.1"
# Source: github.com/deny-sh-crypto/deny-rs
# Usage
use deny_sh::{encrypt, decrypt, generate_deniable_control};
let (ct, ctrl) = encrypt(b"seed phrase", "pw1", "pw2", None).unwrap();
let msg = decrypt(&ct, "pw1", "pw2", &ctrl).unwrap();
let fake_ctrl = generate_deniable_control(&ct, "pw1", "pw2", b"decoy seed").unwrap();
Honey Mode
One call generates a decoy and arms it. Encrypt a structured secret declaring its type, and the wrong password returns a fresh, validator-gated, shape-correct fake of that exact type instead of failed-decrypt noise. The fake is deterministic per record (seeded from the wrong-password bytes, salt-bound and type-tagged), so it is stable per wrong password and independent of the real secret. Structured types only — eligible across 63 of the 69 credential types; the unstructured catch-alls and four JSON/URI types are refused (data with no fixed shape has nothing to fake). Live in every reference SDK (npm, PyPI, crates, Go).
// TypeScript (out.value: real on right pw, typed fake on wrong)
import { encryptHoney, decryptHoney } from 'deny-sh';
const res = await encryptHoney({
secret, passwords: { p1, p2 }, honeyType: 'stripe-live-key',
});
const out = await decryptHoney(
res.ciphertext, ctrl, { p1, p2 }, res.honeyType, res.band,
);
# Python
from deny_sh import encrypt_honey, decrypt_honey
res = encrypt_honey(secret, p1, p2, 'stripe-live-key')
out = decrypt_honey(
res.ciphertext, ctrl, p1, p2, res.honey_type, res.band,
)
// Go
res, _ := deny.EncryptHoney(secret, p1, p2, "stripe-live-key")
out, _ := deny.DecryptHoney(
res.Ciphertext, ctrl, p1, p2, res.HoneyType, res.Band,
)
// Rust
let res = encrypt_honey(secret, p1, p2, "stripe-live-key")?;
let out = decrypt_honey(
&res.ciphertext, &ctrl, p1, p2, &res.honey_type, res.band,
)?;
# CLI
$ deny-sh encrypt -m "$SECRET" -p1 "$P1" -p2 "$P2" \
--honey --honey-type stripe-live-key
Persist the honeyType and band from the encrypt result — both are required inputs to the honey decrypt. Full API reference and the branch-telemetry caveat: deny.sh/docs#honey-mode.
Algorithm parity
All SDKs implement the same algorithm. A file encrypted with the Python SDK can be decrypted with Go or Rust. The ciphertext format is identical: salt(32) + iv(16) + AES-256-CTR(Argon2id-derived key, LE32(plaintext_length) || plaintext XOR control_data).
Every SDK is Apache 2.0. Free for commercial and proprietary use. See /licensing for the application-layer split.