Skip to main content

detssh

Deterministic Ed25519 SSH key generation from a seed passphrase. Same passphrase + same parameters → same keypair, every time, on any machine - nothing to back up but what's in your head.

detssh

Prompts for everything (passphrase hidden + confirmed) and writes to the same place ssh-keygen would: ~/.ssh/id_ed25519 / ~/.ssh/id_ed25519.pub (mode 600/700, ~/.ssh created if missing). Pass --output to write somewhere else instead.

Non-interactively:

detssh --kdf argon2id --salt-algo blake2b --seed "..." --label github --overwrite-files

Any flag switches to non-interactive mode: missing optional fields fall back to their defaults, --seed is the only required one. Passing --seed on the command line puts it in your shell history and process list (ps aux) - fine for local scripting, but prefer the interactive prompt when that matters.

By default the private key file is written unencrypted, like ssh-keygen -N "". Pass --key-passphrase (or leave it blank at the interactive prompt to skip) to encrypt it at rest instead - anyone who gets the file will still need that passphrase to use it. This is unrelated to the seed passphrase: it never touches key derivation, so it doesn't need to be remembered to recreate the key, only to unlock the file you already have. Same caveat as --seed applies if passed as a flag.

Using the key with ssh

ssh only auto-offers keys with default filenames (id_rsa, id_ed25519, ...) sitting directly in ~/.ssh/ - a key written under --output or a per-label subdirectory needs either -i <path>, a hand-written Host block in ~/.ssh/config, or ssh-adding it to your agent.

The interactive run offers to do this for you at the end, right after it writes the key:

Register this key so `ssh isik:personal:contabo` connects with it? [Y/n]: y
Destination (user@host): root@contabo.com
Non-default ssh port:

Registered isik:personal:contabo -> root@contabo.com
Run: ssh isik:personal:contabo

The label you derived the key with doubles as the Host name, so there's nothing extra to type. In flag mode, --register root@contabo.com (plus --register-port when it isn't 22) does the same thing, and needs --label for that reason. Declining changes nothing about the key that was written.

For a key you already have, detssh ssh register reaches the same end state:

detssh ssh register isik:personal:contabo root@contabo.com --key ~/.ssh/isik:personal:contabo/id_ed25519

Either route records the label in ~/.config/detssh/config, generates a Host block for it in a detssh-managed file, and adds a single Include line to ~/.ssh/config (once, at the top) that pulls it in. After that, ssh isik:personal:contabo just works - through real ssh, nothing routed through detssh at connect time.

detssh ssh list              # show registered labels
detssh ssh forget <label>    # remove a label (never deletes the key file)

Backends

  • --kdf: argon2id (default), argon2i, argon2d, scrypt, pbkdf2, bcrypt_pbkdf
  • --salt-algo: blake2b (default), blake2s, sha256, sha384, sha512, sha3_256, sha3_384, sha3_512

Each combination has its own options (cost knobs, salt digest size, etc.), with their valid ranges shown in both --help and the interactive prompts. See them with:

detssh --kdf <kdf> --salt-algo <salt-algo> --help

We recommend sticking with the defaults (argon2id, memory-hard with a high cost) unless you have a specific reason not to - they make brute-forcing your passphrase significantly more expensive than faster KDFs like pbkdf2.

Cost knobs (iterations, memory, cost, rounds, ...) also carry a soft safety limit meant to catch typos before they cost you minutes of derivation time. In interactive mode, going over one doesn't interrupt you - everything you exceeded is summarized in one confirmation at the very end. In flag mode, it fails immediately unless you pass --force-allow-soft-constraints.

Determinism guarantee

Same explicit parameters → same key, across any release sharing the same major version - only a major version bump may change key derivation. That guarantee starts at 1.0; we're still on 0.x, so it doesn't apply yet (see tests/golden/README.md).

Development

uv sync
uv run pytest
uv run ruff check .
uv run ruff format --check .

pytest runs with coverage on by default ([tool.pytest.ini_options] in pyproject.toml) and fails under 100% line+branch coverage - same gate locally and in CI, nothing extra to remember to pass.

100% coverage means every line runs under test, not that every behavior change gets caught - a test can execute a line without asserting anything meaningful about it. mutmut checks the second thing: it rewrites the source one small change at a time (flips a comparison, drops an argument, tweaks a string) and reruns the tests against each mutant, one at a time. A mutant that still passes ("survived") is a change the suite wouldn't notice.

uv run mutmut run       # takes a few minutes; ctrl-c-safe, resumes where it left off
uv run mutmut results   # list survivors by name
uv run mutmut show <name>   # see what a specific mutant changed

A survivor is either a real test gap (add an assertion that would fail against the mutant) or a genuinely equivalent mutant - the mutated code is provably indistinguishable from the original for every input (e.g. "utf-8" vs "UTF-8" as a codec name: Python's codec lookup is case-insensitive, so no test could tell them apart). CI (.github/workflows/tests.yml, job mutation) fails on any survivor not listed in tests/mutmut_known_equivalents.txt, which names each one and says why - add a confirmed-equivalent mutant there (never to silence a failure without doing that check) rather than chasing an unwinnable test.

Metadata

Release files for detssh 0.6.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 detssh 0.6.0
File Size Uploaded Provenance
detssh-0.6.0.tar.gz 121.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for detssh 0.6.0
File Interpreter ABI Platform
detssh-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 146.7 kB

Release files / detssh-0.6.0.tar.gz

Download URL detssh-0.6.0.tar.gz
Size 121.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e14498c23d0a8d9b843d73da3b46836f2f23466dea5846655878e09723f5e64c
BLAKE2b-256 checksum
How to use checksums
7d5e1b947f133801041509bae7f92dde58bf7f3f49fd28b7cc0358684de9e9a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.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 Sep 19, 2026.

Transparency log

Release files / detssh-0.6.0-py3-none-any.whl

Download URL detssh-0.6.0-py3-none-any.whl
Size 25.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
baa26e702855ce9d7b12a3d204c2b9b68473e865594009d49cf726709e380556
BLAKE2b-256 checksum
How to use checksums
13fe68477580e4095110a772d2e47b7887b4b4859851f3722153974f4c84e3b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.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 Sep 19, 2026.

Transparency log

Security for detssh 0.6.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for detssh-0.6.0.tar.gz

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 Sep 19, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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