Skip to main content

Overview

EHBP (Encrypted HTTP Body Protocol) is a protocol that encrypts HTTP message bodies end-to-end while leaving HTTP headers in the clear for routing. This allows encrypted payloads to transit proxies unchanged while maintaining all standard HTTP semantics. The protocol uses HPKE (RFC 9180) for hybrid public key encryption, ensuring that only the intended recipient can decrypt the message body.

How It Works

Architecture

EHBP is implemented as two complementary components:
  1. Client Transport: Encrypts outgoing request bodies and decrypts incoming response bodies
  2. Server Middleware: Decrypts incoming request bodies and encrypts outgoing response bodies

Message Flow

For each HTTP exchange:

Encryption Process

Request Encryption:
  1. Client fetches server’s public key from /.well-known/hpke-keys
  2. Client generates ephemeral key pair
  3. Client derives HPKE encryption context from server’s public key
  4. Client encrypts request body as a stream of length-prefixed chunks
  5. Client includes Ehbp-Client-Public-Key and Ehbp-Encapsulated-Key headers
Response Encryption:
  1. Server reads client’s public key from request header
  2. Server derives HPKE encryption context from client’s public key
  3. Server encrypts response body as a stream of length-prefixed chunks
  4. Server includes Ehbp-Encapsulated-Key header

Body Framing

Encrypted bodies are framed as a sequence of chunks:
[LENGTH (4 bytes)] [CIPHERTEXT (LENGTH bytes)]
[LENGTH (4 bytes)] [CIPHERTEXT (LENGTH bytes)]
...
  • Each chunk has a 4-byte big-endian length prefix
  • Length counts ciphertext bytes only
  • AEAD encryption (AES-256-GCM) is applied to each chunk
  • End of message is indicated by HTTP entity body termination

Protocol Headers

Request Headers

Ehbp-Client-Public-Key: <hex-encoded-client-public-key>
Ehbp-Encapsulated-Key: <hex-encoded-encapsulated-key>
Transfer-Encoding: chunked
  • Ehbp-Client-Public-Key: Required for any request expecting an encrypted response (including GET requests with no body)
  • Ehbp-Encapsulated-Key: Required only when request has an encrypted body
  • Transfer-Encoding: Set to chunked when sending encrypted bodies

Response Headers

Ehbp-Encapsulated-Key: <hex-encoded-encapsulated-key>
Transfer-Encoding: chunked
  • Ehbp-Encapsulated-Key: Required for encrypted responses
  • Ehbp-Fallback: Set to 1 if server responded in plaintext (when fallback is enabled)

Implementation Examples

Go Server Middleware

package main

import (
  "log"
  "net/http"

  "github.com/tinfoilsh/encrypted-http-body-protocol/identity"
  "github.com/tinfoilsh/encrypted-http-body-protocol/protocol"
)

func main() {
  // Load or create server identity
  id, err := identity.FromFile("server_identity.json")
  if err != nil {
    log.Fatalf("server exited: %v", err)
  }

  mux := http.NewServeMux()

  // Serve HPKE public key configuration
  mux.HandleFunc(protocol.KeysPath, id.ConfigHandler)

  // Wrap handler with EHBP middleware
  mux.Handle("/secure", id.Middleware(false)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // Body is automatically decrypted
    // Response will be automatically encrypted
    w.Write([]byte("ok"))
  })))

  log.Println("Server listening on :8080")
  http.ListenAndServe(":8080", mux)
}

Go Client Transport

package main

import (
  "bytes"
  "io"
  "log"
  "net/http"

  "github.com/tinfoilsh/encrypted-http-body-protocol/client"
  "github.com/tinfoilsh/encrypted-http-body-protocol/identity"
)

func main() {
  // Load or create client identity
  id, err := identity.FromFile("client_identity.json")
  if err != nil {
    log.Fatalf("client exited: %v", err)
  }

  // Create EHBP transport
  tr, err := client.NewTransport("http://localhost:8080", id, false)
  if err != nil {
    log.Fatalf("failed to create transport: %v", err)
  }

  // Use with standard HTTP client
  httpClient := &http.Client{Transport: tr}

  resp, err := httpClient.Post(
    "http://localhost:8080/secure",
    "text/plain",
    bytes.NewBufferString("sensitive data"),
  )
  if err != nil {
    log.Fatalf("request failed: %v", err)
  }
  defer resp.Body.Close()

  body, _ := io.ReadAll(resp.Body)
  log.Printf("Response: %s", body)
}

JavaScript/TypeScript Client

import { Identity, createTransport } from 'ehbp';

async function main() {
  // Create client identity
  const identity = await Identity.generate();

  // Create EHBP transport
  const transport = await createTransport({
    serverUrl: 'http://localhost:8080',
    identity,
    insecureSkipVerify: false
  });

  // Make encrypted request
  const response = await transport.fetch('http://localhost:8080/secure', {
    method: 'POST',
    body: JSON.stringify({ message: 'sensitive data' }),
    headers: { 'Content-Type': 'application/json' }
  });

  const data = await response.text();
  console.log('Response:', data);
}

Key Distribution

Server Public Key Discovery

Servers must expose their HPKE configuration at:
/.well-known/hpke-keys
Response format:
  • Content-Type: application/ohttp-keys
  • Body: Key configuration as defined in RFC 9458 Section 3
Key configuration includes:
  • key_id: 0
  • kem_id: X25519_HKDF_SHA256
  • cipher_suites: HKDF_SHA256 with AES_256_GCM
  • public_key: Server’s KEM public key bytes

Identity Management

Server Identity:
// Generate new identity (writes to file if not exists)
id, err := identity.FromFile("server_identity.json")

// Or create ephemeral identity
id, err := identity.Generate()
Client Identity:
// Generate new identity (writes to file if not exists)
id, err := identity.FromFile("client_identity.json")

// Or create ephemeral identity
id, err := identity.Generate()

CLI Tools

The EHBP repository includes command-line tools for testing and development.

Example Server

# Run example server (creates identity if needed)
go run ./cmd/example/server -l :8080 -i server_identity.json -v

# Enable plaintext fallback
go run ./cmd/example/server -p -v

Example Client

# Run example client
go run ./cmd/example/client -s http://localhost:8080 -i client_identity.json -v

Fetch Tool

curl-like tool that sends encrypted requests:
# POST encrypted data
go run ./cmd/fetch -i client_identity.json -X POST -d 'hello' http://localhost:8080/secure

# GET request with encrypted response
go run ./cmd/fetch -i client_identity.json http://localhost:8080/secure

Use in Tinfoil Node SDK

The Tinfoil Node SDK uses EHBP to encrypt all API request and response bodies to the inference enclaves. This is particularly important for browser environments where TLS certificate pinning is not available. Why EHBP in the browser? Since browsers don’t support certificate pinning, EHBP provides an alternative mechanism to verify you’re communicating with the attested enclave. The SDK:
  1. Fetches the enclave’s attested HPKE public key
  2. Encrypts all request bodies directly to that verified key
  3. Decrypts response bodies from the enclave
This ensures your prompts, completions, and other sensitive data are encrypted end-to-end to the verified enclave, even when running in the browser where traditional certificate pinning isn’t possible. When you use the TinfoilAI client:
  1. Client automatically fetches and verifies the enclave’s public key
  2. All request bodies are encrypted with EHBP before transmission
  3. All response bodies are decrypted after receipt
  4. HTTP headers remain visible for API routing and load balancing

EHBP in Action

You can see EHBP in action by visiting chat.tinfoil.sh and inspecting the network tab. The request payload shows encrypted content that only the attested enclave can decrypt: EHBP Network Inspection
I