# How EncryptedTransport Derives Its AES-256-GCM Key in OpenFlux

> Learn how EncryptedTransport derives its AES-256-GCM key in OpenFlux. Discover the three-stage process combining scrypt HMAC and Go crypto for secure encryption.

- Repository: [p1neappleXpress/OpenFlux](https://github.com/p1neappleXpress/OpenFlux)
- Tags: internals
- Published: 2026-09-14

---

**`EncryptedTransport` derives its AES-256-GCM key using a three-stage process that combines scrypt-based master key generation, HMAC-SHA-256 directional splitting, and standard Go crypto/GCM construction to create independent bidirectional encryption streams.**

The `EncryptedTransport` implementation in the OpenFlux repository transforms a shared secret into cryptographically independent encryption keys for secure tunneling. This process ensures that traffic flowing from client to exit node uses a completely different AES key than traffic returning from exit to client, providing strict directional secrecy.

## Three-Stage Key Derivation Architecture

The derivation pipeline implemented in [`transport/encrypted.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/encrypted.go) converts a user-supplied secret and session context into two distinct 32-byte AES-256 keys. According to the OpenFlux source code, this involves sequential application of memory-hard key stretching, domain-separated HMAC derivation, and AEAD construction.

### Stage 1: Master Key Generation with scrypt

The process begins by feeding the shared secret through **scrypt** to produce a 32-byte master key. The implementation uses SHA-256 to preprocess the salt, incorporating a versioned context string to prevent cross-protocol attacks.

```go
salt := sha256.Sum256([]byte("OpenFlux encrypted transport v1\x00" + context))
master, err := scrypt.Key([]byte(secret), salt[:], 32768, 8, 1, 32)

```

*Source:* [`transport/encrypted.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/encrypted.go) lines 58-60

The scrypt parameters are explicitly set to **N=32768, r=8, p=1**, providing memory-hard protection against hardware-accelerated brute force attacks while yielding exactly 32 bytes (256 bits) required for AES-256.

### Stage 2: Directional Sub-keys via HMAC-SHA-256

The master key undergoes domain separation to generate two independent directional keys. The `deriveDirectionalKey` helper function uses HMAC-SHA-256 with protocol-specific labels to ensure cryptographic independence between streams.

```go
clientToExit := deriveDirectionalKey(master, "client-to-exit")
exitToClient := deriveDirectionalKey(master, "exit-to-client")

```

*Source:* [`transport/encrypted.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/encrypted.go) lines 63-65

The underlying HMAC implementation prefixes labels with a domain separator to prevent collision attacks:

```go
func deriveDirectionalKey(master []byte, label string) []byte {
    mac := hmac.New(sha256.New, master)
    _, _ = mac.Write([]byte("OpenFlux direction v1\x00" + label))
    return mac.Sum(nil)
}

```

*Source:* [`transport/encrypted.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/encrypted.go) lines 90-94

This construction ensures that knowledge of the client-to-exit key provides zero information about the exit-to-client key, even if the master key were somehow compromised.

### Stage 3: AES-256-GCM Construction

Each 32-byte directional sub-key initializes a distinct **AES-256-GCM** AEAD instance. The `newGCM` factory function wraps Go's standard library cipher implementations to provide authenticated encryption with associated data (AEAD).

```go
func newGCM(key []byte) (cipher.AEAD, error) {
    block, err := aes.NewCipher(key)
    aead, err := cipher.NewGCM(block)
    return aead, nil
}

```

*Source:* [`transport/encrypted.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/encrypted.go) lines 96-104

The transport initializes one GCM instance for transmission and one for reception, selecting which directional key serves which purpose based on the `exitNode` boolean flag passed during construction.

## Directional Key Selection Logic

When `EncryptedTransport` initializes, it evaluates the `exitNode` parameter to determine key assignment without requiring manual key configuration. If `exitNode` is `false` (client mode), the transport uses `clientToExit` for encryption (sending) and `exitToClient` for decryption (receiving). When `exitNode` is `true`, these assignments swap automatically.

This design eliminates the risk of nonce reuse across bidirectional communication channels, as each direction operates with an independent AES key and independent nonce space within the GCM construction.

## Practical Implementation Example

The following example demonstrates initializing an encrypted transport on the client side using the exported constructor:

```go
package main

import (
	"log"
	"github.com/p1neappleXpress/OpenFlux/main/transport"
)

// Example: create a client-side EncryptedTransport
func main() {
	inner := transport.NewRawSocketTransport() // any implementation of Transport
	secret := "super-secret-shared-key-1234"
	context := "session-42" // public per-session identifier
	exitNode := false      // client, not exit

	enc, err := transport.NewEncryptedTransport(inner, secret, context, exitNode)
	if err != nil {
		log.Fatalf("failed to initialise encrypted transport: %v", err)
	}

	// Use it like any other Transport
	if err := enc.Send([]byte("Hello, exit node!")); err != nil {
		log.Fatalf("send error: %v", err)
	}
	enc.Receive(func(p []byte) {
		log.Printf("received: %s", string(p))
	})
}

```

Running identical code on the exit node with `exitNode := true` automatically swaps the encryption and decryption keys, preserving directional secrecy without additional configuration.

## Summary

- **Scrypt-based stretching** in [`transport/encrypted.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/encrypted.go) (lines 58-60) generates a 32-byte master key using SHA-256-derived salts and memory-hard parameters (32768, 8, 1).
- **HMAC-SHA-256 domain separation** creates two independent directional keys via `deriveDirectionalKey` (lines 90-94), preventing cross-direction key leakage.
- **AES-256-GCM construction** (lines 96-104) wraps each directional key in authenticated encryption mode, providing confidentiality and integrity.
- **Automatic key selection** based on the `exitNode` boolean ensures clients and exits use complementary key pairs without manual coordination.

## Frequently Asked Questions

### What hashing algorithm does EncryptedTransport use for the salt?

`EncryptedTransport` uses **SHA-256** to hash the concatenation of a versioned protocol string (`"OpenFlux encrypted transport v1"`) and the user-provided context string before passing it as the salt to scrypt. This ensures deterministic salt generation while binding the key to the specific protocol version.

### Why does OpenFlux use scrypt instead of PBKDF2?

OpenFlux selects **scrypt** specifically for its memory-hard properties. Unlike PBKDF2, which is vulnerable to GPU and ASIC acceleration due to its low memory requirements, scrypt with parameters N=32768, r=8 requires significant memory to compute, making brute-force attacks against the shared secret substantially more expensive.

### How does the transport prevent key reuse between directions?

The transport prevents key reuse by deriving **two cryptographically independent keys** from the master key using HMAC-SHA-256 with distinct, domain-separated labels (`"client-to-exit"` and `"exit-to-client"`). Because HMAC acts as a pseudorandom function, the two directional keys are computationally indistinguishable from random and independent of each other, ensuring that nonce spaces remain isolated between send and receive operations.

### Where is the deriveDirectionalKey function defined?

The `deriveDirectionalKey` helper function is defined in [`transport/encrypted.go`](https://github.com/p1neappleXpress/OpenFlux/blob/main/transport/encrypted.go) at lines 90-94. It accepts a master key byte slice and a direction label string, then returns a 32-byte derived key suitable for AES-256-GCM initialization via HMAC-SHA-256 processing.