Skip to content

Repository files navigation

Integrating Blockchain in IoT — Secure Data Pipeline Prototype

Python FastAPI Solidity License Status


IPFS Blockchain Web3 Ganache AES-256-GCM ECDSA

A security-first research prototype that enforces IoT data authenticity, confidentiality, integrity, and replay resistance by anchoring cryptographic proofs on a local Ethereum blockchain (Ganache) and storing raw payloads on IPFS — all exposed through a production-style FastAPI backend.

Built to demonstrate applied cryptography and distributed-systems security design for a cybersecurity engineering context.


Table of Contents


Problem Statement

IoT ecosystems suffer from a structural trust problem: sensor data travels through multiple untrusted hops (device → network → server), and at any point it can be forged, tampered with, replayed, or silently dropped without detection.

This project tackles four concrete attack surfaces:

Threat Attack Vector
Forged data Any actor impersonating a registered device
Payload tampering Man-in-the-middle modification in transit or at rest
Replay attacks Re-submitting a valid, previously captured packet
Unauthorized data access No provenance or verifiability after ingestion

What Was Implemented

End-to-End Secure IoT Data Pipeline

flowchart LR
    A[IoT Device --> Encrypt + Sign] -->|Encrypted payload + nonce + timestamp| B

    B{FastAPI Backend\nValidate}
    B -->|Reject| R([401 Unauthorized])
    B -->|Pass| C[Decrypt & Hash SHA-256]

    C -->|Plaintext JSON| D[(IPFS)]
    C -->|CID + Hash + Address| E[(DataStorage.sol + Ethereum)]

    E -.->|verifyDataIntegrity| F([Integrity Check --> true / false])
Loading

Implemented Components

Smart Contract — contracts/DataStorage.sol

  • Solidity ^0.8.20 with OpenZeppelin Ownable and ReentrancyGuard
  • Stores per-device records: IPFS CID, SHA-256 data hash, device address, data type, block timestamp
  • Functions: registerDevice (owner-only), storeDataHash, verifyDataIntegrity, grantAccess / revokeAccess, enumeration helpers
  • Deployed to localhost (Ganache) and Polygon Amoy testnet

FastAPI Backend — app/

Endpoint Method Purpose
/api/iot/register POST Enroll device with ECDSA public key; registers on-chain
/api/iot/upload POST Accept encrypted + signed sensor payload; full validation pipeline
/api/iot/verify/integrity POST Recompute hash and validate against chain record
/api/iot/devices/{id}/status GET Device registration status
/api/iot/devices/{id}/uploads GET Paginated upload history
/api/iot/onchain/records GET Recent on-chain data records

IoT Device Simulator — iot_device/device_simulator.py

  • Generates mock sensor data (temperature, humidity, etc.)
  • Derives Ethereum-style device address from ECDSA secp256k1 public key (keccak, last 20 bytes)
  • Encrypts payload with AES-256-GCM, signs canonical upload envelope

Cryptographic Services — app/services/

  • encryption.py — AES-256-GCM encrypt/decrypt with canonical JSON serialization
  • signature.py — canonical JSON bytes, SHA-256 digest, ECDSA secp256k1 signature verification
  • registry.py — in-memory device registry with nonce cache and timestamp freshness check

Architecture & System Design

Tech Stack

Layer Technology Role
Smart Contract Solidity 0.8.20, OpenZeppelin Immutable on-chain data anchoring
Blockchain Ganache (local), Polygon Amoy (testnet) EVM execution environment
Web3 Interface Web3.py Python ↔ EVM bridge
Backend API FastAPI, Uvicorn, Pydantic v2 Secure HTTP API with request validation
Decentralized Storage IPFS (ipfshttpclient) Content-addressed plaintext storage
Payload Crypto cryptography (AES-256-GCM) Authenticated encryption
Identity Crypto ecdsa (secp256k1) Device authentication and non-repudiation
Contract Tooling py-solc-x, npm @openzeppelin/contracts Compilation and deployment
Testing Pytest, FastAPI TestClient Automated security regression suite

Data Flow (Step-by-Step)

  1. Device sends POST /register with device_id (IOT-... prefix enforced) and ECDSA public key
  2. Backend derives Ethereum checksum address from public key; calls registerDevice on-chain
  3. Device constructs upload: encrypts sensor payload (AES-256-GCM), signs canonical envelope (ECDSA), attaches timestamp and nonce
  4. Backend validates: registered device → timestamp freshness (configurable REPLAY_WINDOW_SECONDS) → nonce uniqueness → ECDSA signature
  5. Backend decrypts payload, serializes to canonical JSON, computes SHA-256
  6. Backend uploads plaintext to IPFS; receives content CID
  7. Backend calls storeDataHash(cid, dataType, deviceAddress, dataHash) on-chain; returns IPFS CID + tx hash

Key Architectural Decisions

Decision Rationale Trade-off
Canonical JSON before hashing/signing Eliminates key-ordering ambiguity across languages Slight serialization overhead
AES-256-GCM (authenticated encryption) Provides both confidentiality AND integrity in one primitive Requires secure key distribution
ECDSA secp256k1 (same curve as Ethereum) Enables on-chain address derivation from public key without extra key types Requires careful DER/raw format handling
In-memory nonce cache Simple, fast replay prevention for prototype Non-persistent — cleared on restart
IPFS for raw payload, blockchain for hash only Keeps transaction costs low; IPFS provides content-addressing IPFS availability is not guaranteed (no pinning in prototype)
Localhost-first (Ganache) Zero-cost deterministic testing environment Not production; requires explicit deploy step

Security Controls

Each control maps to a concrete code path and an automated test:

Control Implementation Automated Test
Device authentication ECDSA secp256k1 signature verification on every upload test_upload_rejected_on_invalid_signature
Payload confidentiality AES-256-GCM encryption end-to-end test_encryption.py — round-trip
Payload integrity (crypto) AES-GCM authentication tag; tampered ciphertext raises InvalidTag test_encrypt_then_tamper_decrypt_raises
Replay prevention Nonce uniqueness cache + timestamp freshness window test_upload_rejected_on_replay_nonce
Immutable audit trail SHA-256 hash anchored on blockchain; verifyDataIntegrity call test_verify_integrity_true_and_false
Device authorization Registration check before any upload is processed test_upload_rejected_when_unregistered
Structured security logging All rejection paths emit structured log events Backend log output
Startup validation Missing/malformed env vars raise immediately at launch app/config.pyget_settings()

Latest test run: 9 passed, 4 warnings


Prerequisites & Installation

Prerequisites

Dependency Minimum Version Purpose
Python 3.10+ Backend and device simulator
Node.js + npm 18.x OpenZeppelin contract dependencies
Ganache CLI Latest Local EVM (or Docker equivalent)
IPFS Kubo Latest Local IPFS node
Docker Desktop Optional Run IPFS as container

Installation

1. Clone the repository

git clone https://github.com/menotliam/Intergrating-Blockchain-In-IOT.git
cd Intergrating-Blockchain-In-IOT

2. Create and activate a Python virtual environment

python -m venv myenv
myenv\Scripts\Activate.ps1

3. Install Python dependencies

pip install -r requirement.txt

4. Install Solidity/OpenZeppelin dependencies

npm install
cd contracts && npm install && cd ..

5. Start local blockchain & IPFS

npx ganache --host 127.0.0.1 --port 8545 --chain.chainId 1337 --wallet.totalAccounts 5 # Terminal 1st
# Copy Account #0 address, private key --> ACCOUNT_ADDRESS, PRIVATE_KEY (without 0x)
ipfs daemon # Terminal 2nd

6. Configure environment variables

cp .env.example .env
# Generate AES key:
python -c "import os,base64; print(base64.b64encode(os.urandom(32)).decode())"
# Edit .env — replace CONTRACT_ADDRESS, PRIVATE_KEY, and DEVICE_SHARED_AES_KEY

7. Compile and deploy the smart contract

python contracts/compile.py
python contracts/deploy.py localhost
# Copy the printed CONTRACT_ADDRESS into .env

Usage / Quick Start

Register a Device

curl -X POST http://localhost:8000/api/iot/register \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "IOT-DEMO001",
    "public_key": "<hex-encoded-ecdsa-secp256k1-public-key>"
  }'

Expected response:

{
  "status": "registered",
  "device_id": "IOT-DEMO001",
  "device_address": "0xAbCd...",
  "tx_hash": "0x1a2b..."
}

Upload an Encrypted Payload

curl -X POST http://localhost:8000/api/iot/upload \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "IOT-DEMO001",
    "encrypted_payload": { "ciphertext": "...", "nonce": "...", "tag": "..." },
    "timestamp": 1712600000,
    "nonce": "unique-uuid-v4",
    "signature": "<hex-ecdsa-signature>"
  }'

Expected response:

{
  "status": "stored",
  "ipfs_cid": "QmXyz...",
  "tx_hash": "0x3c4d...",
  "data_hash": "sha256:abcd..."
}

Verify Data Integrity

curl -X POST http://localhost:8000/api/iot/verify/integrity \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "IOT-DEMO001",
    "plain_data": { "temperature": 22.5, "humidity": 60 },
    "record_index": 0
  }'

Expected response:

{
  "integrity_ok": true,
  "chain_hash": "sha256:abcd...",
  "computed_hash": "sha256:abcd..."
}

Run the Device Simulator

# Initialize device keys and register
python iot_device/device_simulator.py init_device

Testing

Tests use Pytest with a FastAPI TestClient. Blockchain and IPFS calls are replaced with test doubles so the full security logic runs without external infrastructure.

Run the Full Test Suite

.\scripts\run_tests.ps1

Or directly:

$env:PYTEST_DISABLE_PLUGIN_AUTOLOAD='1'; python -m pytest -q tests

Test Coverage by Area

Test File What It Validates
tests/test_encryption.py AES-256-GCM round-trip; tampered ciphertext raises InvalidTag
tests/test_signature.py Valid ECDSA verification passes; tampered payload fails
tests/test_iot_routes.py Full route security: register, upload success, replay rejection, invalid signature rejection, unregistered device rejection, integrity endpoint true/false, paginated on-chain records

Security Test Cases

Scenario Expected HTTP Status Test Name
Valid register + upload 200 test_upload_success_after_registration
Unregistered device upload 401 test_upload_rejected_when_unregistered
Invalid ECDSA signature 401 test_upload_rejected_on_invalid_signature
Replayed nonce 401 test_upload_rejected_on_replay_nonce
Integrity check — match integrity_ok: true test_verify_integrity_true_and_false
Integrity check — tampered integrity_ok: false test_verify_integrity_true_and_false

Configuration / Environment Variables

Copy .env.example to .env and populate each value. Never commit .env to source control.

Variable Type Default / Example Description
APP_HOST string 0.0.0.0 FastAPI bind address
APP_PORT int 8000 FastAPI listen port
BLOCKCHAIN_RPC_URL string http://127.0.0.1:8545 JSON-RPC endpoint (Ganache or testnet)
CONTRACT_ADDRESS string 0x0000... Deployed DataStorage.sol address
ACCOUNT_ADDRESS string 0xdB44... Ethereum account used to send transactions
PRIVATE_KEY string (no default) Hex private key for transaction signing (no 0x prefix)
CONTRACT_ABI_PATH string contracts/build/artifacts/DataStorage.json Path to compiled ABI file
IPFS_API_URL string /dns/localhost/tcp/5001/http IPFS multiaddr API endpoint
DEVICE_SHARED_AES_KEY string (no default) Base64-encoded 32-byte AES-256-GCM key
REPLAY_WINDOW_SECONDS int 300 Maximum age of an accepted upload timestamp (seconds)
DEVICE_ID string IOT-DEMO001 Simulator device identifier (must start with IOT-)
PRIVATE_KEY_PATH string iot_device/device_private_key.pem Path to device ECDSA private key PEM file
BACKEND_REGISTER_URL string http://localhost:8000/api/iot/register Simulator registration endpoint
BACKEND_UPLOAD_URL string http://localhost:8000/api/iot/upload Simulator upload endpoint

Security note: PRIVATE_KEY and DEVICE_SHARED_AES_KEY are secrets. Rotate them before any non-local use. The .env.example file contains placeholder values only — they are not production keys.


Future Work

This is an intentional prototype. The following areas are defined for production hardening:

Area Gap in Prototype Proposed Direction
Persistence Device registry and nonce cache are in-memory only Add PostgreSQL/Redis with encrypted-at-rest storage
API Authentication No operator-level auth on backend endpoints Implement JWT or mTLS for API consumers
Key Management Shared AES key via env var; no lifecycle management Integrate AWS KMS, HashiCorp Vault, or TPM-backed HSM
Contract Modularity Single DataStorage.sol handles all concerns Split into Registry, AuditLog, and AccessControl contracts
Threat-Driven Testing Happy-path and rejection tests only Add DoS simulation, key-compromise scenarios, and fault injection
Testnet / Mainnet Deployment Localhost only; Amoy deploy exists but untested at scale Model gas costs, finality, and key custody for a target testnet
IPFS Durability No pinning or replication Integrate Pinata or Filecoin for guaranteed persistence
Observability Structured logging only Add Prometheus metrics, distributed tracing, and alerting

👨‍💻 Author

Ngo Giang
Aspiring Cybersecurity & Network Engineer


License

This project is released under the MIT License.


Prototype built for cybersecurity research and learning. Not intended for production deployment without addressing the future-work items above.

About

A security-first research prototype that enforces IoT data authenticity, confidentiality, integrity, and replay resistance by anchoring cryptographic proofs on a local Ethereum blockchain (Ganache) and storing raw payloads on IPFS.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages