How Croc's PAKE End-to-End Encryption Works Internally

Croc establishes secure file transfers by implementing a Password-Authenticated Key Exchange (PAKE) protocol that transforms a short human-readable code into a strong symmetric session key, encrypting all subsequent communications with AES-GCM.

Croc is an open-source command-line file transfer tool written in Go that enables secure data exchange between computers without pre-shared keys. At the heart of its security model lies a Password-Authenticated Key Exchange (PAKE) implementation that protects the confidentiality and integrity of transferred files. This article examines the internal mechanics of croc's PAKE end-to-end encryption by analyzing the source code in the schollz/croc repository.

The PAKE Handshake Architecture

Croc's encryption workflow begins before any file data is transmitted, utilizing the github.com/schollz/pake/v3 library to perform an authenticated key exchange over the initial insecure channel.

Role Initialization and Curve Selection

The protocol distinguishes between the sender (role 1) and receiver (role 0) during initialization. In src/croc/croc.go, both parties instantiate PAKE instances using the shared secret derived from the user-provided transfer code.

Receivers initialize with role 0:

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

Senders initialize with role 1:

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

The curve parameter defaults to P-256 but supports multiple elliptic curves available through pake.AvailableCurves().

Public Value Exchange

After initialization, the receiver transmits its public PAKE components unencrypted via the control channel. The message uses type TypePAKE and includes the selected curve name for verification.

In src/croc/croc.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 local PAKE instance with the receiver's public value:

c.Pake.Update(m.Bytes)

Session Key Derivation

Once both parties have exchanged public components, each derives the identical symmetric session key by calling SessionKey(). This 32-byte key materializes independently on both ends without ever traversing the network.

sessionKey, err = c.Pake.SessionKey()

The resulting key establishes the foundation for all subsequent encrypted communications.

Securing the Channel with AES-GCM

Following successful PAKE completion, croc transitions to symmetric encryption using the derived session key. The internal crypt package implements AES-GCM encryption, creating random nonces for each message.

When transmitting file information or data chunks, the code passes the session key to message.Send, which invokes crypt.Encrypt automatically:

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

The encryption process in src/crypt/crypt.go generates fresh nonces, encrypts content with cipher.NewGCM(aes.NewCipher(key)), and prepends the nonce to the ciphertext for decryption on the receiving end.

Security Properties and Error Handling

PAKE provides critical security guarantees that differentiate croc from basic password-based systems. The protocol offers password authentication without exposing the secret to offline brute-force attacks, forward secrecy through ephemeral key generation per transfer, and mutual authentication ensuring both parties possess the shared code.

If the PAKE handshake fails due to mismatched secrets or protocol violations, src/croc/croc.go encapsulates the error and aborts the transfer:

if err != nil {
    return fmt.Errorf("pake handshake failed: %w", err)
}

Summary

  • Croc implements PAKE through the external github.com/schollz/pake/v3 library, initializing separate instances for senders (role 1) and receivers (role 0) in src/croc/croc.go.
  • The handshake exchanges public PAKE values via TypePAKE messages before deriving a shared 32-byte symmetric key using the SessionKey() method.
  • All subsequent communications encrypt using AES-GCM via the internal crypt package, with the PAKE-derived key serving as the encryption root.
  • The protocol prevents offline brute-force attacks against the human-readable code while providing forward secrecy for each transfer session.

Frequently Asked Questions

What cryptographic curve does croc use for PAKE?

Croc defaults to the P-256 elliptic curve but supports multiple curves available in the underlying pake library. The specific curve name transmits during the initial handshake in the Bytes2 field of the PAKE message, allowing both parties to negotiate compatible parameters as implemented in src/croc/croc.go.

How does croc prevent brute-force attacks on the transfer code?

PAKE protocols mathematically prevent attackers from verifying password guesses against captured handshake messages. Because the shared secret never transmits directly across the network, offline dictionary attacks become computationally infeasible without interacting with the legitimate peer, providing cryptographic resistance to brute-force attempts.

What happens if the PAKE handshake fails during a transfer?

The connection aborts immediately with a clear error message. In src/croc/croc.go, failed PAKE operations return wrapped errors that terminate the transfer before any file data exchanges, preventing downgrade attacks or unauthorized access attempts while alerting users to authentication failures.

Does croc use the PAKE key directly for file encryption?

Yes, the 32-byte session key derived from SessionKey() serves directly as the AES-GCM encryption key for the entire session. Both control messages and file payloads pass through crypt.Encrypt and crypt.Decrypt functions using this shared secret, ensuring end-to-end confidentiality throughout the transfer.

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 →