Security model
This page sets out what rypt sees of your data, the path a request takes, where the cryptography runs, what stays in memory and for how long, what is logged and stored, and what rypt does not offer. It describes api.rypt.dev as it runs today. In the memory sections, zeroed means rypt overwrites a buffer with zeros when it drops it, and freed, not zeroed means the buffer goes back to the memory allocator with its contents in place, where they stay until something else reuses that memory.
What rypt sees
Section titled "What rypt sees"rypt is not zero-knowledge. It sees what it encrypts:
encryptreceives your plaintext in the request, anddecryptreturns it in the response.wrapreceives the data key you send, or generates one and returns it;unwrapreturns the data key.rewrapreturns no plaintext to you, but the plaintext passes through rypt's memory between its decryption and its re-encryption.- Every operation receives the
aadyou send.
In envelope mode the data you encrypt on your own machine, under a data key, never
reaches rypt: only the data key, the wrapped data key and the aad travel.
Direct or envelope explains the two modes.
rypt, which x6c LLC operates, holds the keys that decrypt your data and can use them. rypt offers no key that it cannot use; see what rypt does not offer.
The request path
Section titled "The request path"- DNS. Cloudflare serves the DNS records for api.rypt.dev. It is not in the request path.
- TLS. Your client connects to Google's global external Application Load Balancer,
which terminates TLS (version 1.2 or later). The load balancer handles the request
decrypted: your plaintext or data key, your
aadand your API key. It is global, so where your TLS connection ends is not pinned to us-central1. - The service. The load balancer forwards the request to rypt's service on Google Cloud Run in us-central1. rypt does not terminate TLS itself.
- Cloud KMS. On Software (
software) and Hardware (hsm) keys, your plaintext or data key travels between rypt and Cloud KMS over TLS: rypt sends it to be encrypted, or receives it back from a decryption. The Cloud KMS keys are in us-central1.
No secret travels in a URL. Plaintext, data keys and aad travel only in request and
response bodies, and the API key only in the Authorization header. A URL carries at
most a key id and query parameters.
Where the cryptography runs
Section titled "Where the cryptography runs"| Tier | Where encryption runs | What reaches Cloud KMS |
|---|---|---|
Free (free) |
In rypt's process, AES-256-GCM | Only each key version's own data key, to be wrapped or unwrapped by the root key |
Extra wrapped (wrapped) |
In rypt's process, AES-256-GCM | The same as Free |
Software (software) |
Cloud KMS, SOFTWARE protection level | Your plaintext or data key (encrypt, wrap), or your ciphertext or wrapped data key (decrypt, unwrap), with your tenant id and aad; Cloud KMS returns the plaintext or data key to rypt |
Hardware (hsm) |
Cloud KMS, HSM protection level | The same as Software |
Free and Extra wrapped keys. Each key version has its own random 256-bit data key. rypt stores it in its database, wrapped under one root key held in Cloud KMS at the HSM protection level, which every Free and Extra wrapped key shares. Encryption and decryption run in rypt's process with that data key, so the HSM protects the root key, not the operation. Cloud KMS is called only to wrap a key version's data key and to unwrap one that is not in the cache. Your plaintext and the data keys you wrap do not go to Cloud KMS on these tiers. The key version's data key is in rypt's memory, unwrapped, while it is in use and while it is cached, and copies of it can outlive both; see plaintext in memory.
Software and Hardware keys. Each rypt key is a Cloud KMS key of its own, in
us-central1, in a key ring for your account. The operation runs in Cloud KMS, which
receives your plaintext or data key. rypt puts your tenant id in front of your aad
before sending it; see
the tenant binding.
rewrap. On Software and Hardware keys, Cloud KMS returns the plaintext to rypt and rypt sends it back to Cloud KMS to be encrypted under the current version. On Free and Extra wrapped keys both halves run in rypt's process. Either way the plaintext is in rypt's memory for the length of the call, and is never returned to you.
Plaintext in memory
Section titled "Plaintext in memory"The secrets rypt handles in a request are the plaintext you send to encrypt, the
plaintext decrypt returns, the data keys you send to wrap, the data keys wrap
generates and unwrap returns, and your API key. Inside rypt there are two more: the
plaintext rewrap recovers, and on Free and Extra wrapped keys each key version's data
key. rypt does not treat aad as secret.
rypt's service is written in Rust and zeroes with the zeroize library, which overwrites a value when rypt drops it. That reaches only the memory the value occupies when it is dropped. Moving a fixed-size value, such as a Free or Extra wrapped key version's 32-byte data key, copies it, and the zeroize library's documentation warns that the compiler may leave such copies on the stack; those are not zeroed. Your plaintext and the data keys you send or receive are held in heap buffers, which a move does not copy.
Zeroed
Section titled "Zeroed"- The decoded bytes of the
plaintextyou send toencrypt, right after the encryption call returns. - The decoded bytes of the
dekyou send towrap, and a data keywrapgenerates, right after the response is built. - The text of the
plaintextordekfield as parsed from your JSON. rypt overwrites this base64 string in place right after decoding it, and also when decoding fails. - The plaintext
decryptrecovers and the data keyunwraprecovers, right after their base64 for the response is made. The plaintextrewraprecovers, right after it is encrypted again. - On Software and Hardware keys, the recovered values above are rypt's own copies of what Cloud KMS returns. The Cloud KMS client's response they were copied from is not zeroed (see below).
- On Free and Extra wrapped keys, the copies of a key version's data key that rypt's own code holds: the one it generates or unwraps, the one it takes from the cache for each operation, and an entry the cache pushes out when it is full. The AES key schedule expanded from a data key is zeroed too. The cache's own copy is not always zeroed; see below.
- Your API key, as rypt copies it out of the
Authorizationheader to check it. - A new API key's token, in the buffer rypt generates it in. The copy in the response is not zeroed (see below).
Freed, not zeroed
Section titled "Freed, not zeroed"- The raw request body, as rypt received it, in every request. It holds the base64 of your plaintext or data key. Overwriting the parsed field does not reach this copy.
- The response. The base64
plaintextfromdecrypt, thedekfromwrap(when rypt generated it) and fromunwrap, and the serialized response body. - The parsed field when a request ends before decoding it. The base64
plaintextordekstring is freed without being overwritten when the body fails JSON validation, when the key is unknown (404) or pending deletion (409), and, onencryptandwrap, whenaadis not valid base64 or is too long, because rypt checksaadfirst. When theplaintextordekfield itself is not valid base64, the bytes decoded before the error are freed, not zeroed. - Everything about
aad: the string, the decoded bytes, and the copies rypt builds with its binding in front. - The
Authorizationheader as received, which holds your API key. - A new API key's token as written into the response to the dashboard.
- The cache's copy of a data key, when its entry expires or its version is scheduled for destruction. On those removals the cache library moves the entry out of its heap slot and frees the slot without overwriting it. rypt zeroes the moved copy, but the slot is freed with the data key still in it. Only an entry pushed out because the cache is full has its slot overwritten, by the entry that replaces it.
- Copies inside libraries: the HTTP server's and the JSON parser's buffers; the copy rypt hands the Cloud KMS client, and that client's request, response and TLS buffers; and the working memory of the Argon2 and SHA-256 code that checks your API key. On Software and Hardware keys the Cloud KMS buffers hold your plaintext or data key. On Free and Extra wrapped keys, a key version's data key passes through them each time it is wrapped or unwrapped.
What rypt does not do
Section titled "What rypt does not do"rypt does not lock its memory against paging (there is no mlock), does not turn off
core dumps, and uses the default memory allocator, which does not clear freed memory.
rypt makes no statement about whether Google Cloud Run pages the service's memory to
disk or writes it out in a crash dump.
The cache of unwrapped key versions
Section titled "The cache of unwrapped key versions"Unwrapping a key version's data key on a Free or Extra wrapped key is a Cloud KMS call, so rypt keeps unwrapped data keys in memory for a short time:
- There is one cache per process. The service runs on 1 to 4 Cloud Run instances, each with a cache of its own.
- A cache holds up to 10,000 entries, one per key version, across all accounts.
- An entry expires 300 seconds (5 minutes) after it was cached. Using it does not extend that, and rypt never uses an expired entry.
An entry is removed when it is read after it has expired, when a sweep that runs every 5 minutes finds it expired, when the cache is full and it is the least recently used, or when its key version is scheduled for destruction. That last removal happens only on the instance that handled the request; the other instances stop using the version at once, because every operation reads the version's state from the database, but keep their copy until it expires and is swept.
Removal zeroes the copy of the data key that rypt gets back from the cache. Except when the cache is full, it leaves the cache's own copy in freed memory; see freed, not zeroed.
How soon removal happens is best effort. While an instance is handling requests, an entry leaves the cache within about 10 minutes of being cached: its 5 minutes, plus at most one sweep interval. Cloud Run gives the service CPU only while it handles requests, so on an idle instance the sweep can run late, and an expired data key stays in the cache, unused, until it does.
There is no master key held for the life of the process: the root key stays in Cloud KMS. The data keys in an instance's memory are the ones it has generated or unwrapped: those in its cache, and copies in freed memory that has not yet been reused. A key version the instance has not used is not in its memory. That does not bound what a disclosure of its memory reaches: the memory also holds the credentials rypt uses to call Cloud KMS, which can use every Software and Hardware key and the root key.
rypt's service logs to Google Cloud Logging, for the operator. Its lines about your requests include:
- Request log: one line per request, with the method, the URL (path and query), the HTTP version, the request id, the status and the latency.
- Usage line: one per
encrypt,decrypt,wrap,unwraporrewrapthat got past the key lookup, successful or not. It has your tenant id, the operation, the result (okorerror) and the input's length in bytes when it is known, plus the request log's method, URL (which names the key), HTTP version and request id. Usage lines are copied to BigQuery in the US multi-region and kept indefinitely. - Errors: for a
500 internal_error, the internal cause, with the request id. It is never sent to you. - Refusals by an API key's key list or actions: the API key's id, the key id and, for
actions, the action. On
POST /v1/keys, which names no key, a refusal for thecreateaction names the API key's container instead, and a refusal by the key list has no line of its own. By its firewall: the API key's id, your tenant id and the source address.
rypt's code writes no plaintext, ciphertext, data keys, aad, API keys, request or
response bodies, or headers to any log line, except that an x-request-id you send
becomes the logged request id. An API key appears only as its id, or in a
debug-level line as its 8-character prefix, which rypt stores in clear. The request and
response types of the five operations implement neither Debug nor Clone, so code
that tried to print or copy one would not compile.
Other lines record events such as key creation and destruction, and billing, by id. Google's load balancer also logs every request, for the operator.
The audit log is a separate record, kept in rypt's database. It records the length of an operation's input, never its content, and your source address. See the audit log.
Storage
Section titled "Storage"rypt's database is Postgres on Google Cloud SQL, in us-central1. It holds:
- account records: your tenant, dashboard users (sign-in id, email and name), containers, firewalls and billing state;
- your keys' metadata: name, tier, container, versions and state;
- for Free and Extra wrapped keys, each key version's data key, wrapped under the root key;
- for Software and Hardware keys, the name of the Cloud KMS key, whose material stays in Cloud KMS;
- API keys: an Argon2id hash of the whole token, the 8-character prefix in clear (to find the row), the name and the restrictions;
- audit rows, usage counters, and the record of API key permission changes.
It never holds plaintext, ciphertext, aad, the data keys you wrap or receive, or an
API key's token. rypt stores a new API key's token nowhere: it returns it once, in the
response that creates it, and the dashboard shows it once.
API key hashes. Each token is hashed with Argon2id, version 0x13, 19 MiB of memory (19,456 KiB), 2 iterations and a parallelism of 1, with a random salt per API key. Verification uses the parameters stored with the hash. To avoid hashing on every request, rypt remembers for 60 seconds that a token verified, as a salted SHA-256 digest in process memory, never the token itself. Revocation is checked before that memory on every request, so it cannot keep a revoked API key working.
Encryption at rest. The database is encrypted with a Cloud KMS key of its own at the HSM protection level, separate from the root key. It has no public IP address and accepts only encrypted connections.
Destroyed key versions. When a version of a Free or Extra wrapped key is destroyed, rypt erases its wrapped data key from the database. Database backups can still hold it, wrapped, until they expire.
Browser access
Section titled "Browser access"The API sends CORS headers for one origin, https://dashboard.rypt.dev. The list is
exact, never *, and does not allow credentials. rypt sets no cookies and reads none.
Every authenticated call carries an Authorization header, so a browser sends a
preflight request first. For a page on any other origin rypt does not approve the
preflight, and the browser does not send the request. Call rypt from your server, which
also keeps your API key out of your users' browsers. CORS binds browsers only: any other
client can call the API with a valid API key.
Credentials
Section titled "Credentials"The five operations accept one credential, an API key sent as a bearer token
(Authorization: Bearer ry_K3pQ7xTa_...). There is no mutual TLS, no client
certificate, no request signing and no cookie. Whoever holds the token can use it,
within its restrictions and from wherever its firewall
allows. In transit, TLS is its only protection.
The dashboard signs you in through Auth0 and calls the API with a dashboard session. A
dashboard session cannot run the five operations, so the dashboard never handles your
plaintext. Of the two credentials, only a dashboard session can issue, restrict or
revoke an API key. The dashboard keeps its session tokens in memory, not in browser
storage. Every dashboard page loads Google Analytics, a script from
www.googletagmanager.com, including the page that shows a new API key's token.
If an API key leaks
Section titled "If an API key leaks"Revoke it in the dashboard. From its next request it is refused with
401 unauthorized; see revoking an API key.
Until then it can do what its actions allow, and also what
actions do not restrict, within
its container, key list and firewall. Each key's audit log names the API key in the
rows it caused; the audit log says which events are recorded.
What rypt does not offer
Section titled "What rypt does not offer"- Customer-managed keys or key import. rypt creates every key. You cannot import
your own key material, hold a rypt key in your own cloud account, or get key material
out: no call returns it.
wraptakes a data key you generate, but that is a data key rypt sees, not a rypt key. - Asymmetric keys. Every rypt key is AES-256, for encryption and decryption only. There are no asymmetric or signing keys.
- Compliance attestations. rypt makes no compliance claim and offers no attestation or certification report. The audit log is evidence; the conclusions are your auditor's.
- A region choice. Cloud KMS keys, the root key, the service and its database are in us-central1. TLS termination, at a global load balancer, and database backups, in Cloud SQL's default backup location, are not pinned there. Usage lines are in BigQuery's US multi-region. There is no other region and no multi-region option.