1435  Encryption Labs and Practice Exercises

1435.1 Learning Objectives

By completing these hands-on labs, you will be able to:

  • Implement symmetric encryption using XOR and observe encryption/decryption cycles
  • Demonstrate classical ciphers with Caesar cipher and understand its weaknesses
  • Create hash functions for data integrity verification
  • Build Message Authentication Codes (MAC) to verify message authenticity
  • Simulate key exchange using Diffie-Hellman concepts
  • Visualize encryption operations through LED indicators and serial output
TipIn Plain English

These labs let you build cryptographic systems on real hardware (simulated ESP32). You’ll see encryption in action - watch data transform into ciphertext, verify integrity with hashes, and experience what happens when attacks occur.

Important: The algorithms in these labs are simplified for educational purposes. Production IoT systems use libraries like mbedTLS with AES-GCM.

1435.2 Lab 1: Basic Cryptographic Operations

1435.2.1 Components

Component Purpose Wokwi Element
ESP32 DevKit Main controller esp32:devkit-v1
Green LED Success indicator led:green
Red LED Failure indicator led:red
Yellow LED Processing indicator led:yellow
Blue LED Key exchange status led:blue
Push Button 1 Cycle through modes button
Push Button 2 Execute operation button
Push Button 3 Key exchange / attack mode button

1435.2.2 Circuit Connections

ESP32 Pin Connections:
----------------------
GPIO 2  --> Green LED (+)  --> 220 ohm Resistor --> GND
GPIO 4  --> Red LED (+)    --> 220 ohm Resistor --> GND
GPIO 5  --> Yellow LED (+) --> 220 ohm Resistor --> GND
GPIO 18 --> Blue LED (+)   --> 220 ohm Resistor --> GND
GPIO 15 --> Button 1       --> GND  (Mode Select)
GPIO 16 --> Button 2       --> GND  (Execute)
GPIO 17 --> Button 3       --> GND  (Key Exchange)

1435.2.3 Interactive Wokwi Simulator

TipSimulator Tips
  • Wire first: Connect all components before pasting code
  • Paste code: Copy the complete code into the editor
  • Run: Click the green “Play” button
  • Serial Monitor: View output in the Serial Monitor panel
  • Buttons: Click on-screen buttons to interact

1435.2.4 Lab Code

// =====================================================
// Interactive Cryptographic Operations Lab - ESP32
// Demonstrates: XOR, Caesar, Hash, MAC, Key Exchange
// Educational purposes - NOT for production use
// =====================================================

// Pin Definitions
#define LED_SUCCESS   2
#define LED_FAILURE   4
#define LED_PROCESSING 5
#define LED_KEYEXCHANGE 18

#define BTN_MODE      15
#define BTN_EXECUTE   16
#define BTN_KEYEXCHANGE 17

// Crypto Mode Enumeration
enum CryptoMode {
  MODE_XOR = 0,
  MODE_CAESAR = 1,
  MODE_HASH = 2,
  MODE_MAC = 3,
  MODE_KEYEXCHANGE = 4,
  MODE_COUNT = 5
};

// Global Variables
CryptoMode currentMode = MODE_XOR;
bool attackMode = false;
String sharedKey = "SECRET";
int caesarShift = 3;
unsigned long lastDebounce = 0;
const unsigned long debounceDelay = 200;

// Diffie-Hellman parameters (educational only)
const int DH_PRIME = 23;
const int DH_BASE = 5;
int privateKeyA = 6;
int privateKeyB = 15;

// Sample messages
String messages[] = {
  "SENSOR:TEMP=25C",
  "ALERT:MOTION_DETECTED",
  "CMD:UNLOCK_DOOR",
  "DATA:HUMIDITY=60%",
  "STATUS:ONLINE"
};
int messageIndex = 0;

void setup() {
  Serial.begin(115200);

  pinMode(LED_SUCCESS, OUTPUT);
  pinMode(LED_FAILURE, OUTPUT);
  pinMode(LED_PROCESSING, OUTPUT);
  pinMode(LED_KEYEXCHANGE, OUTPUT);

  pinMode(BTN_MODE, INPUT_PULLUP);
  pinMode(BTN_EXECUTE, INPUT_PULLUP);
  pinMode(BTN_KEYEXCHANGE, INPUT_PULLUP);

  ledStartupSequence();

  Serial.println("\n================================================");
  Serial.println("   Interactive Cryptographic Operations Lab");
  Serial.println("================================================");
  Serial.println("\nControls:");
  Serial.println("  Button 1 (GPIO 15): Cycle crypto mode");
  Serial.println("  Button 2 (GPIO 16): Execute operation");
  Serial.println("  Button 3 (GPIO 17): Key exchange / Toggle attack");
  Serial.println("================================================\n");

  printCurrentMode();
}

void loop() {
  if (digitalRead(BTN_MODE) == LOW && millis() - lastDebounce > debounceDelay) {
    lastDebounce = millis();
    cycleMode();
  }

  if (digitalRead(BTN_EXECUTE) == LOW && millis() - lastDebounce > debounceDelay) {
    lastDebounce = millis();
    executeCurrentOperation();
  }

  if (digitalRead(BTN_KEYEXCHANGE) == LOW && millis() - lastDebounce > debounceDelay) {
    lastDebounce = millis();
    if (currentMode == MODE_KEYEXCHANGE) {
      performKeyExchange();
    } else {
      toggleAttackMode();
    }
  }
}

// LED Control
void ledStartupSequence() {
  int leds[] = {LED_SUCCESS, LED_FAILURE, LED_PROCESSING, LED_KEYEXCHANGE};
  for (int i = 0; i < 4; i++) {
    digitalWrite(leds[i], HIGH);
    delay(150);
    digitalWrite(leds[i], LOW);
  }
}

void showProcessing() {
  digitalWrite(LED_PROCESSING, HIGH);
  digitalWrite(LED_SUCCESS, LOW);
  digitalWrite(LED_FAILURE, LOW);
}

void showSuccess() {
  digitalWrite(LED_PROCESSING, LOW);
  digitalWrite(LED_SUCCESS, HIGH);
  delay(500);
  digitalWrite(LED_SUCCESS, LOW);
}

void showFailure() {
  digitalWrite(LED_PROCESSING, LOW);
  digitalWrite(LED_FAILURE, HIGH);
  delay(500);
  digitalWrite(LED_FAILURE, LOW);
}

// Mode Management
void cycleMode() {
  currentMode = (CryptoMode)((currentMode + 1) % MODE_COUNT);
  messageIndex = (messageIndex + 1) % 5;
  printCurrentMode();
}

void printCurrentMode() {
  Serial.println("\n----------------------------------------");
  Serial.print("Current Mode: ");
  switch (currentMode) {
    case MODE_XOR:
      Serial.println("XOR ENCRYPTION");
      break;
    case MODE_CAESAR:
      Serial.println("CAESAR CIPHER");
      break;
    case MODE_HASH:
      Serial.println("HASH FUNCTION");
      break;
    case MODE_MAC:
      Serial.println("MESSAGE AUTHENTICATION CODE");
      break;
    case MODE_KEYEXCHANGE:
      Serial.println("KEY EXCHANGE");
      break;
  }
  Serial.print("Sample message: ");
  Serial.println(messages[messageIndex]);
  Serial.println("----------------------------------------");
}

void toggleAttackMode() {
  attackMode = !attackMode;
  Serial.print("\nAttack mode: ");
  Serial.println(attackMode ? "ENABLED" : "DISABLED");
}

// Execute Operations
void executeCurrentOperation() {
  showProcessing();

  switch (currentMode) {
    case MODE_XOR:
      demonstrateXOR();
      break;
    case MODE_CAESAR:
      demonstrateCaesar();
      break;
    case MODE_HASH:
      demonstrateHash();
      break;
    case MODE_MAC:
      demonstrateMAC();
      break;
    case MODE_KEYEXCHANGE:
      Serial.println("Press Button 3 to perform key exchange");
      showFailure();
      break;
  }
}

// XOR Encryption
void demonstrateXOR() {
  Serial.println("\n========== XOR ENCRYPTION ==========");
  String plaintext = messages[messageIndex];
  String key = attackMode ? "WRONG" : sharedKey;

  Serial.print("Plaintext:  ");
  Serial.println(plaintext);
  Serial.print("Key:        ");
  Serial.println(key);

  String ciphertext = xorEncrypt(plaintext, sharedKey);
  Serial.print("Ciphertext: ");
  printHex(ciphertext);

  String decrypted = xorEncrypt(ciphertext, key);
  Serial.print("Decrypted:  ");
  Serial.println(decrypted);

  if (decrypted == plaintext) {
    Serial.println("\n[SUCCESS] Decryption verified!");
    showSuccess();
  } else {
    Serial.println("\n[FAILURE] Wrong key!");
    showFailure();
  }
}

String xorEncrypt(String text, String key) {
  String result = "";
  for (int i = 0; i < text.length(); i++) {
    result += (char)(text[i] ^ key[i % key.length()]);
  }
  return result;
}

// Caesar Cipher
void demonstrateCaesar() {
  Serial.println("\n========== CAESAR CIPHER ==========");
  String plaintext = messages[messageIndex];
  int shift = attackMode ? (caesarShift + 5) % 26 : caesarShift;

  Serial.print("Plaintext: ");
  Serial.println(plaintext);
  Serial.print("Shift: ");
  Serial.println(shift);

  String encrypted = caesarEncrypt(plaintext, caesarShift);
  Serial.print("Encrypted: ");
  Serial.println(encrypted);

  String decrypted = caesarDecrypt(encrypted, shift);
  Serial.print("Decrypted: ");
  Serial.println(decrypted);

  if (decrypted == plaintext) {
    Serial.println("\n[SUCCESS] Caesar cipher verified!");
    showSuccess();
  } else {
    Serial.println("\n[FAILURE] Wrong shift!");
    showFailure();
  }
}

String caesarEncrypt(String text, int shift) {
  String result = "";
  for (int i = 0; i < text.length(); i++) {
    char c = text[i];
    if (c >= 'A' && c <= 'Z') {
      c = ((c - 'A' + shift) % 26) + 'A';
    }
    result += c;
  }
  return result;
}

String caesarDecrypt(String text, int shift) {
  return caesarEncrypt(text, 26 - (shift % 26));
}

// Hash Function
void demonstrateHash() {
  Serial.println("\n========== HASH FUNCTION ==========");
  String data = messages[messageIndex];
  String tamperedData = attackMode ? (data + "X") : data;

  Serial.print("Original: ");
  Serial.println(data);

  unsigned long originalHash = simpleHash(data);
  Serial.print("Hash: 0x");
  Serial.println(originalHash, HEX);

  if (attackMode) {
    Serial.print("Tampered: ");
    Serial.println(tamperedData);
  }

  unsigned long receivedHash = simpleHash(tamperedData);

  if (originalHash == receivedHash) {
    Serial.println("\n[VERIFIED] Integrity confirmed!");
    showSuccess();
  } else {
    Serial.println("\n[TAMPERED] Data modified!");
    showFailure();
  }
}

unsigned long simpleHash(String text) {
  unsigned long hash = 5381;
  for (int i = 0; i < text.length(); i++) {
    hash = ((hash << 5) + hash) + text[i];
  }
  return hash;
}

// MAC
void demonstrateMAC() {
  Serial.println("\n========== MAC ==========");
  String data = messages[messageIndex];
  String key = attackMode ? "FAKE_KEY" : sharedKey;

  Serial.print("Data: ");
  Serial.println(data);

  unsigned long originalMAC = simpleHash(data + sharedKey);
  Serial.print("Original MAC: 0x");
  Serial.println(originalMAC, HEX);

  unsigned long receivedMAC = simpleHash(data + key);
  Serial.print("Received MAC: 0x");
  Serial.println(receivedMAC, HEX);

  if (originalMAC == receivedMAC) {
    Serial.println("\n[AUTHENTICATED] MAC verified!");
    showSuccess();
  } else {
    Serial.println("\n[FORGED] Wrong key or tampered!");
    showFailure();
  }
}

// Key Exchange
void performKeyExchange() {
  Serial.println("\n========== DIFFIE-HELLMAN ==========");
  digitalWrite(LED_KEYEXCHANGE, HIGH);

  int publicA = modPow(DH_BASE, privateKeyA, DH_PRIME);
  int publicB = modPow(DH_BASE, privateKeyB, DH_PRIME);

  Serial.print("Alice public: ");
  Serial.println(publicA);
  Serial.print("Bob public: ");
  Serial.println(publicB);

  int secretA = modPow(publicB, privateKeyA, DH_PRIME);
  int secretB = modPow(publicA, privateKeyB, DH_PRIME);

  Serial.print("Alice secret: ");
  Serial.println(secretA);
  Serial.print("Bob secret: ");
  Serial.println(secretB);

  if (secretA == secretB) {
    Serial.println("\n[SUCCESS] Shared secret established!");
    showSuccess();
  } else {
    showFailure();
  }

  digitalWrite(LED_KEYEXCHANGE, LOW);
}

int modPow(int base, int exp, int mod) {
  int result = 1;
  base = base % mod;
  while (exp > 0) {
    if (exp % 2 == 1) {
      result = (result * base) % mod;
    }
    exp = exp >> 1;
    base = (base * base) % mod;
  }
  return result;
}

void printHex(String text) {
  for (int i = 0; i < text.length(); i++) {
    if ((byte)text[i] < 16) Serial.print("0");
    Serial.print((byte)text[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

1435.2.5 Lab Activities

Activity 1: XOR Encryption

  1. Run the simulation and observe the XOR mode
  2. Press Button 2 to encrypt a message
  3. Note how the same XOR operation encrypts and decrypts
  4. Press Button 3 to enable “attack mode” (wrong key)
  5. Observe how decryption fails with the wrong key

Activity 2: Hash Integrity

  1. Switch to HASH mode (Button 1)
  2. Execute to see the hash of the original message
  3. Enable attack mode to simulate tampering
  4. Observe how any change produces a completely different hash

Activity 3: Key Exchange

  1. Switch to KEY EXCHANGE mode
  2. Press Button 3 to perform Diffie-Hellman
  3. Observe that Alice and Bob compute the same shared secret
  4. Note that this works over an “insecure” channel

1435.3 Lab 2: Secure Message Exchange

This lab demonstrates a complete secure communication protocol with encryption, integrity, and timestamps.

1435.3.1 Learning Goals

  • Combine multiple cryptographic primitives
  • Implement replay attack prevention
  • Understand authenticated encryption concepts
// Secure Message Exchange - ESP32
// Demonstrates: Encryption + Integrity + Replay Prevention

struct SecurePacket {
  String senderId;
  unsigned long timestamp;
  String encryptedPayload;
  unsigned long integrityHash;
};

const String SHARED_KEY = "IOT_SECRET_KEY";

void setup() {
  Serial.begin(115200);
  Serial.println("Secure Communication Demo");

  // Scenario 1: Normal communication
  Serial.println("\n=== SCENARIO 1: Normal Communication ===");

  SecurePacket packet = createSecurePacket("SENSOR_A", "temp=25.5C", SHARED_KEY);

  Serial.println("Sender creates secure packet:");
  Serial.print("  Sender ID: ");
  Serial.println(packet.senderId);
  Serial.print("  Timestamp: ");
  Serial.println(packet.timestamp);
  Serial.print("  Encrypted: ");
  printHex(packet.encryptedPayload);
  Serial.print("  Hash: 0x");
  Serial.println(packet.integrityHash, HEX);

  // Receiver verifies
  bool verified = verifySecurePacket(packet, SHARED_KEY);
  if (verified) {
    String decrypted = xorEncrypt(packet.encryptedPayload, SHARED_KEY);
    Serial.print("\n[VERIFIED] Decrypted: ");
    Serial.println(decrypted);
  }

  // Scenario 2: Tampering detection
  Serial.println("\n=== SCENARIO 2: Tampering Detection ===");

  SecurePacket tamperedPacket = packet;
  tamperedPacket.encryptedPayload[0] ^= 0xFF; // Flip bits

  verified = verifySecurePacket(tamperedPacket, SHARED_KEY);
  if (!verified) {
    Serial.println("[ALERT] Integrity check FAILED!");
    Serial.println("[ACTION] Packet rejected");
  }

  // Scenario 3: Replay attack prevention
  Serial.println("\n=== SCENARIO 3: Replay Prevention ===");

  unsigned long currentTime = packet.timestamp + 10000; // 10 seconds later
  unsigned long maxAge = 5000; // 5 second window

  if (currentTime - packet.timestamp > maxAge) {
    Serial.println("[ALERT] Packet too old - REPLAY ATTACK!");
    Serial.println("[ACTION] Packet rejected");
  }
}

void loop() {}

SecurePacket createSecurePacket(String sender, String payload, String key) {
  SecurePacket pkt;
  pkt.senderId = sender;
  pkt.timestamp = millis();
  pkt.encryptedPayload = xorEncrypt(payload, key);

  String toHash = sender + String(pkt.timestamp) + pkt.encryptedPayload + key;
  pkt.integrityHash = simpleHash(toHash);

  return pkt;
}

bool verifySecurePacket(SecurePacket pkt, String key) {
  String toHash = pkt.senderId + String(pkt.timestamp) + pkt.encryptedPayload + key;
  return (simpleHash(toHash) == pkt.integrityHash);
}

String xorEncrypt(String text, String key) {
  String result = "";
  for (int i = 0; i < text.length(); i++) {
    result += (char)(text[i] ^ key[i % key.length()]);
  }
  return result;
}

unsigned long simpleHash(String text) {
  unsigned long hash = 5381;
  for (int i = 0; i < text.length(); i++) {
    hash = ((hash << 5) + hash) + text[i];
  }
  return hash;
}

void printHex(String text) {
  for (int i = 0; i < text.length(); i++) {
    if ((byte)text[i] < 16) Serial.print("0");
    Serial.print((byte)text[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

1435.3.2 Security Layers Demonstrated

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22'}}}%%
flowchart LR
    subgraph Node_A["Node A (Sender)"]
        A1[Sensor Data] --> A2[Encrypt with Key]
        A2 --> A3[Add Timestamp]
        A3 --> A4[Calculate Hash]
        A4 --> A5[Secure Packet]
    end

    A5 -->|Network| B1

    subgraph Node_B["Node B (Receiver)"]
        B1[Receive Packet] --> B2[Verify Hash]
        B2 --> B3[Check Timestamp]
        B3 --> B4[Decrypt with Key]
        B4 --> B5[Valid Data]
    end

    style A1 fill:#16A085,color:#fff
    style A5 fill:#E67E22,color:#fff
    style B1 fill:#E67E22,color:#fff
    style B5 fill:#16A085,color:#fff

Figure 1435.1: Security layers: encryption, integrity hash, and timestamp for replay prevention

1435.4 Challenge Exercises

TipChallenge 1: Implement Vigenere Cipher

The Vigenere cipher improves on Caesar by using a keyword to create multiple shift values.

Task: Modify the Caesar cipher to use a multi-character key like “IOTKEY”.

Hint: For each position i, use key[i % keyLength] to determine the shift amount.

TipChallenge 2: Add Nonce for Replay Prevention

Timestamps alone may not prevent replay attacks if clocks are not synchronized.

Task: Add a random nonce (number used once) that both parties track to reject duplicate messages.

Steps:

  1. Generate a random nonce for each message
  2. Include nonce in the packet structure
  3. Receiver maintains a list of recently seen nonces
  4. Reject any message with a previously seen nonce
TipChallenge 3: Implement Rolling Keys

Using the same key forever is dangerous. Implement key rotation.

Task: Create a system where after every 10 messages, derive a new session key:

newKey = hash(oldKey + counter)

Both sender and receiver independently calculate the same new key.


WarningEducational vs Production Security

This lab uses simplified algorithms for learning. Real IoT security requires:

Lab Demo Production System
XOR encryption AES-128-GCM or AES-256-GCM
Caesar cipher Not used (educational only)
DJB2 hash SHA-256 or SHA-3
Simple hash auth HMAC-SHA256
Hardcoded key TLS key exchange + certificates

Never deploy lab code in production. Use libraries like:

  • ESP32: mbedTLS (built into ESP-IDF)
  • Arduino: Crypto library or AES library
  • DTLS: tinydtls for constrained devices

1435.5 Summary

These hands-on labs demonstrate:

  • XOR encryption as the foundation of symmetric ciphers
  • Caesar cipher and why classical ciphers are insecure
  • Hash functions for data integrity verification
  • MACs for authentication plus integrity
  • Diffie-Hellman for secure key exchange
  • Complete protocols combining encryption, integrity, and replay prevention

1435.6 What’s Next

Continue to Cipher Challenge Game to test your cryptography knowledge through interactive puzzles and progressively challenging cipher challenges.