security operations

Implementing Log Integrity with Blockchain

Build an append-only log integrity chain using SHA-256 hash chaining for tamper detection. Each log entry is hashed with the previous entry's hash to create a blockchain-like structure where modifying any entry invalidates all subsequent hashes. Implements log ingestion, chain verification, tamper detection with pinpoint identification, and periodic checkpoint anchoring to external timestamping services.

audit-logginghash-chaininglog-integritysecurity-operationssha-256tamper-detection
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When deploying or configuring implementing log integrity with blockchain capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Familiarity with security operations concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Instructions

  1. Install dependencies: pip install requests
  2. Ingest log entries from syslog, JSON, or plain text files.
  3. For each entry, compute SHA-256 hash of: previous_hash + timestamp + log_content.
  4. Store the chain as a JSON ledger with entry index, timestamp, content hash, previous hash, and chain hash.
  5. Verify chain integrity by recomputing all hashes and detecting breaks.
  6. Optionally anchor checkpoint hashes to an external timestamping service.
python scripts/agent.py --log-file /var/log/syslog --chain-file log_chain.json --verify --output integrity_report.json

Examples

Chain Entry Structure

{"index": 42, "timestamp": "2024-01-15T10:30:00Z", "content_hash": "a1b2c3...",
 "prev_hash": "d4e5f6...", "chain_hash": "SHA256(prev_hash + timestamp + content_hash)"}

Tamper Detection

If entry 42 is modified, chain_hash[42] will not match SHA256(chain_hash[41] + ...), and all entries from 42 onward will be flagged as invalid.

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.5 KB

API Reference: Log Integrity with Blockchain Hash Chaining

hashlib - SHA-256 Hashing

import hashlib
hash_hex = hashlib.sha256("data".encode("utf-8")).hexdigest()
# Returns 64-char hex string

Chain Entry Structure

{
  "index": 0,
  "timestamp": "2024-01-15T10:30:00.000Z",
  "content_hash": "SHA256(log_entry_text)",
  "prev_hash": "0000...0000 (genesis) or previous chain_hash",
  "chain_hash": "SHA256(prev_hash + timestamp + content_hash)",
  "content_preview": "first 200 chars of log entry"
}

Chain Construction Algorithm

genesis_hash = "0" * 64
for each log_entry:
    content_hash = SHA256(log_entry)
    chain_hash = SHA256(prev_hash + timestamp + content_hash)
    store(index, timestamp, content_hash, prev_hash, chain_hash)
    prev_hash = chain_hash

Verification Algorithm

prev_hash = genesis_hash
for each entry in chain:
    expected = SHA256(prev_hash + entry.timestamp + entry.content_hash)
    if expected != entry.chain_hash:
        TAMPER DETECTED at index
    prev_hash = entry.chain_hash

Checkpoint Structure

{
  "timestamp": "2024-01-15T12:00:00Z",
  "chain_length": 1000,
  "head_hash": "chain_hash of last entry",
  "head_index": 999,
  "checkpoint_hash": "SHA256(chain_length + head_hash)"
}

Tamper Detection Properties

  • Modifying any entry invalidates all subsequent chain_hashes
  • First break index identifies the tampered entry
  • Checkpoint comparison detects retroactive modifications

Scripts 1

agent.py7.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Log Integrity Chain Agent - Implements SHA-256 hash-chained append-only log for tamper detection."""

import json
import hashlib
import logging
import argparse
from datetime import datetime

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

GENESIS_HASH = "0" * 64


def compute_hash(data):
    """Compute SHA-256 hash of a string."""
    return hashlib.sha256(data.encode("utf-8")).hexdigest()


def create_chain_entry(index, timestamp, content, prev_hash):
    """Create a single chain entry with hash linking."""
    content_hash = compute_hash(content)
    chain_input = f"{prev_hash}{timestamp}{content_hash}"
    chain_hash = compute_hash(chain_input)
    return {
        "index": index,
        "timestamp": timestamp,
        "content_hash": content_hash,
        "prev_hash": prev_hash,
        "chain_hash": chain_hash,
        "content_preview": content[:200],
    }


def load_chain(chain_file):
    """Load an existing hash chain from a JSON file."""
    try:
        with open(chain_file, "r") as f:
            chain = json.load(f)
        logger.info("Loaded chain with %d entries from %s", len(chain), chain_file)
        return chain
    except FileNotFoundError:
        logger.info("No existing chain found, starting new chain")
        return []


def save_chain(chain, chain_file):
    """Save the hash chain to a JSON file."""
    with open(chain_file, "w") as f:
        json.dump(chain, f, indent=2)
    logger.info("Saved chain with %d entries to %s", len(chain), chain_file)


def ingest_log_file(log_file):
    """Read log entries from a file (one entry per line)."""
    entries = []
    with open(log_file, "r", errors="ignore") as f:
        for line in f:
            line = line.strip()
            if line:
                entries.append(line)
    logger.info("Read %d log entries from %s", len(entries), log_file)
    return entries


def ingest_json_log(json_file):
    """Read structured log entries from a JSON array file."""
    with open(json_file, "r") as f:
        data = json.load(f)
    entries = []
    if isinstance(data, list):
        for item in data:
            entries.append(json.dumps(item, sort_keys=True))
    logger.info("Read %d JSON log entries from %s", len(entries), json_file)
    return entries


def append_entries(chain, log_entries):
    """Append new log entries to the hash chain."""
    prev_hash = chain[-1]["chain_hash"] if chain else GENESIS_HASH
    start_index = len(chain)
    new_entries = []
    for i, content in enumerate(log_entries):
        timestamp = datetime.utcnow().isoformat() + "Z"
        entry = create_chain_entry(start_index + i, timestamp, content, prev_hash)
        chain.append(entry)
        new_entries.append(entry)
        prev_hash = entry["chain_hash"]
    logger.info("Appended %d entries to chain (total: %d)", len(new_entries), len(chain))
    return new_entries


def verify_chain(chain):
    """Verify the integrity of the entire hash chain."""
    if not chain:
        return {"valid": True, "entries_checked": 0, "breaks": []}
    breaks = []
    prev_hash = GENESIS_HASH
    for entry in chain:
        expected_input = f"{prev_hash}{entry['timestamp']}{entry['content_hash']}"
        expected_hash = compute_hash(expected_input)
        if entry["chain_hash"] != expected_hash:
            breaks.append({
                "index": entry["index"],
                "expected_hash": expected_hash,
                "actual_hash": entry["chain_hash"],
                "prev_hash_match": entry["prev_hash"] == prev_hash,
            })
        if entry["prev_hash"] != prev_hash:
            breaks.append({
                "index": entry["index"],
                "issue": "prev_hash mismatch",
                "expected_prev": prev_hash,
                "actual_prev": entry["prev_hash"],
            })
        prev_hash = entry["chain_hash"]
    valid = len(breaks) == 0
    logger.info("Chain verification: %d entries checked, %d breaks found", len(chain), len(breaks))
    return {"valid": valid, "entries_checked": len(chain), "breaks": breaks}


def find_tampered_range(breaks):
    """Identify the range of entries affected by tampering."""
    if not breaks:
        return None
    first_break = min(b["index"] for b in breaks)
    return {
        "first_tampered_entry": first_break,
        "total_affected": len(breaks),
        "tamper_start_index": first_break,
        "note": f"All entries from index {first_break} onward may be compromised",
    }


def create_checkpoint(chain, checkpoint_file):
    """Create an integrity checkpoint with the current chain head hash."""
    if not chain:
        return None
    checkpoint = {
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "chain_length": len(chain),
        "head_hash": chain[-1]["chain_hash"],
        "head_index": chain[-1]["index"],
        "genesis_hash": GENESIS_HASH,
        "checkpoint_hash": compute_hash(f"{len(chain)}{chain[-1]['chain_hash']}"),
    }
    with open(checkpoint_file, "w") as f:
        json.dump(checkpoint, f, indent=2)
    logger.info("Created checkpoint at index %d: %s", checkpoint["head_index"], checkpoint["checkpoint_hash"][:16])
    return checkpoint


def verify_checkpoint(chain, checkpoint_file):
    """Verify chain against a previously saved checkpoint."""
    with open(checkpoint_file, "r") as f:
        checkpoint = json.load(f)
    cp_index = checkpoint["head_index"]
    if cp_index >= len(chain):
        return {"valid": False, "error": "Chain shorter than checkpoint"}
    actual_hash = chain[cp_index]["chain_hash"]
    valid = actual_hash == checkpoint["head_hash"]
    return {
        "valid": valid,
        "checkpoint_index": cp_index,
        "expected_hash": checkpoint["head_hash"],
        "actual_hash": actual_hash,
    }


def generate_report(verification, checkpoint, chain_length):
    """Generate log integrity verification report."""
    report = {
        "timestamp": datetime.utcnow().isoformat(),
        "chain_length": chain_length,
        "integrity_valid": verification["valid"],
        "entries_checked": verification["entries_checked"],
        "breaks_found": len(verification["breaks"]),
        "break_details": verification["breaks"][:20],
        "checkpoint": checkpoint,
    }
    status = "INTACT" if verification["valid"] else "TAMPERED"
    print(f"LOG INTEGRITY: {status} - {chain_length} entries, {len(verification['breaks'])} breaks")
    return report


def main():
    parser = argparse.ArgumentParser(description="Log Integrity Chain Agent")
    parser.add_argument("--log-file", help="Log file to ingest")
    parser.add_argument("--chain-file", default="log_chain.json", help="Hash chain storage file")
    parser.add_argument("--verify", action="store_true", help="Verify chain integrity")
    parser.add_argument("--checkpoint", help="Create/verify checkpoint file")
    parser.add_argument("--output", default="integrity_report.json")
    args = parser.parse_args()

    chain = load_chain(args.chain_file)

    if args.log_file:
        entries = ingest_log_file(args.log_file)
        append_entries(chain, entries)
        save_chain(chain, args.chain_file)

    verification = {"valid": True, "entries_checked": 0, "breaks": []}
    if args.verify:
        verification = verify_chain(chain)

    checkpoint = None
    if args.checkpoint:
        if args.verify:
            checkpoint = verify_checkpoint(chain, args.checkpoint)
        else:
            checkpoint = create_checkpoint(chain, args.checkpoint)

    report = generate_report(verification, checkpoint, len(chain))
    with open(args.output, "w") as f:
        json.dump(report, f, indent=2)
    logger.info("Report saved to %s", args.output)


if __name__ == "__main__":
    main()
Keep exploring