language sdks

Encrypt inside your app, four languages.

Keep messages and passwords on your own systems. These free software development kits (SDKs) support TypeScript, Rust, Go and Python, with a shared encrypted file format. Apache 2.0 permits commercial use. Install with npm install deny-sh, cargo add deny-sh, pip install deny-sh, or go get github.com/deny-sh-crypto/deny-go/v2. Local encryption needs no network call. Both passwords are always required together; the control file selects the real message or your chosen fake (decoy).

↓
01Choose your language02Run in your process03Keep controls separate


node.js / typescript

Node.js / TypeScript

Use encryption inside your Node.js application. The SDK uses node:crypto (the built-in cryptography library). See the package dependency notes below.

# 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

Python

Use the same encrypted file format in Python. pycryptodome handles AES encryption; argon2-cffi turns passwords into keys using Argon2id.

# 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

Go

Use the shared format in Go. golang.org/x/crypto/argon2 turns passwords into keys; the standard crypto/aes library handles encryption.

# 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

Rust

Use the shared format in Rust. The aes and ctr crates handle encryption, argon2 turns passwords into keys, and sha2 provides hashing.

# 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

Honey Mode

Honey Mode makes a wrong password/control combination return a fake in a supported format. It does not register a monitored alert. Declare the format when encrypting, so wrong inputs return a fake in that format instead of garbage. The same wrong inputs return the same fake. It is generated from those inputs and record metadata, not from the real message. 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#honey-mode.

algorithm parity

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.