How Croc's PAKE Encryption Works: Secure Key Exchange in File Transfers

Croc uses a Password Authenticated Key Exchange (PAKE) protocol to convert a short human-readable code into a strong symmetric session key, establishing an encrypted AES-GCM channel before any file data is transmitted.

Croc is an open-source command-line file transfer tool written in Go that secures connections using PAKE encryption. According to the schollz/croc source code, the protocol transforms a low-entropy shared secret—displayed to the user as the "code"—into a cryptographically secure key via an elliptic-curve Diffie-Hellman exchange. This mechanism ensures that even if a malicious actor intercepts the network traffic, they cannot brute-force the password or decrypt the payload offline.

Initializing the PAKE Handshake

The exchange begins when both the sender and receiver initialize PAKE instances using the external github.com/schollz/pake/v3 library. Each party calls pake.InitCurve() with the shared secret truncated to remove the first five characters (c.Options.SharedSecret[5:]), which strips the transfer identifier prefix.

Receiver Initialization (Role 0)

The receiving party creates a PAKE instance with role 0 to indicate it is the non-sender. In src/croc/croc.go, the code initializes the curve as follows:

c.Pake, err = pake.InitCurve([]byte(c.Options.SharedSecret[5:]), 0, c.Options.Curve)

This generates the receiver's private and public elliptic curve values based on the shared secret and the selected curve (defaulting to p256).

Sender Initialization (Role 1)

The sender initializes its own instance with role 1, distinguishing its position in the exchange:

B, _ := pake.InitCurve([]byte(c.Options.SharedSecret[5:]), 1, c.Options.Curve)

Both roles use the same shared secret input, but the differing role integers ensure the mathematical operations produce complementary results that converge on an identical session key.

Exchanging Public PAKE Values

After initialization, the parties transmit their public elliptic curve points over the TCP control channel. These messages are sent unencrypted; the security relies on the PAKE protocol's mathematical properties rather than transport-layer encryption.

Transmitting the PAKE Payload

The receiver sends a message of type TypePAKE containing its public bytes and the curve name via src/message/message.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 message and updates its local PAKE instance with the receiver's public value:

c.Pake.Update(m.Bytes)

This Update() call incorporates the counterparty's public component into the local state, allowing both sides to compute the same shared secret.

Deriving the Session Key

Once the public values are exchanged, both parties derive the symmetric encryption key by calling SessionKey(). This method returns a 32-byte shared secret (kA on the sender, kB on the receiver) that serves as the AES-256-GCM key for all subsequent communication.

From src/croc/croc.go, the derivation occurs as:

kA, err = A.SessionKey()
// Corresponding receiver call:
kB, err = B.SessionKey()

The derived key is stored in the connection context (c.Key) and passed to the encryption layer. At this point, the PAKE handshake is complete, and the channel is cryptographically secured.

Securing the Data Channel with AES-GCM

After the PAKE handshake, croc uses the session key to encrypt all file metadata, chunk data, and control messages. The src/crypt/crypt.go package provides a wrapper around AES-GCM, automatically generating random nonces and authenticating ciphertext.

When transmitting file information, the code encrypts the payload using the established session key:

msg := message.Message{
    Type:  message.TypeFileInfo,
    Bytes: fileInfoJSON,
}
message.Send(conn, sessionKey, msg)

The message.Send function in src/message/message.go encodes the message using crypt.Encrypt, which prepends the nonce to the ciphertext. Subsequent messages—including file chunks and transfer completion signals—use this same encrypted pipeline, ensuring end-to-end confidentiality.

Error Handling and Security Guarantees

If any step of the PAKE handshake fails—such as a mismatch in shared secrets, unsupported curve parameters, or network corruption—the connection is terminated immediately. The error is wrapped as a pakeHandshakeError (visible in src/croc/croc.go around lines 314–322), preventing the transfer from falling back to unencrypted communication.

The PAKE protocol provides forward secrecy: each transfer generates a unique session key derived from ephemeral curve parameters. Past transfers remain confidential even if the shared secret is compromised later. Additionally, PAKE prevents offline dictionary attacks; an attacker must actively participate in a protocol exchange to test a password guess.

Summary

  • Croc implements PAKE via the schollz/pake/v3 library to bootstrap encryption from a short human-readable code.
  • Both sender and receiver call pake.InitCurve() with roles 1 and 0 respectively, using c.Options.SharedSecret[5:] as the password.
  • Public PAKE values are exchanged via TypePAKE messages in src/croc/croc.go before any file payload is transmitted.
  • The SessionKey() method generates a 32-byte symmetric key used by the src/crypt/crypt.go package for AES-GCM encryption.
  • All subsequent communication in src/message/message.go is encrypted with this session key, ensuring the data channel remains confidential and tamper-evident.

Frequently Asked Questions

What happens if the PAKE handshake fails during a croc transfer?

If the PAKE handshake fails—typically due to mismatched codes or network interruption—croc aborts the connection immediately and returns a handshake error. The transfer does not proceed, ensuring no file data is transmitted over an unencrypted or unauthenticated channel.

Why does croc use PAKE instead of traditional TLS or SSH?

PAKE allows croc to establish mutual authentication using only a short, human-memorable code without requiring pre-shared certificates, key files, or PKI infrastructure. This design eliminates the complexity of certificate management while mathematically preventing offline brute-force attacks against the password.

Which elliptic curves does croc support for PAKE?

Croc defaults to the P-256 curve but supports any curve returned by pake.AvailableCurves(). The specific curve is passed as the third argument to InitCurve() and is transmitted during the initial PAKE exchange via the Bytes2 field of the message, ensuring both parties agree on the cryptographic parameters.

Is the initial PAKE message encrypted?

No, the initial PAKE exchange containing public elliptic curve points is sent unencrypted over the TCP control channel. The security of the protocol relies on the PAKE construction itself, which ensures that an eavesdropper observing these public values cannot derive the shared secret or the resulting session key without knowing the password.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →