# How croc's --store Feature Handles Encrypted Temporary File Storage

> Discover how croc's --store feature secures your files with local AES-GCM encryption before temporary storage. Protect your data from prying eyes.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: internals
- Published: 2026-07-30

---

**croc encrypts all files locally using AES-GCM with HKDF-derived keys before uploading to temporary storage, ensuring the server only handles ciphertext and never accesses plaintext or encryption keys.**

The `schollz/croc` secure file transfer tool provides a `--store` flag that enables encrypted temporary file storage, allowing users to queue transfers that persist until retrieval or expiration. This feature uploads encrypted data to a relay storage service while maintaining strict zero-knowledge guarantees. The architecture ensures that all cryptographic operations occur client-side, rendering the temporary storage server incapable of decrypting or inspecting transferred content.

## Client-Side Encryption Architecture

### Master Key Generation and HKDF Derivation

When initiating a stored transfer, croc generates a cryptographically secure 256-bit master key using [`storecrypto.GenerateKey`](https://github.com/schollz/croc/blob/main/src/storecrypto/storecrypto.go#L70-L77). This key serves as the root secret from which all purpose-specific subkeys are derived using HKDF (HMAC-based Extract-and-Expand Key Derivation Function).

### Redeem Capabilities and Authorization Tokens

The system creates a **redeem capability** via [`storecrypto.RedeemCapability`](https://github.com/schollz/croc/blob/main/src/storecrypto/storecrypto.go#L99-L104), which derives a 32-byte token authorizing read access without revealing decryption keys. This capability enables the storage service to verify download permissions while maintaining the zero-knowledge property.

## Encrypting Manifests and File Chunks

### Manifest Encryption with Transfer Binding

Before any file data uploads, croc encrypts the JSON manifest containing filenames, sizes, and SHA-256 hashes using [`storecrypto.SealManifest`](https://github.com/schollz/croc/blob/main/src/storecrypto/storecrypto.go#L72-L82). This function employs **AES-GCM** AEAD encryption with associated data (AAD) that binds the manifest to the specific transfer ID, preventing substitution attacks.

### Chunk-Level AEAD Protection

Files are split into 4 MiB blocks, with each chunk encrypted independently via [`storecrypto.SealChunk`](https://github.com/schollz/croc/blob/main/src/storecrypto/storecrypto.go#L31-L38). Each chunk receives unique AAD containing the chunk index and transfer ID, ensuring integrity at the block level and preventing reordering attacks.

## Uploading to Encrypted Temporary Storage

### Storage Reservation and Metadata

The client initiates the upload process by calling [`storeclient.Client.createUpload`](https://github.com/schollz/croc/blob/main/src/storeclient/client.go#L55-L88), which POSTs encrypted manifest size, per-chunk sizes, and the redeem capability verifier to the storage service. The server returns a transfer ID and upload token without receiving any plaintext or cryptographic material.

### Encrypted Data Transmission

Using [`storeclient.Client.uploadObjects`](https://github.com/schollz/croc/blob/main/src/storeclient/client.go#L114-L145) and [`storeclient.Client.uploadFile`](https://github.com/schollz/croc/blob/main/src/storeclient/client.go#L258-L286), croc uploads the ciphertext manifest and encrypted chunks via authenticated HTTP PUT requests. The `putWithRetry` mechanism ensures reliable delivery of the encrypted payload without exposing plaintext to network errors.

### Finalization and Local Receipts

After successful upload, [`storeclient.Client.completeUpload`](https://github.com/schollz/croc/blob/main/src/storeclient/client.go#L218-L236) finalizes the transfer on the server. The CLI layer saves a local receipt via [`saveStoreReceipt`](https://github.com/schollz/croc/blob/main/src/cli/store.go#L82-L90) in [`store-receipts.json`](https://github.com/schollz/croc/blob/main/store-receipts.json), preserving the upload token for subsequent revocation.

## Receiving and Revoking Stored Transfers

### Decryption Process

Recipients parse the share token using `storecrypto.ParseShare` to extract the master key and transfer metadata. The client retrieves the encrypted manifest using the redeem capability, decrypts it with `storecrypto.OpenManifest`, then downloads and decrypts each chunk using [`storecrypto.OpenChunk`](https://github.com/schollz/croc/blob/main/src/storecrypto/storecrypto.go#L40-L48), verifying SHA-256 checksums before atomic file installation.

### Revocation Mechanism

Senders can revoke unclaimed transfers using `croc --revoke <id>`, which utilizes the locally stored receipt to authenticate a DELETE request to the storage service, immediately invalidating the encrypted temporary files regardless of their remaining lifetime.

## Practical CLI Usage for Encrypted Storage

Send files to encrypted temporary storage:

```bash
croc send --store document.pdf photos/

```

Output includes the encrypted browser link and transfer token:

```text
Stored transfer is encrypted and available until Thu, 01 Aug 2026 12:34:56 GMT
Browser link:
    https://store.example.com/s/ABCD1234#v1.<base64-master-key>
CLI recipient:
    croc-store-v1.<base64-origin>.<transfer-id>.<base64-master-key>

```

Receive the encrypted files:

```bash
croc

# Paste the token when prompted

```

Revoke before download:

```bash
croc --revoke ABCD1234

```

## Summary

- **Client-side encryption** uses AES-GCM with keys derived via HKDF from a 256-bit master key generated per transfer.
- The **storage server** only handles ciphertext and redeem capabilities, maintaining zero-knowledge security.
- **Manifest and chunk-level integrity** is enforced through transfer-specific AAD bindings that prevent tampering and reordering.
- **Local receipt storage** in [`store-receipts.json`](https://github.com/schollz/croc/blob/main/store-receipts.json) enables secure revocation and transfer management.
- All cryptographic operations in `schollz/croc` occur before data reaches the network, ensuring encrypted temporary file storage remains confidential even if the remote service is compromised.

## Frequently Asked Questions

### How does croc ensure the storage server cannot access my files?

All encryption occurs client-side before transmission. The server receives only AES-GCM ciphertext and redeem capabilities (authorization tokens), while the master encryption key remains exclusively with the sender and recipient through the share token. The server cannot decrypt content because it never possesses the HKDF-derived keys used for manifests or chunks.

### What happens if a chunk upload fails during transfer?

The retry logic in [`storeclient/client.go`](https://github.com/schollz/croc/blob/main/storeclient/client.go) automatically handles transient HTTP PUT failures for encrypted chunks, ensuring complete encrypted payload delivery without exposing plaintext to network errors. The client validates each upload before finalizing the transfer via `completeUpload`.

### How long do encrypted files remain in temporary storage?

Transfers persist until the expiration time returned by `Client.completeUpload`, typically 24 hours, or until explicitly revoked using the local receipt saved by `saveStoreReceipt`. The encrypted data is automatically purged by the service after this period regardless of download status.

### Why does croc use HKDF instead of using the master key directly?

HKDF enables separation of concerns by deriving distinct keys for manifests, data chunks, and redeem capabilities from the single master key. This prevents cryptographic key reuse across different contexts and ensures that compromise of one component (such as the redeem capability) does not endanger the confidentiality of file contents.