rypt.sh 0.1.1

Encrypt files from a shell.

The rypt command line: one POSIX shell script that needs curl and base64, runs on Linux and macOS, and keeps your API key off the command line.

Install

One line, no sudo

curl -fsSL https://sh.rypt.dev/install | sh

The installer puts rypt in ~/.local/bin, then asks for your API key, with the input hidden, and saves it where only you can read it. Get an API key at dashboard.rypt.dev first, or press Enter to skip and add it later. Run the same line again to update.

The installer checks the script against its published SHA-256 before installing it, and never takes the API key as an argument, so the key stays out of your shell history. Read it first at sh.rypt.dev/install.

In CI

Pass the key from your CI's secret store in RYPT_API_KEY. The installer saves it and asks nothing. If the secret is missing, so the variable is empty, the install step fails rather than a later one.

curl -fsSL https://sh.rypt.dev/install | RYPT_API_KEY="$RYPT_API_KEY" sh

By hand

Download the script, read it, check it, and put it on your PATH yourself.

curl -fsSLO https://sh.rypt.dev/rypt.sh
curl -fsSLO https://sh.rypt.dev/rypt.sh.sha256
sha256sum -c rypt.sh.sha256        # Linux
shasum -a 256 -c rypt.sh.sha256    # macOS
mkdir -p ~/.local/bin && install -m 755 rypt.sh ~/.local/bin/rypt

The checksum catches a corrupted or truncated download. It comes from the same server as the script, so it is not a signature. The script itself is at sh.rypt.dev/rypt.sh.

API key

Where your API key lives

The installer saves your API key to ~/.config/rypt/api-key, readable only by you. To add or change it by hand, paste it this way, which keeps it off the screen and out of your shell history (it works from bash, zsh and fish alike):

mkdir -p ~/.config/rypt
sh -c 'umask 077; trap "stty echo; echo" EXIT; trap "exit 130" INT; stty -echo; head -n 1 > ~/.config/rypt/api-key'
# paste the API key and press Enter; it is not shown

The script also reads RYPT_API_KEY from the environment, and RYPT_API_KEY_FILE names a different key file. An API key can create, rotate and delete every key in your account, so treat it like a password.

Use

Files in, files out, or a pipe

# list the keys in your account
rypt keys

# encrypt a file: writes customers.csv.enc
rypt encrypt --key orders customers.csv

# decrypt it: writes customers.csv
rypt decrypt --key orders customers.csv.enc

# or use stdin and stdout
printf 'hello world' | rypt encrypt --key orders > hello.enc
rypt decrypt --key orders < hello.enc

--key takes a key's name or its id. New files are readable only by you, and an existing file is never replaced unless you pass --force. A .enc file holds the ciphertext exactly as the API returned it: one line of base64.

Reference

Commands, settings and exit codes

CommandWhat it does
rypt encrypt [FILE]Encrypts FILE, or stdin, with the key you name. Writes FILE.enc, or stdout.
rypt decrypt [FILE.enc]Decrypts FILE.enc, or stdin. Writes FILE without the .enc, or stdout.
rypt keysLists your account's keys: id, tier and name.
rypt help, rypt versionPrints usage, or the version.
OptionMeaning
-k, --key KEYThe key's name or id. Defaults to RYPT_KEY.
--aad TEXTBinds the ciphertext to a context, such as a row id. Decrypt needs the same text.
-o, --output OUTWrites to OUT instead of the default. -o - means stdout.
-f, --forceReplaces an existing output file.
VariableMeaning
RYPT_API_KEYThe API key. Used before the key file.
RYPT_API_KEY_FILEA file holding the API key. Default ~/.config/rypt/api-key.
RYPT_KEYThe default for --key.
RYPT_API_URLThe API to call. Default https://api.rypt.dev. Plain http is refused except on localhost.
Exit codeMeaning
0Done.
1The API refused the request. The message names the error code and the request_id.
2Usage: a missing key, a bad option, a missing API key.
3Local: a file, the size limit, the network.

encrypt takes at most 65,536 bytes, or 8,192 including --aad on a Hardware key, and --aad at most 65,536. The API takes 128 KiB per request, so a large input with a long --aad is refused before it is sent: its ciphertext could not be sent back to decrypt. For anything larger, use envelope mode through the API.

Secrets

What it does with your key and your data

  • The API key never goes on a command line. curl reads the Authorization header from a pipe on a file descriptor, so ps cannot show it, and curl ignores your ~/.curlrc, so no setting there can print it. RYPT_API_KEY is removed from the environment before any other program starts.
  • Plaintext moves through pipes. It never becomes an argument to any program.
  • Everything is checked before it is sent. The API key, the key id and the ciphertext read from a file must match their exact formats, so a doctored file or name cannot change the request.
  • Files are private and never clobbered. Output goes to a new mode-600 file, created under a random name beside the destination and linked into place when it is complete. An existing file is kept unless you pass --force, and a bad output path is caught before any request is sent.
  • Nothing leaks by accident. Tracing with sh -x, core dumps and curl's TLS key log are switched off, and control characters in anything the server sends are removed before it is printed.
  • The installer never takes the API key as an argument. It asks with the input hidden, or reads RYPT_API_KEY in CI, and saves the key with mode 600. It checks the script's SHA-256 before installing, never uses sudo, and does nothing if its own download was cut short.
  • It talks to one place. Only the rypt API, over HTTPS; plain http is refused except on localhost, and a proxy setting never sees a localhost request. There is no telemetry and no update check.

Your plaintext still reaches rypt, which encrypts it and holds the key. The security page says where keys live and what rypt sees.