Repository: https://github.com/x0prc/AnR2

Building Anonymous Routing with Garlic-Inspired Encryption


Understanding the Architecture

AnR2 follows a layered architecture with three primary components: the frontend React application, the Flask backend with WebSocket support, and the node-based routing network. Let’s examine how these components work together.

The Node Architecture

At the heart of AnR2 lies our node-based routing system. We designed three distinct node types, each serving a specific purpose in the message routing chain:

class EntryNode:
    def __init__(self):
        self.connected_clients = []
    
    def accept_connection(self, client):
        self.connected_clients.append(client)
        # Forward the encrypted message to the next node
        self.forward_message(client.encrypted_message, next_node)
        
class RelayNode:
    def forward_message(self, encrypted_message, next_node):
        # Forward the encrypted message to the next node
        next_node.receive_message(encrypted_message)
        
class ExitNode:
    def receive_message(self, encrypted_message):
        # Decrypt the message
        decrypted_message = self.decrypt(encrypted_message)
        # Forward the message to the final destination
        self.send_to_destination(decrypted_message)

Explanation: The EntryNode serves as the gateway for client connections, accepting encrypted messages and initiating the routing chain. The RelayNode acts as an intermediary, forwarding messages without decrypting them—crucial for maintaining sender anonymity. Finally, the ExitNode performs the final decryption and delivers the message to its destination. This three-tier architecture mirrors the structure of anonymous networks while remaining conceptually simple.


The Encryption Layer: AES in CBC Mode

Encryption is the foundation of any anonymous routing system. We implemented AES encryption in CBC (Cipher Block Chaining) mode to ensure message confidentiality at each routing hop.

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import os
 
class AESEncryption:
    def __init__(self, key):
        self.key = key
 
    def encrypt(self, plain_text):
        cipher = AES.new(self.key, AES.MODE_CBC)
        ct_bytes = cipher.encrypt(pad(plain_text.encode(), AES.block_size))
        return cipher.iv + ct_bytes
 
    def decrypt(self, cipher_text):
        iv = cipher_text[:AES.block_size]
        ct = cipher_text[AES.block_size:]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return unpad(cipher.decrypt(ct), AES.block_size).decode()

Explanation: Our AESEncryption class encapsulates the encryption logic. When initializing, we generate a 16-byte random key using os.urandom(16), ensuring each node has a unique encryption key.

During encryption:

  1. We create a new AES cipher in CBC mode, which automatically generates a random Initialization Vector (IV)
  2. The plaintext is padded to match AES block size requirements using PKCS7 padding
  3. We prepend the IV to the ciphertext, creating a self-contained encrypted package that can be decrypted later

During decryption:

  1. We extract the IV from the first 16 bytes of the ciphertext
  2. Using the stored key and extracted IV, we decrypt the remaining bytes
  3. The unpad function removes the padding, revealing the original plaintext

This approach ensures that even if an attacker intercepts the message at any point in the routing chain, they cannot read its contents without the specific node’s key.


Real-Time Communication with Flask-SocketIO

Anonymous routing requires bidirectional communication between clients and nodes. We chose Flask-SocketIO to enable real-time message exchange:

from flask import Flask, request, jsonify
from flask_socketio import SocketIO, send, emit
 
app = Flask(__name__)
socketio = SocketIO(app)  
 
# WebSocket event to receive messages
@socketio.on('message')
def handle_message(msg):
    print(f"Received message: {msg}")
    
    # Decrypt the received message
    decrypted_message = aes.decrypt(bytes.fromhex(msg))
    print(f"Decrypted message: {decrypted_message}")
    
    # Broadcast 
    send(f"Decrypted message: {decrypted_message}", broadcast=True)
 
# Client sent encrypted messages
@socketio.on('send_encrypted')
def handle_encrypted_message(msg):
    print(f"Received encrypted message: {msg}")
    
    # Encrypt the received message and broadcast it
    encrypted_message = aes.encrypt(msg)
    print(f"Encrypted message: {encrypted_message.hex()}")
    
    emit('receive_encrypted', encrypted_message.hex(), broadcast=True)
 
@socketio.on('connect')
def handle_connect():
    print("Client connected")
    send("Client connected to WebSocket server")
 
@socketio.on('disconnect')
def handle_disconnect():
    print("Client disconnected")

Explanation: Our WebSocket implementation provides multiple event handlers for different communication scenarios:

  • message event: Handles incoming messages from clients. We decrypt the message using our AES instance and broadcast the decrypted content to all connected clients.
  • send_encrypted event: Receives plaintext messages from clients, encrypts them server-side, and broadcasts the encrypted hex string. This simulates the encryption layer in our routing network.
  • connect/disconnect events: Provide lifecycle management, logging when clients join or leave the network.

The broadcast=True parameter ensures all connected clients receive updates, creating a distributed communication environment where messages propagate through the network.


The Frontend: React with Real-Time Updates

We built the user interface using React, integrating it with our WebSocket backend to provide real-time message updates:

import React, { useState, useEffect } from 'react';
import { sendMessageViaAPI } from './utils/api';
import { socket } from './utils/socket';
import MessageList from './components/MessageList';
import MessageInput from './components/MessageInput';
 
function App() {
  const [messages, setMessages] = useState([]);
  const [inputMessage, setInputMessage] = useState('');
 
  useEffect(() => {
    // Listen for encrypted messages from WebSocket
    socket.on('receive_encrypted', (encryptedMessage) => {
      setMessages((prevMessages) => [...prevMessages, `Encrypted: ${encryptedMessage}`]);
    });
 
    return () => {
      socket.off('receive_encrypted');
    };
  }, []);
 
  const handleSend = async () => {
    setMessages((prevMessages) => [...prevMessages, `You: ${inputMessage}`]);
 
    // Emit the message to the WebSocket server
    socket.emit('send_encrypted', inputMessage);
 
    // Send the message via REST API
    const response = await sendMessageViaAPI(inputMessage);
    setMessages((prevMessages) => [...prevMessages, `API Response: ${response.encrypted_message}`]);
 
    setInputMessage('');
  };
 
  return (
    <Container>
      <Row className="justify-content-center">
        <Col md={8}>
          <h1 className="text-center">AnR2</h1>
          <MessageList messages={messages} />
          <MessageInput
            inputMessage={inputMessage}
            setInputMessage={setInputMessage}
            handleSend={handleSend}
          />
        </Col>
      </Row>
    </Container>
  );
}

Explanation: The React application manages message state and handles dual-channel communication:

  • useEffect hook: Establishes the WebSocket connection and listens for receive_encrypted events. When an encrypted message arrives from the server, we append it to our messages array, allowing users to see the encrypted payload.
  • handleSend function: Implements a dual-transmission strategy:
    1. Emits the message via WebSocket for real-time delivery
    2. Sends the message via REST API for acknowledgment and additional encryption
  • State management: We use React’s useState to track both the message history and the current input, providing immediate UI feedback.

This dual-channel approach demonstrates how messages can traverse both synchronous (WebSocket) and asynchronous (REST) pathways in a distributed system.


The REST API Layer

In addition to WebSocket communication, we expose a REST API endpoint for message processing:

# API route to receive and encrypt a message
@app.route('/api/send', methods=['POST'])
def send_message():
    data = request.get_json()
    message = data['message']
    
    # Encrypt the message using AES
    encrypted_message = aes.encrypt(message)
    
    # Simulate sending the message to the routing network
    response = f"Message received and encrypted: {encrypted_message.hex()}"
    
    return jsonify(response=response)

Explanation: The /api/send endpoint accepts JSON payloads containing messages. Upon receiving a message:

  1. We extract the plaintext from the request body
  2. Encrypt it using our AES instance
  3. Return the encrypted message in hexadecimal format

This endpoint serves as an alternative entry point for clients that may not support WebSockets, ensuring broad compatibility across different client implementations.


Client-Side Encryption with Node.js

We also implemented a Node.js-based encryption utility that can be used for client-side message preparation:

const crypto = require('crypto');
 
function garlicEncrypt(message) {
  // For simplicity, let's use AES encryption here. You can integrate your garlic routing logic
  const algorithm = 'aes-256-cbc';
  const key = crypto.randomBytes(32);
  const iv = crypto.randomBytes(16);
 
  const cipher = crypto.createCipheriv(algorithm, key, iv);
  let encrypted = cipher.update(message, 'utf8', 'hex');
  encrypted += cipher.final('hex');
 
  return `${iv.toString('hex')}:${encrypted}`;
}

Explanation: The garlicEncrypt function uses Node.js’s built-in crypto module to perform AES-256-CBC encryption. We:

  1. Generate a 32-byte random key and 16-byte IV using crypto.randomBytes()
  2. Create a cipher instance with the specified algorithm, key, and IV
  3. Encrypt the message in UTF-8 encoding, outputting hexadecimal strings
  4. Concatenate the IV and ciphertext with a colon delimiter for easy parsing

This client-side encryption capability allows messages to be encrypted before ever reaching the network, adding an additional layer of security and demonstrating the principle of end-to-end encryption.


Deployment and Running the System

AnR2 is containerized for easy deployment. We use Docker to package both the frontend and backend services:

# Base image
FROM python:3.9-slim
 
# Set working directory
WORKDIR /app
 
# Copy requirements and install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
 
# Copy application code
COPY . .
 
# Expose port
EXPOSE 5000
 
# Run the application
CMD ["python", "backend/app.py"]

To run the complete system:

# Start the Flask server
python3 backend/app.py
 
# Start the React frontend
npm start
 
# Or using Docker
docker-compose up

Future Directions

While AnR2 demonstrates the core concepts of anonymous routing, several enhancements would bring it closer to production-ready anonymous communication:

  1. Full Garlic Routing: Implement true garlic routing where multiple messages are bundled together and routed through multiple nodes simultaneously, significantly improving network efficiency.

  2. Dynamic Key Exchange: Implement a key exchange mechanism (such as Diffie-Hellman) to allow nodes to establish secure channels without pre-shared keys.

  3. Path Selection Algorithm: Develop an intelligent path selection algorithm that considers node reputation, latency, and geographical distribution when building routing chains.

  4. User Authentication: Add cryptographic identity management while preserving anonymity, potentially using zero-knowledge proofs.


Conclusion

Building AnR2 taught us valuable lessons about distributed systems, cryptography, and the trade-offs between anonymity and performance. By layering AES encryption, WebSocket communication, and a node-based routing architecture, we’ve created a foundation for anonymous message routing that can serve as a stepping stone toward more sophisticated privacy-preserving systems.


Happy routing! 🧄