How Croc Handles Secure File Transfers Without Port Forwarding: Relay Architecture Explained

Croc achieves secure file transfers without port forwarding by routing all traffic through a public relay server that acts as a TCP-level proxy, while Password-Authenticated Key Agreement (PAKE) and AES-GCM encryption ensure end-to-end security that the relay cannot compromise.

Croc is an open-source command-line tool developed by schollz that enables encrypted file sharing between any two computers without configuring firewalls or routers. Understanding how croc handles secure file transfers without port forwarding requires examining its unique relay-based architecture, which masks NAT complexity through outbound connections and cryptographic handshakes. The implementation leverages specific packages in the schollz/croc repository to establish secure channels that completely bypass the need for manual port forwarding.

The Relay-Only Transport Architecture

Instead of requiring peers to accept inbound connections, croc routes all traffic through a publicly reachable relay server. When a sender initiates a transfer with croc send, the client establishes an outbound TCP connection to the configured relay address, defaulting to croc.schollz.com on ports 9009, 9010, and subsequent ports as defined in src/models/constants.go.

Because both the sender and receiver initiate outbound connections to this relay, typical home routers and NAT devices allow the traffic through automatically. The relay acts solely as a dumb TCP pipe, forwarding encrypted bytes between peers without ever holding the decryption keys. This architecture eliminates the need for manual firewall configuration or port forwarding rules on either end of the transfer.

The relay configuration also supports IPv6-first connectivity with automatic IPv4 fallback, configured through the address defaults in src/models/constants.go (lines 94-101). The repository additionally provides src/webrelay/webrelay.go, which exposes an embedded web client that bridges HTTP/WebSocket connections to the same TCP relay infrastructure, enabling browser-based transfers without installing the CLI.

Password-Authenticated Key Agreement (PAKE)

Once the TCP connection to the relay is established, croc performs a cryptographic handshake to generate a shared secret that the relay cannot access. Inside src/tcp/tcp.go, the implementation calls pake.InitCurve to initiate the PAKE exchange (lines 78-84).

This handshake creates a strong session key (strongKey) derived from the user's code phrase without transmitting the key itself over the network. The PAKE protocol guarantees that even if an attacker controls the relay or eavesdrops on the connection, they cannot derive the session key or decrypt the subsequent communication. This step is crucial for establishing trust between sender and receiver without relying on the relay's integrity.

End-to-End Encryption Implementation

After the PAKE handshake completes, croc feeds the strongKey into the crypt package to derive an encryption key via PBKDF2 (or Argon2, depending on version). The implementation in src/crypt/crypt.go (lines 16-33) provides crypt.New to initialize the cipher and crypt.Encrypt/crypt.Decrypt for payload protection.

All data—including the transfer password, file metadata, and individual file chunks—is encrypted using AES-GCM before being transmitted to the relay. Because encryption occurs after the secure key exchange, the relay merely forwards ciphertext without ever learning the file contents, metadata, or authentication credentials. This ensures true end-to-end encryption where the relay has zero knowledge of the transferred data.

Practical Usage and Code Examples

Using croc requires no configuration files or network setup. The CLI (src/cli/cli.go) parses commands and constructs a croc.Client via croc.New (defined in src/croc/croc.go, lines 28-33), automatically handling the relay connection and encryption handshake.

Sending a file via CLI:

croc send myphoto.jpg

# Output:

# Sending 'myphoto.jpg' (2.3 MiB)

# Code is: sunrise-apple-tiger

Receiving on another machine:

croc sunrise-apple-tiger

# File automatically saves to current directory

Programmatic usage in Go:

import (
    "github.com/schollz/croc/v10/src/croc"
    "github.com/schollz/croc/v10/src/models"
)

func main() {
    opts := models.Options{
        RelayAddress: "croc.schollz.com", // default relay
        Debug:        true,
    }
    client, _ := croc.New(opts)

    // Send a file
    client.SendFile("example.txt")

    // Receive (blocks until sender uses matching codephrase)
    client.Receive()
}

The croc.New function initializes the client with the specified relay address, while the underlying tcp and crypt packages manage the connection and security layers transparently.

Summary

  • Relay-based architecture: Both peers connect outbound to a public relay (default croc.schollz.com), eliminating the need for inbound port forwarding or NAT traversal.
  • Zero-knowledge relay: The relay only forwards TCP traffic and cannot decrypt content because encryption keys are established independently via PAKE.
  • Cryptographic handshake: pake.InitCurve in src/tcp/tcp.go generates a strongKey that never traverses the network, preventing man-in-the-middle attacks even if the relay is compromised.
  • AES-GCM encryption: All payloads are encrypted using keys derived via PBKDF2/Argon2 in src/crypt/crypt.go, ensuring end-to-end confidentiality.
  • Cross-platform compatibility: IPv6-first design with IPv4 fallback ensures the tool works across diverse network topologies without configuration.

Frequently Asked Questions

Does the croc relay server store any of my file data?

No. The relay server operates as a stateless TCP proxy that only forwards encrypted packets between connected peers. Because files are encrypted using AES-GCM before transmission via the crypt.Encrypt function in src/crypt/crypt.go, the relay possesses no mechanism to decrypt or persist the data it routes.

What happens if the public relay is offline or blocked?

You can deploy a private relay using the croc relay binary and specify it via the --relay flag. The src/models/constants.go file documents the expected relay address format, and the croc.Options struct allows custom relay configuration when initializing clients programmatically.

How does croc verify the identity of the receiving party?

Croc uses the PAKE protocol (pake.InitCurve in src/tcp/tcp.go) to ensure that only participants possessing the matching code phrase can derive the session key. This prevents unauthorized interception even if an attacker knows the relay room ID or connects to the same TCP socket, as they cannot complete the cryptographic handshake without the correct password.

Is IPv6 required for croc to work without port forwarding?

No. While croc defaults to IPv6-first connectivity as configured in src/models/constants.go (lines 94-101), it automatically falls back to IPv4 if IPv6 is unavailable. The relay architecture works identically on both protocols, maintaining NAT traversal capabilities regardless of which IP version connects to the public relay.

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 →