Skip to content

rypt docs

rypt is an HTTP API that encrypts and decrypts with keys it holds for you, in Google Cloud. A Software (software) or Hardware (hsm) key is a Cloud KMS key of its own. A Free (free) or Extra wrapped (wrapped) key is a set of data keys, one per key version, stored wrapped under one shared Cloud KMS root key at the HSM protection level. Key material stays inside rypt and Cloud KMS: no call returns it, and you cannot import your own.

It offers two modes, five operations in all:

  • Direct: encrypt and decrypt take the data itself, 1 to 65,536 bytes of plaintext a call. On a Hardware key, plaintext and aad together may be at most 8,176 bytes.
  • Envelope: wrap and unwrap take a data key, which you use to encrypt your data on your own machine. wrap can generate the data key for you.

The fifth, rewrap, works in both modes: it moves a ciphertext or a wrapped data key to the key's current version. Direct or envelope explains how to choose.

There is no SDK. You call the HTTP API from your own code, or use rypt.sh from a shell.

rypt is not zero-knowledge. In direct mode your plaintext travels to rypt in the request, and decrypt sends it back; on Software and Hardware keys it also goes to Cloud KMS, where the encryption runs. In envelope mode your data stays with you, but rypt sees every data key it wraps or unwraps for you, and on Software and Hardware keys so does Cloud KMS. rypt holds the keys that decrypt your data, and can use them.

The security model sets out what rypt sees, what it keeps and what it does not claim.

The API is at https://api.rypt.dev. Every endpoint is under /v1, except that the health check, /v1/healthz, is also served at /healthz.

Request and response bodies are JSON. Send Content-Type: application/json with every request body: a body without a JSON content type is refused with 415 unsupported_media_type.

Binary fields (plaintext, ciphertext, aad, dek, wrapped) are standard base64, the alphabet with + and /, with = padding, in requests and in responses. URL-safe or unpadded base64 is refused with 400 invalid_base64. Sizes are counted after decoding, but a request body over 128 KiB is refused whatever its fields hold: see limits.

Every response carries an x-request-id header. A failed request answers with a JSON body of error, message and request_id; errors lists every code.

Send an API key as a bearer token:

Authorization: Bearer ry_K3pQ7xTa_...

An API key is 44 characters: ry_, an 8-character prefix, _, and a 32-character secret, all letters and digits.

You issue API keys in the dashboard, on the API keys page. Only a dashboard session can issue, restrict or revoke one. The token is shown once, when it is issued, and cannot be recovered: if you lose it, revoke that API key and issue another. Storage says how rypt keeps it.

An API key works only with keys in its own container. A new API key has no actions unless you choose some when you issue it. Actions do not restrict everything an API key can do, and decrypt and unwrap overlap: read what actions do not restrict before you rely on them. Access control also covers key lists and firewalls.

A missing, malformed, wrong or revoked API key gets 401 unauthorized, and the response does not say which. A dashboard session cannot run the five operations: they answer it with 403 api_key_required.

A key's tier is chosen when the key is created and cannot be changed.

Tier API id Where encryption runs Versions kept Operations a month
Free free In rypt, with AES-256-GCM, under a data key wrapped by the root key 1 10,000, then refused
Extra wrapped wrapped In rypt, the same way as Free 1 50,000 included, then billed
Software software In Cloud KMS, on a key at the SOFTWARE protection level 3 100,000 included, then billed
Hardware hsm In Cloud KMS, on a key at the HSM protection level 2 250,000 included, then billed

Prices are on rypt.dev.

Before you store anything:

  • A Free key allows 10,000 operations a UTC calendar month. At that cap it refuses every operation, decrypt included, with 429 ops_cap_reached until the month ends. Paid keys are not capped: operations past the included number are billed. See the Free tier's monthly cap.
  • A paid key needs the tenant's billing in good standing to be created and to make new ciphertext. See billing refusals.
  • A Free or Extra wrapped key keeps one version, so rotating it makes everything already encrypted under it undecryptable at once, and rewrap cannot prevent that. See rotation.

This encrypts and decrypts hello world with a Free key, using curl and jq.

  1. Sign in at dashboard.rypt.dev. Your first sign-in creates your tenant.
  2. Open Encryption keys and press Create key.
  3. Enter a Name, pick free under Tier, and press Create key.
  4. Open the key by its name in the list, and copy its Key id.

A tenant can hold one Free key, and a Free key pending deletion still holds that place. If the free option is disabled, the tenant already has one, perhaps in another container: use that key (restore it from its page first if it is pending deletion), and issue the API key in its container. See one Free key per tenant.

  1. Open API keys and press Issue API key.
  2. Enter a Name. Under What it may do, in the Use keys group, tick encrypt and decrypt.
  3. Press Issue API key.
  4. Press Copy token and store the token. Tick I have stored this token. and press Done.

The key and the API key must be in the same container. Both pages act on the container chosen in the Container menu of the navigation. A new tenant has one, named Default.

Put both values in your shell:

export RYPT_TOKEN='ry_K3pQ7xTa_...'                       # the token
export KEY_ID='0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0'      # your Key id

aGVsbG8gd29ybGQ= is hello world in base64 (printf 'hello world' | base64).

curl -sS -X POST "https://api.rypt.dev/v1/keys/$KEY_ID/encrypt" \
  -H "Authorization: Bearer $RYPT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"plaintext": "aGVsbG8gd29ybGQ="}' | tee encrypted.json

The response has one field:

{"ciphertext":"...base64..."}

Store the ciphertext: rypt keeps no copy of it. It names the key version it was made under, so decrypt needs only it and the key, plus the same aad if you sent one.

CIPHERTEXT=$(jq -r .ciphertext encrypted.json)
curl -sS -X POST "https://api.rypt.dev/v1/keys/$KEY_ID/decrypt" \
  -H "Authorization: Bearer $RYPT_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"ciphertext\": \"$CIPHERTEXT\"}"
{"plaintext":"aGVsbG8gd29ybGQ="}

To print the text, pipe that response through jq -r .plaintext | base64 --decode, which prints hello world. Each call was one operation of the key's monthly 10,000.

Response Cause
401 unauthorized RYPT_TOKEN is not the full token, or the API key was revoked.
403 action_not_allowed The API key was not given that action. Add it with Restrict on the API keys page.
403 api_key_required The bearer token is a dashboard session, not an API key.
404 not_found KEY_ID is wrong, or the key is in another container than the API key.
409 key_pending_deletion The key was deleted. Press Restore on its page.

rypt.sh is a POSIX shell script that encrypts and decrypts files, or standard input, with encrypt and decrypt. That is direct mode: the file's contents go to rypt, and a file can be at most 65,536 bytes, or 8,176 bytes together with its --aad on a Hardware key. It needs curl and base64, and uses jq when it is installed.

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

The installer puts the script in ~/.local/bin/rypt and, at a terminal, asks for an API key with the input hidden and saves it in ~/.config/rypt/api-key, readable only by you. The script reads the API key from that file, or from RYPT_API_KEY (not RYPT_TOKEN). Give the API key the encrypt and decrypt actions.

rypt keys                                        # the id, tier and name of each key
rypt encrypt --key "$KEY_ID" notes.txt           # writes notes.txt.enc
rypt decrypt --key "$KEY_ID" -o - notes.txt.enc  # prints the plaintext

--key takes a key's name or its id. A .enc file holds the ciphertext exactly as the API returned it, as one line of base64. Without -o, decrypt writes notes.txt, and neither command replaces an existing file unless you pass --force.