# How croc's End-to-End Encryption Works with PAKE: A Deep Dive into the Handshake

> Discover how croc uses PAKE for secure file transfers. Learn how a simple code becomes a strong encryption key via AES-GCM for protected data exchange.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: deep-dive
- Published: 2026-07-23

---

**croc secures file transfers using Password-Authenticated Key Exchange (PAKE) to transform a short human-readable code into a strong symmetric session key that encrypts all data via AES-GCM.**

The open-source tool **schollz/croc** implements a zero-configuration file transfer system where security relies on a PAKE handshake that occurs before any payload transmission. This mechanism ensures that even if an attacker intercepts the network traffic, they cannot brute-force the shared secret or decrypt the transferred files without participating in the live exchange.

## The PAKE Handshake Architecture

The PAKE exchange in croc happens over the control channel immediately after the TCP connection establishes. Both parties—the sender and the receiver—initialize separate PAKE instances using the shared secret (the transfer "code") and exchange public components to derive an identical session key.

### Initializing the Exchange with Distinct Roles

In [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go), the handshake begins when both sides create PAKE instances through the external `github.com/schollz/pake/v3` library. The receiver (role `0`) and sender (role `1`) use different role identifiers to ensure cryptographic separation:

```go
// Receiver initialization (croc.go L288-L291)
c.Pake, err = pake.InitCurve([]byte(c.Options.SharedSecret[5:]), 0, c.Options.Curve)

// Sender initialization (croc.go L1150-L1154)
c.Pake, err = pake.InitCurve([]byte(c.Options.SharedSecret[5:]), 1, c.Options.Curve)

```

The `InitCurve` function takes three parameters: the shared secret bytes (trimmed to remove the first five characters), the role integer, and the elliptic curve name (defaulting to `p256`).

### Exchanging Public Components

After initialization, the receiver transmits its public PAKE value through the unencrypted control channel using a message of type `TypePAKE`. This occurs in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) at lines 1796-1802:

```go
message.Send(c.conn[0], c.Key, message.Message{
    Type:   message.TypePAKE,
    Bytes:  c.Pake.Bytes(),
    Bytes2: []byte(c.Options.Curve),
})

```

The sender receives this payload and updates its own PAKE instance with the receiver's public value (lines 2094-2105):

```go
c.Pake, err = pake.InitCurve([]byte(c.Options.SharedSecret[5:]), 1, string(m.Bytes2))
// ...
err = c.Pake.Update(m.Bytes)

```

### Deriving the Shared Session Key

Once both parties have exchanged public components, each calls `SessionKey()` to compute the identical symmetric key. This 32-byte secret becomes the foundation for all subsequent encryption:

```go
// Both sides execute this (croc.go L1209-L1212)
sessionKey, err := c.Pake.SessionKey()
c.Key = sessionKey

```

After this step, `c.Key` is stored in the client instance and passed to all future `message.Send` and `message.Decode` calls, ensuring that file metadata and payload chunks travel encrypted.

## Cryptographic Implementation Details

### Curve Selection and Parameters

The `c.Options.Curve` field determines which elliptic curve performs the underlying Diffie-Hellman operations. By default, croc uses the NIST P-256 curve, though the `pake` library supports multiple curves via `pake.AvailableCurves()`. The curve name travels alongside the PAKE payload in `Bytes2`, ensuring both parties negotiate identical cryptographic parameters even if defaults differ.

### From PAKE to AES-GCM Encryption

Once the session key is established, croc delegates encryption to the internal `crypt` package ([`src/crypt/crypt.go`](https://github.com/schollz/croc/blob/main/src/crypt/crypt.go)). The `message` package ([`src/message/message.go`](https://github.com/schollz/croc/blob/main/src/message/message.go), lines 40-63) acts as the intermediary:

```go
// Encryption before transmission
encryptedPayload, err := crypt.Encode(bytes, key)

// Decryption upon receipt
decryptedPayload, err := crypt.Decode(raw, key)

```

The `crypt` implementation uses AES-GCM with a random nonce generated for each message. The nonce prepends the ciphertext, allowing the receiver to decrypt without additional out-of-band data. This design provides **authenticated encryption**, detecting any tampering with transmitted chunks.

## Error Handling and Security Guarantees

If any stage of the PAKE handshake fails—whether through curve mismatch, secret disagreement, or network corruption—croc aborts the transfer immediately. The error handling at lines 314-322 in [`croc.go`](https://github.com/schollz/croc/blob/main/croc.go) wraps these failures as `pakeHandshakeError`, presenting users with a clear "pake not successful" message rather than cryptic cryptographic failures.

This architecture provides three critical security properties:

- **Password Authentication**: The low-entropy "code" authenticates both parties without exposing the secret to offline dictionary attacks, thanks to PAKE's interactive nature.
- **Forward Secrecy**: Each transfer generates a fresh session key; compromising the shared secret after transmission completion does not expose historical file contents.
- **No Pre-Shared Keys**: Users need only agree on the human-readable transfer code, while the heavy cryptographic lifting (ECDH key agreement) happens automatically.

## Code Walkthrough: The Complete Handshake

Below is a consolidated view of how croc implements the PAKE handshake in practice, combining logic from [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) and [`src/message/message.go`](https://github.com/schollz/croc/blob/main/src/message/message.go):

```go
// Receiver side (role 0)
receiverPake, _ := pake.InitCurve([]byte(secret[5:]), 0, "p256")
msg := message.Message{
    Type:   message.TypePAKE,
    Bytes:  receiverPake.Bytes(),
    Bytes2: []byte("p256"),
}
message.Send(conn, nil, msg) // Sent without encryption (key is nil)

// Sender side (role 1)
senderPake, _ := pake.InitCurve([]byte(secret[5:]), 1, "p256")
// Receive the PAKE payload
var incoming message.Message
message.Decode(nil, rawBytes, &incoming)
senderPake.Update(incoming.Bytes)
sessionKey, _ := senderPake.SessionKey()

// All subsequent messages use the derived key
fileMsg := message.Message{Type: message.TypeFileInfo, Bytes: fileData}
message.Send(conn, sessionKey, fileMsg) // AES-GCM encrypted

```

## Summary

- **croc uses the external `github.com/schollz/pake/v3` library** to perform Password-Authenticated Key Exchange before transmitting any file data.
- **Role differentiation** (receiver as `0`, sender as `1`) ensures cryptographic separation during the `pake.InitCurve` initialization in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go).
- **Public component exchange** happens via unencrypted `TypePAKE` messages that carry the curve name and PAKE bytes.
- **Session key derivation** occurs through `SessionKey()`, producing a 32-byte symmetric key stored in `c.Key` for the transfer duration.
- **AES-GCM encryption** protects all subsequent communication through the `crypt` package, using the PAKE-derived key for both file metadata and payload chunks.
- **Failure handling** immediately aborts transfers if the handshake fails, preventing silent security degradation.

## Frequently Asked Questions

### What is PAKE and why does croc use it?

**PAKE (Password-Authenticated Key Exchange) is a cryptographic protocol that allows two parties to establish a shared secret key using a low-entropy password, without exposing the password to offline brute-force attacks.** croc uses PAKE because it allows users to authenticate transfers using simple, human-readable codes (like "croc1234") while still achieving strong encryption equivalent to high-entropy keys. The protocol ensures that an eavesdropper observing the network traffic gains no advantage in guessing the shared secret.

### How does croc prevent man-in-the-middle attacks during the PAKE handshake?

**croc mitigates man-in-the-middle attacks through the interactive nature of PAKE combined with the shared secret requirement.** Since both parties must possess the identical transfer code to derive the same session key, an attacker who intercepts the connection cannot establish a valid encrypted channel without knowing the code. Additionally, if the PAKE exchange produces mismatched keys (detected when subsequent encrypted messages fail to decrypt), the transfer aborts immediately with a handshake error.

### What encryption algorithm protects the actual file data after the PAKE handshake completes?

**After PAKE derives the session key, croc uses AES-GCM (Galois/Counter Mode) to encrypt all file data and control messages.** The implementation in [`src/crypt/crypt.go`](https://github.com/schollz/croc/blob/main/src/crypt/crypt.go) creates a new AES cipher from the 32-byte PAKE session key, wraps it in GCM mode for authenticated encryption, and generates a random nonce for each message. This ensures both confidentiality and integrity for every chunk of transferred data.

### Can I use a different elliptic curve for the PAKE exchange in croc?

**Yes, croc supports multiple elliptic curves through the `pake` library's `AvailableCurves()` function, configurable via the `--curve` flag.** While the default is `p256` (NIST P-256), users can specify alternative curves during client initialization. The chosen curve name travels in the `Bytes2` field of the initial PAKE message, ensuring both sender and receiver negotiate the same curve parameters regardless of local defaults.