CLI tool
TimothyYe/skm avatar
TimothyYe/skm

skm: an SSH key manager that keeps aliases, passphrases and audit trails

A simple and powerful SSH keys manager

1,089 stars60 forksGoMIT

At a glance

What is it?
skm is a Go command line tool that moves your SSH keys out of a flat .ssh directory into a per-alias store, and adds the things a plain ssh setup lacks: passphrase management, backups, a recoverable trash, an audit of weak keys and a publish command for GitHub, GitLab and Bitbucket. It is a single binary with no daemon.
Who is it for?
skm fits a developer who juggles several keys across work, personal and client hosts and wants one place to see, rotate, copy and audit them, and it does nothing for you if you have exactly one key.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 82 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Keys get an alias and a directory of their own

The problem skm solves is the pile of id_rsa, id_ed25519 and id_ed25519_sk files in a .ssh directory, each with a comment nobody remembers. The tool replaces that with a store where every key has a name and a home. The first command is skm init, and it creates the store at $HOME/.skm with every key inside it. If you already have an id_rsa and id_rsa.pub pair, the documentation says skm moves them to $HOME/.skm/default.

The command surface is long but flat. There is create, ls, use to set a default by alias, delete, rename, copy to a remote host, display, backup and restore to an archive, import of an existing key pair from anywhere on disk, export of a single key as a tar.gz bundle that can optionally be encrypted, fingerprint and info for inspection, passphrase for adding, rotating or removing a passphrase, publish to a hosting provider, doctor, audit, cache for the SSH agent, and a trash command.

Three global options shape where things live: --store-path for the profiles, --ssh-path for the .ssh folder, and --restic-path for the restic binary. The presence of a restic path is the hint that backups are meant to be deduplicated and encrypted with an existing restic setup, rather than being a plain tarball in a folder. There is also a prompt interface with fuzzy search for choosing a key, which matters when you have a dozen aliases and do not remember their names.

Installing one binary, three ways

Since v0.8.9 the tool is in homebrew-core, so on macOS and Linux the shortest path is:

bash
brew install skm

Users of the older tap are told to remove that first, so that the two do not fight over the same binary name. For people who already work in Go, the module path is the alternative:

bash
go get github.com/TimothyYe/skm/cmd/skm

And for everyone else the project points at the releases page, with the binary to be extracted into /usr/bin or a directory on the PATH. The build is a standard Go project: the module requires Go 1.26, and the dependencies are small and mostly about the terminal. fatih/color handles colour output, manifoldco/promptui provides the fuzzy prompt, and urfave/cli drives the command dispatch. A goreleaser configuration sits at the root of the repository, which is how the release binaries are produced.

There is a completions/ directory in the repository, so shell completion for the command line is part of the distribution rather than something you write yourself.

Key types, including hardware-backed ones

The create command supports ed25519 as the default type, plus rsa, ed25519-sk and ecdsa-sk. The two -sk variants are the FIDO2 hardware backed keys, and the documentation is explicit about their requirement: they need ssh-keygen 8.2 or newer and a security key, a YubiKey or a Solo for instance, plugged in at creation time. A run through the tool looks like this:

bash
skm create prod -C "[email protected]"

For RSA the floor is enforced rather than suggested. The documentation states that RSA keys below 3072 bits are rejected, that skm audit flags the same keys as weak so the two commands agree on what is safe, and that you should use ssh-keygen directly if you need a smaller key for testing. The examples show the shape of the options:

bash
skm create old -t rsa -b 4096           # RSA (minimum 3072 bits enforced)
skm create yubi -t ed25519-sk           # hardware-backed; prompts for PIN + touch

The hardware key path is the one that changes how you work. Because the key never leaves the device, the passphrase prompt becomes a PIN and a touch, and the private key file in your store is a stub rather than the secret itself.

Listing keys shows more than a directory of files

The default listing is three columns, alias, key type and comment, with the active default key marked:

bash
% skm ls

✔ Found 3 SSH key(s)!

->  default  [ssh-ed25519]  [work@laptop]
     dev      [ssh-ed25519]  [dev]
     prod     [ssh-rsa]      [prod]

The long form adds the details that matter when a key stops working, its bit length, its SHA256 fingerprint, whether it is loaded in the agent and the date it was created. A quiet flag prints just the alias names, and a JSON flag exists for scripting, which is the form you want if you are wiring skm into a provisioning script.

That fingerprint column is the practical answer to the question this tool exists for. When a server rejects a key, the thing you need is the SHA256 fingerprint of the public key you offered, and having it printed next to the alias removes a round trip through ssh-keygen. The export command, which produces a tar.gz bundle for a single key and can encrypt it, is the counterpart when a key has to move to another machine.

audit, doctor and trash are the security commands

Three commands go beyond key management. skm doctor runs diagnostics against the SKM environment, the SSH agent and the stored keys, which is the first thing to run when a key is not being offered or an agent refuses to sign. skm audit reads the stored keys and flags weak strength, missing passphrases and age, so it is a periodic check rather than a one time action.

Deletion is soft. Instead of removing a key, skm moves it to a recoverable trash, and the trash command takes list, restore and empty, so a mistaken delete is recoverable. A passphrase command adds, rotates or removes the passphrase on a key, which is the operation you actually need when someone leaves the company and you have to rotate their access without rebuilding every key.

The event system is the least visible feature and one of the more useful. Hooks are pluggable and fire on post-use, post-create, pre-delete and post-copy events, both per key and globally. A team can therefore log every copy of a public key to a host, or run a check after a key is created, without wrapping the tool in a script.

Publishing is also built in. skm publish uploads a public key to GitHub, GitLab or Bitbucket, so the account side of adding a key does not require visiting three web interfaces.

What skm changes about a plain ssh setup

The alternative is the one most people already have: keys in ~/.ssh, aliases in the config file, and ssh-add to load a passphrase into the agent. That approach is a handful of lines of configuration and no extra binary, and it is entirely adequate for one or two keys.

The difference in approach is that skm makes the key itself a managed object with a name, a lifecycle and an audit trail, rather than a file that OpenSSH happens to find. A plain setup can point at a specific key with an IdentityFile directive and can be scripted with shell, but it has no notion of rotating a passphrase, no way to publish to a hosting provider, and no command that tells you which of your keys are weak or old. skm also relocates keys out of the .ssh directory, which changes the paths other tools and scripts expect.

That last point is the real trade. Moving keys into $HOME/.skm is convenient for skm and inconvenient for anything else, so a team with existing automation around ~/.ssh should expect to change that automation. The alternative, keeping the .ssh layout and adopting only parts of the tool, is possible through the --ssh-path option but is not the documented path.

The licence question deserves a note too, because the README shows two licence badges, one for MIT pointing at the repository LICENSE file and one naming NPL, the 996 Prohibited License, pointing at the 996.ICU licence. The project is recorded under the MIT licence, and the LICENSE file is the one to read, but a project that manages your private keys is exactly where you read the licence text rather than the badge.

Maintenance, upgrades and where to start

The project moves in small steps. The last push was on 2026-08-02, and the recent releases are v0.9.1, v0.9.2 and v0.9.3 between May 21 and May 24, 2026, which is the pace of a tool maintained by one person in bursts.

A sensible first run is short. Install the binary, run skm init, and look at what the tool did to an existing id_rsa pair before you create anything new. Then run skm doctor to see what it thinks of your environment and agent, and skm audit to see what it thinks of the keys it now owns. Create one throwaway alias, copy it to a host you control, and check that the key you expect is the one that arrives.

Two things are worth deciding early. Whether the store becomes the only home for your keys, since the tool pushes you towards it, and whether the trash and the backup are enough for your recovery story, because skm backup writes an archive that the restic path option suggests you should be replicating somewhere else. Nothing in the project is a daemon or a background service, which means there is no upgrade to plan and no migration to run, and the whole state of the tool is one directory of files.

Editorial conclusion

skm fits a developer who juggles several keys across work, personal and client hosts and wants one place to see, rotate, copy and audit them, and it does nothing for you if you have exactly one key. Before adopting it, read the LICENSE file, because the README carries two licence badges that do not agree, and confirm that moving your existing keys into the store is acceptable; then try skm doctor and skm audit on a machine that matters before you start deleting the originals from your .ssh directory.

Frequently asked questions

How do I install skm?

Since v0.8.9 it is in homebrew-core, so brew install skm works on macOS and Linux. Go users can run go get github.com/TimothyYe/skm/cmd/skm, and the releases page offers a binary to extract into /usr/bin or a directory on the PATH.

Where does skm store my SSH keys?

The store is created at $HOME/.skm by skm init, with each key in a directory named after its alias. The path can be changed with the --store-path option, and the location of your .ssh folder with --ssh-path.

Does skm support FIDO2 hardware security keys?

Yes. The create command supports ed25519-sk and ecdsa-sk, which are the FIDO2 hardware backed variants. They require ssh-keygen 8.2 or newer and a security key such as a YubiKey or Solo plugged in when the key is created.

What does skm audit check?

It audits the stored keys for weak strength, missing passphrases and age. The documentation also states that RSA keys below 3072 bits are rejected at creation, so create and audit agree on what is considered safe.

Can I recover a key I deleted with skm?

Deletion is soft, moving the key to a recoverable trash. The trash command supports list, restore and empty, and there are separate backup and restore commands that work on an archive of all your keys.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. TimothyYe/skm on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/timothyye-skm.svg)](https://hysenlabs.com/projects/timothyye-skm)

Was this useful?