Introduction
Project Name: ChainLog: Immutable System Logging Framework
Concept: Traditional system logs are vulnerable to tampering - malicious actors can modify or delete logs to cover their tracks. This project creates an immutable log book using blockchain principles, where each log entry is cryptographically linked to the previous one, making any unauthorized alteration immediately detectable.
The Blockchain Solution: Instead of storing logs in a simple database table where they can be easily modified, we implement a blockchain structure specifically for logs. Each log entry becomes a "block" containing the log data, timestamp, and a hash of the previous log. Any change to a historical log would break the chain of hashes, immediately revealing the tampering attempt.
Features
- 🔗 Immutable Chain: Each log entry is cryptographically linked to the previous entry
- 🕒 Tamper Detection: Any modification to historical logs breaks the hash chain
- 📝 Real-time Logging: System events are instantly recorded and chained
- 🔍 Verification System: Verify the integrity of the entire log chain
- 📊 Log Viewer: Browse logs with visual integrity indicators
- 🔐 Cryptographic Proof: Each log has its own hash and references the previous log's hash
- 🚨 Tamper Alerts: Visual indicators show if logs have been compromised
Project File Structure
tamper-proof-logbook/ │ ├── index.php # Main application UI ├── style.css # Custom styling ├── script.js # Frontend JavaScript ├── api/ │ ├── add_log.php # API endpoint to add new logs │ ├── get_logs.php # API endpoint to retrieve logs │ ├── verify_chain.php # API endpoint to verify chain integrity │ └── simulate_tamper.php # Test endpoint to demonstrate tampering ├── includes/ │ ├── config.php # Database configuration │ └── BlockchainLog.php # Core blockchain logic for logs ├── database/ │ └── schema.sql # Database schema └── README.md # Project documentation
Database Setup (MySQL)
1. Create a database named chainlog_db.
2. Run the database/schema.sql script:
CREATE DATABASE IF NOT EXISTS chainlog_db;
USE chainlog_db;
CREATE TABLE IF NOT EXISTS log_chain (
id INT AUTO_INCREMENT PRIMARY KEY,
block_index INT NOT NULL,
log_level VARCHAR(20) NOT NULL,
log_message TEXT NOT NULL,
log_source VARCHAR(100),
timestamp BIGINT NOT NULL,
previous_hash VARCHAR(64) NOT NULL,
hash VARCHAR(64) NOT NULL UNIQUE,
nonce INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_block_index (block_index),
INDEX idx_timestamp (timestamp)
);
-- Insert Genesis Log (First block in the chain)
INSERT INTO log_chain (block_index, log_level, log_message, log_source, timestamp, previous_hash, hash, nonce)
VALUES (0, 'SYSTEM', 'Genesis Log - Chain Initialized', 'System', UNIX_TIMESTAMP(), '0',
SHA2(CONCAT('0', UNIX_TIMESTAMP(), 'Genesis Log - Chain Initialized', 'System', '0', '0'), 256), 0);
Code Implementation
1. includes/config.php (Database Configuration)
<?php
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'chainlog_db');
function getDBConnection() {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
die(json_encode(['success' => false, 'message' => 'Database connection failed: ' . $conn->connect_error]));
}
return $conn;
}
// Set timezone
date_default_timezone_set('UTC');
?>
2. includes/BlockchainLog.php (Core Blockchain Logic for Logs)
<?php
require_once 'config.php';
class BlockchainLog {
private $conn;
private $difficulty = 2; // Proof of work difficulty
public function __construct() {
$this->conn = getDBConnection();
}
/**
* Calculate hash for a log block
*/
public static function calculateHash($index, $timestamp, $logLevel, $logMessage, $logSource, $previousHash, $nonce) {
$data = $index . $timestamp . $logLevel . $logMessage . $logSource . $previousHash . $nonce;
return hash('sha256', $data);
}
/**
* Get the last log from the chain
*/
public function getLastLog() {
$sql = "SELECT * FROM log_chain ORDER BY block_index DESC LIMIT 1";
$result = $this->conn->query($sql);
if ($result && $result->num_rows > 0) {
return $result->fetch_assoc();
}
return null;
}
/**
* Get all logs in order
*/
public function getAllLogs() {
$sql = "SELECT * FROM log_chain ORDER BY block_index ASC";
$result = $this->conn->query($sql);
$logs = [];
if ($result) {
while ($row = $result->fetch_assoc()) {
$logs[] = $row;
}
}
return $logs;
}
/**
* Proof of Work for log blocks
*/
public function proofOfWork($index, $timestamp, $logLevel, $logMessage, $logSource, $previousHash) {
$nonce = 0;
$prefix = str_repeat('0', $this->difficulty);
while (true) {
$hash = $this->calculateHash($index, $timestamp, $logLevel, $logMessage, $logSource, $previousHash, $nonce);
if (substr($hash, 0, $this->difficulty) === $prefix) {
return ['nonce' => $nonce, 'hash' => $hash];
}
$nonce++;
// Prevent infinite loop in extreme cases
if ($nonce > 1000000) {
throw new Exception("Proof of work took too long");
}
}
}
/**
* Add a new log entry to the chain
*/
public function addLog($logLevel, $logMessage, $logSource = 'System') {
try {
// Validate log level
$validLevels = ['INFO', 'WARNING', 'ERROR', 'CRITICAL', 'DEBUG', 'SYSTEM'];
if (!in_array($logLevel, $validLevels)) {
$logLevel = 'INFO';
}
// Get last log
$lastLog = $this->getLastLog();
if (!$lastLog) {
return ['success' => false, 'message' => 'Log chain corrupted. Genesis block missing.'];
}
$newIndex = $lastLog['block_index'] + 1;
$timestamp = time();
$previousHash = $lastLog['hash'];
// Perform Proof of Work
$powResult = $this->proofOfWork($newIndex, $timestamp, $logLevel, $logMessage, $logSource, $previousHash);
// Insert new log
$sql = "INSERT INTO log_chain (block_index, log_level, log_message, log_source, timestamp, previous_hash, hash, nonce)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("isssissi",
$newIndex,
$logLevel,
$logMessage,
$logSource,
$timestamp,
$previousHash,
$powResult['hash'],
$powResult['nonce']
);
if ($stmt->execute()) {
return [
'success' => true,
'message' => 'Log added successfully to the chain',
'log' => [
'index' => $newIndex,
'level' => $logLevel,
'message' => $logMessage,
'source' => $logSource,
'timestamp' => $timestamp,
'hash' => $powResult['hash'],
'previous_hash' => $previousHash,
'nonce' => $powResult['nonce']
]
];
} else {
throw new Exception("Failed to insert log: " . $stmt->error);
}
} catch (Exception $e) {
return ['success' => false, 'message' => 'Error adding log: ' . $e->getMessage()];
}
}
/**
* Verify the integrity of the entire log chain
*/
public function verifyChainIntegrity() {
$logs = $this->getAllLogs();
$results = [
'valid' => true,
'total_blocks' => count($logs),
'checks' => [],
'tampered_blocks' => []
];
if (empty($logs)) {
$results['valid'] = false;
$results['message'] = 'No logs found';
return $results;
}
// Verify genesis block
$genesis = $logs[0];
$expectedGenesisHash = $this->calculateHash(
$genesis['block_index'],
$genesis['timestamp'],
$genesis['log_level'],
$genesis['log_message'],
$genesis['log_source'],
$genesis['previous_hash'],
$genesis['nonce']
);
$genesisValid = ($expectedGenesisHash === $genesis['hash']);
$results['checks'][] = [
'block_index' => $genesis['block_index'],
'valid' => $genesisValid,
'expected_hash' => $expectedGenesisHash,
'actual_hash' => $genesis['hash']
];
if (!$genesisValid) {
$results['valid'] = false;
$results['tampered_blocks'][] = $genesis['block_index'];
}
// Verify subsequent blocks
for ($i = 1; $i < count($logs); $i++) {
$current = $logs[$i];
$previous = $logs[$i - 1];
// Check 1: Does current block's previous_hash match previous block's hash?
$linkValid = ($current['previous_hash'] === $previous['hash']);
// Check 2: Is current block's hash valid?
$expectedHash = $this->calculateHash(
$current['block_index'],
$current['timestamp'],
$current['log_level'],
$current['log_message'],
$current['log_source'],
$current['previous_hash'],
$current['nonce']
);
$hashValid = ($expectedHash === $current['hash']);
$blockValid = $linkValid && $hashValid;
$results['checks'][] = [
'block_index' => $current['block_index'],
'valid' => $blockValid,
'link_valid' => $linkValid,
'hash_valid' => $hashValid,
'expected_hash' => $expectedHash,
'actual_hash' => $current['hash']
];
if (!$blockValid) {
$results['valid'] = false;
$results['tampered_blocks'][] = $current['block_index'];
}
}
$results['message'] = $results['valid'] ? '✅ Log chain is intact and valid' : '⚠️ Chain tampering detected!';
return $results;
}
/**
* Simulate tampering with a log (for demonstration)
*/
public function simulateTampering($blockIndex, $newMessage) {
// This is just for demonstration - in reality, you'd want to prevent this!
$sql = "UPDATE log_chain SET log_message = ? WHERE block_index = ?";
$stmt = $this->conn->prepare($sql);
$stmt->bind_param("si", $newMessage, $blockIndex);
if ($stmt->execute()) {
return ['success' => true, 'message' => "Tampering simulated on block $blockIndex"];
}
return ['success' => false, 'message' => 'Failed to simulate tampering'];
}
public function __destruct() {
$this->conn->close();
}
}
?>
3. api/add_log.php (Add Log Endpoint)
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
require_once '../includes/BlockchainLog.php';
// Only accept POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit;
}
// Get JSON input
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
// Try form data as fallback
$logLevel = $_POST['log_level'] ?? 'INFO';
$logMessage = $_POST['log_message'] ?? '';
$logSource = $_POST['log_source'] ?? 'System';
} else {
$logLevel = $input['log_level'] ?? 'INFO';
$logMessage = $input['log_message'] ?? '';
$logSource = $input['log_source'] ?? 'System';
}
// Validate input
if (empty($logMessage)) {
echo json_encode(['success' => false, 'message' => 'Log message is required']);
exit;
}
// Add log to blockchain
$blockchain = new BlockchainLog();
$result = $blockchain->addLog($logLevel, $logMessage, $logSource);
echo json_encode($result);
?>
4. api/get_logs.php (Retrieve Logs Endpoint)
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../includes/BlockchainLog.php';
$blockchain = new BlockchainLog();
$logs = $blockchain->getAllLogs();
// Format timestamps for display
foreach ($logs as &$log) {
$log['formatted_time'] = date('Y-m-d H:i:s', $log['timestamp']);
$log['short_hash'] = substr($log['hash'], 0, 16) . '...';
$log['short_prev_hash'] = substr($log['previous_hash'], 0, 16) . '...';
}
echo json_encode([
'success' => true,
'total_logs' => count($logs),
'logs' => $logs
]);
?>
5. api/verify_chain.php (Verify Chain Integrity)
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
require_once '../includes/BlockchainLog.php';
$blockchain = new BlockchainLog();
$verification = $blockchain->verifyChainIntegrity();
echo json_encode($verification);
?>
6. api/simulate_tamper.php (Test Tampering - FOR DEMO ONLY)
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Content-Type');
require_once '../includes/BlockchainLog.php';
// Only accept POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'message' => 'Method not allowed']);
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['block_index']) || !isset($input['new_message'])) {
echo json_encode(['success' => false, 'message' => 'Block index and new message required']);
exit;
}
// WARNING: This is for demonstration only!
$blockchain = new BlockchainLog();
$result = $blockchain->simulateTampering($input['block_index'], $input['new_message']);
echo json_encode($result);
?>
7. index.php (Main Application UI)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChainLog | Tamper-Proof Log Book</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap');
body {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
font-family: 'Fira Code', monospace;
}
.log-entry {
transition: all 0.3s ease;
}
.log-entry:hover {
transform: translateX(5px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.chain-link {
border-left: 3px solid #4f46e5;
}
.tampered {
background: linear-gradient(90deg, rgba(239, 68, 68, 0.1) 0%, rgba(239, 68, 68, 0.05) 100%);
border-left: 3px solid #ef4444;
}
.valid {
border-left: 3px solid #10b981;
}
.hash-text {
font-size: 0.7rem;
word-break: break-all;
}
.glow {
animation: glow 2s ease-in-out infinite;
}
@keyframes glow {
0%, 100% { box-shadow: 0 0 5px rgba(79, 70, 229, 0.5); }
50% { box-shadow: 0 0 20px rgba(79, 70, 229, 0.8); }
}
</style>
</head>
<body class="min-h-screen text-gray-100 p-6">
<div class="max-w-7xl mx-auto">
<!-- Header -->
<div class="text-center mb-8">
<div class="flex items-center justify-center mb-4">
<i class="fas fa-link text-5xl text-indigo-400 mr-3"></i>
<h1 class="text-5xl font-bold bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent">
ChainLog
</h1>
</div>
<p class="text-xl text-gray-300 mb-2">Tamper-Proof System Log Book</p>
<p class="text-gray-400 max-w-2xl mx-auto">
Every log entry is cryptographically linked to the previous one.
Any unauthorized modification breaks the chain and is immediately detectable.
</p>
</div>
<!-- Status Bar -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
<div class="flex items-center">
<i class="fas fa-shield-alt text-2xl text-indigo-400 mr-3"></i>
<div>
<p class="text-sm text-gray-400">Chain Status</p>
<p id="chainStatus" class="text-lg font-semibold text-yellow-400">Checking...</p>
</div>
</div>
</div>
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
<div class="flex items-center">
<i class="fas fa-cube text-2xl text-indigo-400 mr-3"></i>
<div>
<p class="text-sm text-gray-400">Total Logs</p>
<p id="totalLogs" class="text-lg font-semibold">0</p>
</div>
</div>
</div>
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
<div class="flex items-center">
<i class="fas fa-clock text-2xl text-indigo-400 mr-3"></i>
<div>
<p class="text-sm text-gray-400">Last Updated</p>
<p id="lastUpdated" class="text-lg font-semibold">-</p>
</div>
</div>
</div>
</div>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Log Entry Form -->
<div class="lg:col-span-1">
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 sticky top-6">
<h2 class="text-xl font-bold mb-4 flex items-center">
<i class="fas fa-pen-fancy text-indigo-400 mr-2"></i>
Add New Log Entry
</h2>
<form id="logForm" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">Log Level</label>
<select id="logLevel" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500">
<option value="INFO">📝 INFO</option>
<option value="WARNING">⚠️ WARNING</option>
<option value="ERROR">❌ ERROR</option>
<option value="CRITICAL">🔥 CRITICAL</option>
<option value="DEBUG">🔍 DEBUG</option>
<option value="SYSTEM">⚙️ SYSTEM</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">Source</label>
<input type="text" id="logSource" value="System" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">Message</label>
<textarea id="logMessage" rows="4" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter log message..."></textarea>
</div>
<button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition duration-300 flex items-center justify-center">
<i class="fas fa-link mr-2"></i>
Add to Chain
</button>
</form>
<!-- Quick Actions -->
<div class="mt-6 pt-6 border-t border-gray-700">
<h3 class="text-sm font-medium text-gray-400 mb-3">Quick Actions</h3>
<div class="space-y-2">
<button onclick="addSampleLogs()" class="w-full bg-gray-700 hover:bg-gray-600 text-gray-300 text-sm py-2 px-3 rounded-lg transition flex items-center">
<i class="fas fa-flask mr-2"></i>
Add Sample Logs
</button>
<button onclick="verifyChain()" class="w-full bg-gray-700 hover:bg-gray-600 text-gray-300 text-sm py-2 px-3 rounded-lg transition flex items-center">
<i class="fas fa-check-circle mr-2"></i>
Verify Chain
</button>
<button onclick="refreshLogs()" class="w-full bg-gray-700 hover:bg-gray-600 text-gray-300 text-sm py-2 px-3 rounded-lg transition flex items-center">
<i class="fas fa-sync-alt mr-2"></i>
Refresh Logs
</button>
</div>
</div>
<!-- Demo Controls -->
<div class="mt-4 p-3 bg-yellow-900 bg-opacity-30 rounded-lg border border-yellow-700">
<p class="text-xs text-yellow-300 mb-2 flex items-center">
<i class="fas fa-exclamation-triangle mr-1"></i>
Demo Controls (Testing Only)
</p>
<button onclick="simulateTampering()" class="w-full bg-red-900 hover:bg-red-800 text-red-300 text-sm py-2 px-3 rounded-lg transition flex items-center">
<i class="fas fa-skull-crossbones mr-2"></i>
Simulate Tampering
</button>
</div>
</div>
</div>
<!-- Log Display -->
<div class="lg:col-span-2">
<div class="bg-gray-800 rounded-lg border border-gray-700 overflow-hidden">
<div class="p-4 border-b border-gray-700 flex justify-between items-center">
<h2 class="text-xl font-bold flex items-center">
<i class="fas fa-list text-indigo-400 mr-2"></i>
Log Chain
</h2>
<div class="flex space-x-2">
<span class="text-xs bg-gray-700 px-3 py-1 rounded-full text-gray-300" id="logCount">0 entries</span>
</div>
</div>
<div id="logContainer" class="p-4 space-y-3 max-h-[600px] overflow-y-auto">
<!-- Logs will be loaded here -->
<div class="text-center text-gray-500 py-8">
<i class="fas fa-spinner fa-spin text-3xl mb-2"></i>
<p>Loading logs...</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Verification Modal -->
<div id="verificationModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
<div class="bg-gray-800 rounded-lg p-6 max-w-2xl w-full mx-4 border border-gray-700">
<h3 class="text-xl font-bold mb-4">Chain Verification Result</h3>
<div id="verificationResult" class="space-y-4 max-h-96 overflow-y-auto">
<!-- Results will be inserted here -->
</div>
<button onclick="closeModal()" class="mt-4 w-full bg-gray-700 hover:bg-gray-600 text-white py-2 rounded-lg transition">
Close
</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
8. script.js (Frontend JavaScript)
// script.js
// Load logs on page load
document.addEventListener('DOMContentLoaded', function() {
refreshLogs();
verifyChain();
// Set up form submission
document.getElementById('logForm').addEventListener('submit', function(e) {
e.preventDefault();
addLog();
});
// Auto-refresh every 30 seconds
setInterval(refreshLogs, 30000);
});
// Add a new log
async function addLog() {
const logLevel = document.getElementById('logLevel').value;
const logSource = document.getElementById('logSource').value;
const logMessage = document.getElementById('logMessage').value;
if (!logMessage) {
alert('Please enter a log message');
return;
}
const logData = {
log_level: logLevel,
log_source: logSource,
log_message: logMessage
};
try {
const response = await fetch('api/add_log.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(logData)
});
const data = await response.json();
if (data.success) {
// Clear form
document.getElementById('logMessage').value = '';
// Refresh logs
refreshLogs();
verifyChain();
// Show success message
showNotification('Log added successfully!', 'success');
} else {
showNotification('Error: ' + data.message, 'error');
}
} catch (error) {
console.error('Error:', error);
showNotification('Failed to add log', 'error');
}
}
// Refresh logs display
async function refreshLogs() {
try {
const response = await fetch('api/get_logs.php');
const data = await response.json();
if (data.success) {
displayLogs(data.logs);
document.getElementById('totalLogs').textContent = data.total_logs;
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
document.getElementById('logCount').textContent = data.total_logs + ' entries';
}
} catch (error) {
console.error('Error:', error);
}
}
// Display logs in the container
function displayLogs(logs) {
const container = document.getElementById('logContainer');
if (!logs || logs.length === 0) {
container.innerHTML = '<div class="text-center text-gray-500 py-8">No logs found</div>';
return;
}
let html = '';
logs.forEach((log, index) => {
const isGenesis = log.block_index === 0;
const logClass = getLogLevelClass(log.log_level);
const timeStr = new Date(log.timestamp * 1000).toLocaleString();
html += `
<div class="log-entry bg-gray-700 bg-opacity-50 rounded-lg p-4 border border-gray-600 ${log.valid ? 'valid' : ''}" data-index="${log.block_index}">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center space-x-2">
<span class="text-xs bg-gray-600 px-2 py-1 rounded">#${log.block_index}</span>
<span class="text-xs ${logClass} px-2 py-1 rounded">${log.log_level}</span>
${isGenesis ? '<span class="text-xs bg-purple-600 px-2 py-1 rounded">GENESIS</span>' : ''}
</div>
<span class="text-xs text-gray-400">${timeStr}</span>
</div>
<p class="text-sm mb-2">${escapeHtml(log.log_message)}</p>
<div class="text-xs text-gray-400 space-y-1 mt-3 pt-2 border-t border-gray-600">
<div class="flex items-start">
<span class="text-gray-500 w-20">Source:</span>
<span class="text-gray-300">${escapeHtml(log.log_source || 'System')}</span>
</div>
<div class="flex items-start">
<span class="text-gray-500 w-20">Previous:</span>
<span class="font-mono text-green-400">${log.short_prev_hash}</span>
</div>
<div class="flex items-start">
<span class="text-gray-500 w-20">Hash:</span>
<span class="font-mono text-blue-400">${log.short_hash}</span>
</div>
<div class="flex items-start">
<span class="text-gray-500 w-20">Nonce:</span>
<span class="text-gray-300">${log.nonce}</span>
</div>
</div>
${!isGenesis ? `
<div class="mt-2 flex items-center text-xs text-gray-500">
<i class="fas fa-link mr-1 text-indigo-400"></i>
<span>Linked to block #${log.block_index - 1}</span>
</div>
` : ''}
</div>
`;
});
container.innerHTML = html;
}
// Verify chain integrity
async function verifyChain() {
try {
const response = await fetch('api/verify_chain.php');
const data = await response.json();
const statusElement = document.getElementById('chainStatus');
const logContainer = document.getElementById('logContainer');
if (data.valid) {
statusElement.innerHTML = '<span class="text-green-400">✅ Valid & Intact</span>';
// Remove tampered class from all logs
document.querySelectorAll('.log-entry').forEach(el => {
el.classList.remove('tampered');
});
} else {
statusElement.innerHTML = '<span class="text-red-400">⚠️ TAMPERED DETECTED</span>';
// Highlight tampered blocks
if (data.tampered_blocks) {
data.tampered_blocks.forEach(blockIndex => {
const logElement = document.querySelector(`.log-entry[data-index="${blockIndex}"]`);
if (logElement) {
logElement.classList.add('tampered');
}
});
}
}
// Show verification details in console
console.log('Chain Verification:', data);
} catch (error) {
console.error('Error verifying chain:', error);
}
}
// Add sample logs for demonstration
async function addSampleLogs() {
const samples = [
{ level: 'INFO', source: 'Auth Service', message: 'User login successful - [email protected]' },
{ level: 'INFO', source: 'Database', message: 'Connection pool initialized' },
{ level: 'WARNING', source: 'API Gateway', message: 'Rate limit approaching for IP 192.168.1.100' },
{ level: 'ERROR', source: 'Payment Service', message: 'Failed to process transaction #TX-12345' },
{ level: 'INFO', source: 'Cache', message: 'Redis cache cleared' },
{ level: 'CRITICAL', source: 'System', message: 'CPU usage exceeded 95% for 5 minutes' },
{ level: 'DEBUG', source: 'Auth Service', message: 'JWT token validation took 150ms' },
{ level: 'INFO', source: 'Cron Job', message: 'Daily backup completed - 2.3GB' }
];
for (const sample of samples) {
const logData = {
log_level: sample.level,
log_source: sample.source,
log_message: sample.message
};
try {
await fetch('api/add_log.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logData)
});
} catch (error) {
console.error('Error adding sample log:', error);
}
}
refreshLogs();
verifyChain();
showNotification('Sample logs added!', 'success');
}
// Simulate tampering (demo only)
async function simulateTampering() {
const blockIndex = prompt('Enter block index to tamper with (e.g., 3):');
if (!blockIndex) return;
const newMessage = prompt('Enter new tampered message:');
if (!newMessage) return;
try {
const response = await fetch('api/simulate_tamper.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
block_index: parseInt(blockIndex),
new_message: newMessage
})
});
const data = await response.json();
if (data.success) {
refreshLogs();
verifyChain();
showNotification('Tampering simulated! Check chain status.', 'warning');
}
} catch (error) {
console.error('Error:', error);
}
}
// Helper: Get CSS class for log level
function getLogLevelClass(level) {
const classes = {
'INFO': 'bg-blue-600 text-blue-100',
'WARNING': 'bg-yellow-600 text-yellow-100',
'ERROR': 'bg-red-600 text-red-100',
'CRITICAL': 'bg-purple-600 text-purple-100',
'DEBUG': 'bg-gray-600 text-gray-100',
'SYSTEM': 'bg-indigo-600 text-indigo-100'
};
return classes[level] || 'bg-gray-600 text-gray-100';
}
// Helper: Escape HTML to prevent XSS
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Helper: Show notification
function showNotification(message, type = 'info') {
// Create notification element
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 animate-bounce ${
type === 'success' ? 'bg-green-600' :
type === 'error' ? 'bg-red-600' :
type === 'warning' ? 'bg-yellow-600' : 'bg-blue-600'
} text-white`;
notification.textContent = message;
document.body.appendChild(notification);
// Remove after 3 seconds
setTimeout(() => {
notification.remove();
}, 3000);
}
// Helper: Close modal
function closeModal() {
document.getElementById('verificationModal').classList.add('hidden');
}
9. style.css (Additional Custom Styles)
/* style.css - Additional styles beyond Tailwind */
/* Custom scrollbar for log container */
#logContainer::-webkit-scrollbar {
width: 8px;
}
#logContainer::-webkit-scrollbar-track {
background: #1f2937;
border-radius: 4px;
}
#logContainer::-webkit-scrollbar-thumb {
background: #4f46e5;
border-radius: 4px;
}
#logContainer::-webkit-scrollbar-thumb:hover {
background: #6366f1;
}
/* Chain link animation */
.chain-link-pulse {
animation: chainPulse 2s infinite;
}
@keyframes chainPulse {
0%, 100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}
/* Hash value hover effect */
.hash-value {
transition: all 0.3s ease;
cursor: pointer;
}
.hash-value:hover {
color: #4f46e5;
text-decoration: underline;
}
/* Log entry transitions */
.log-entry {
position: relative;
overflow: hidden;
}
.log-entry::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(79, 70, 229, 0.1), transparent);
transform: translateX(-100%);
transition: transform 0.5s ease;
}
.log-entry:hover::before {
transform: translateX(100%);
}
/* Tamper warning animation */
@keyframes tamperWarning {
0%, 100% {
border-color: #ef4444;
box-shadow: 0 0 5px #ef4444;
}
50% {
border-color: #dc2626;
box-shadow: 0 0 15px #dc2626;
}
}
.tampered {
animation: tamperWarning 1s infinite;
}
/* Loading spinner */
.spinner {
border: 3px solid rgba(79, 70, 229, 0.3);
border-radius: 50%;
border-top: 3px solid #4f46e5;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
How to Run
- Setup Environment:
- Ensure you have XAMPP, WAMP, or Laragon installed with PHP 7.4+ and MySQL
- Start Apache and MySQL services
- Database Setup:
- Open phpMyAdmin (http://localhost/phpmyadmin)
- Create a new database named
chainlog_db - Import the
database/schema.sqlfile to create the table and genesis block
- Configure Database:
- Open
includes/config.php - Update database credentials if different from default (root/no password)
- Place Files:
- Create a folder named
tamper-proof-logbookin your web root (e.g.,C:\xampp\htdocs\tamper-proof-logbook\) - Copy all project files maintaining the folder structure
- Set Permissions:
- Ensure the web server has write permissions to the database (not file-based, so no issues)
- Access Application:
- Open your browser and navigate to:
http://localhost/tamper-proof-logbook/
Testing the Tamper-Proof Feature
- Add Normal Logs:
- Use the form to add various log entries
- Notice how each log is linked to the previous one
- Verify Chain Integrity:
- Click "Verify Chain" button
- Initially, all logs should show as valid
- Simulate Tampering:
- Use the "Simulate Tampering" button (demo only)
- Enter a block index and new message
- The chain verification will now show tampering detected
- The tampered log will be highlighted in red
- Observe the Effect:
- Notice how modifying one log breaks the entire subsequent chain
- Each block's hash depends on the previous block's hash
- Any change breaks the cryptographic links
Security Features
- SHA-256 Hashing: Each log block uses SHA-256 for cryptographic integrity
- Proof of Work: Optional mining difficulty makes tampering computationally expensive
- Chain Verification: Automatic detection of any unauthorized modifications
- Immutable History: Once written, logs cannot be altered without breaking the chain
Use Cases
- System Administration: Track critical system events
- Security Auditing: Maintain tamper-proof audit trails
- Compliance: Meet regulatory requirements for log integrity
- Forensic Analysis: Ensure evidence integrity for investigations
This project demonstrates how blockchain principles can be applied to create an immutable logging system that immediately detects any tampering attempts, making it ideal for security-critical applications.