The CreatorCon Call for Content is officially open! Get started here.

owenhughes
ServiceNow Employee
ServiceNow Employee

Introduction

We are all aware of audit logs on systems and what they are used for. We all know various ways of protecting audit logs from tampering. One of the most popular ways of log protection is isolating them from the general user and administrator population using separation of duties or responsibilities. This works well but has a penalty of involving another person with Audit Admin privileges. Also, what if the Audit Admin colludes with a nefarious actor to alter logs after a damaging act has been committed.

I have thought about this for a while, and at the same time have been researching Blockchain. I have come to the conclusion the Blockchain could provide an answer to protecting audit logs without the need for an Audit Admin.

This blog is just a thought piece to get you thinking and hopefully provoke a discussion.

What is Blockchain?

Well lets first decide what it is not. Blockchain is not Bitcoin. Bitcoin is a system for exchanging value based on Blockchain technology (Bitcoin: A Peer-to-Peer Electronic Cash System - Satoshi Nakamoto).

So let’s see what Wikipedia says blockchain is.

A blockchain, originally block chain, is a continuously growing list of records, called blocks, which are linked and secured using cryptography. Each block typically contains a cryptographic hash of the previous block, a timestamp and transaction data. By design, a blockchain is inherently resistant to modification of the data. It is "an open, distributed ledger that can record transactions between two parties efficiently and in a verifiable and permanent way”.

So really, a blockchain is a way of storing data securely such that each new data element relies on its predecessor to validate itself.

 find_real_file.png

 

An Audit Blockchain

So now let us look at how we would implement an audit log in blockchain. Firstly, we will assume that we take the whole audit record as it stands and convert it to a JSON document of name-value pairs, if it is not already in this format.

 

{
  "id":           12345678,
  "actor_id":     3210,
  "source_id":    3456,
  "source_type":  "user",
  "source_label": "Jane Doe",
  "action":       "update",
  "changes_description": "Role changed from Administrator to Super User"
  "ip_address":   "NNN.NNN.NNN.NNN",
  "created_at":  "2018-02-05T11:32:44",
}

 

Why JSON? It is easier to work with, in my opinion, and I can treat it like a string payload for the block in the blockchain.

OK, so now what do we need for a block in a block chain. Below is a very rudimentary block in a blockchain, but more than enough for our purposes. Let’s look at each component in turn and see what it is for.

find_real_file.png

 

The Index denotes the position of the block in the blockchain. Every blockchain starts with a “Genesis Block” with an index of 0. This typically contains no important information and is typically hardcoded, whereas all other blocks are created at runtime.

The Timestamp records when the block was created. This can be important as blockchain can be used for storing time series data.

The Hash is the hash value of the entire block after it has been passed through a hashing algorithm, usually SHA256. NOTE: The Hash can only be populated after the entire block has been filled.

The Previous Hash is fairly self-explanatory but is where the strength of the blockchain lies. This field refers to the hash value stored in the previous block.

The Nonce in a block is a 32-bit (4-byte) field whose value is set so that the hash of the block will contain a run of leading zeros. This is used to denote the proof of work, more about this later.

The Data/Payload is, in our case, the JSON document audit record described above. In some implementations, this will be the root of a tree of transactions known as a Merkle Root. Google it!

The Strength of the Chain

The diagram below illustrates the how the blockchain derives its strength. If each block is dependent on the hash value in its previous block it becomes impossible to change a block without having to change every block after the changed block. This is compounded further due to the fact that blockchains are distributed with copies spread across network nodes. Not only would you have to change all blocks after the changed block, you would have to do this in at least 51% of all the copies of the blockchain before the system realises this has been done and evicts the rogue blockchains and blocks the offending user.

find_real_file.png

Implementation in JavaScript

OK, Now for some code. Our constructor looks like this

find_real_file.png

The “difficulty = 4” you see above is used by the proof of work to define the leading number of zeros (4 in this case) required when creating the hash for a block.

“What?” I hear you say. “Proof of Work! Please explain.”

 I mentioned the concept of a proof of work, let me explain a little. You want anyone putting something into the blockchain to have to expend energy to do this. Why? Well making it “expensive” to add a block to the blockchain acts as an additional protection mechanism. Remember I said that you would have to change every block after the changed block, and do this in 51% of the copies of the blockchain, well you have to work hard to add these blocks. This makes it not worth your while to try corrupting the blockchain.

 A proof of work is a piece of data which is difficult (costly, time-consuming) to produce but easy for others to verify and which satisfies certain requirements. Producing a proof of work can be a random process with low probability so that a lot of trial and error is required on average before a valid proof of work is generated. 

 This is where a lot of controversy arises with BitCoin. On average the PoW to add a block to the BitCoin blockchain takes around 10 minutes to process for each new BitCoin mined. The cost in electricity is ~$39, or enough to power a home for a week. Bitcoin's current estimated annual electricity consumption 28.48(TWh) 

 Some say this is a huge waste of resource.

 Let’s say we want to do a Proof of Work on the simple phrase “Hello World!”. Our target is to find a variation of the hash using SHA-256 such that the hash created begins with ‘0000’. We vary the string “Hello World!” by adding an integer value to the end. This integer value is called the Nonce (remember from above), and on each failed attempt to create the desired hash we increment the nonce and try hash the string again. In the example below finding a hash value for “Hello World!” took 4251 attempts.

find_real_file.png

So looking at the code to create the hash, we basically concatenate all the fields we want in the block, including or audit log (this.data) and the hash of the previous block and apply the hashing algorithm.

calculateHash() {
      return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString();
  }

  mineBlock(difficulty) {
    while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
        this.nonce++;
        this.hash = this.calculateHash();
    }

    console.log("BLOCK MINED: " + this.hash

 

The while loop above loops until you have the correct number of zeros (4) at the start of the hash string.

This is a very simple example but similar to how BitCoin works. BitCoin constantly reviews the difficulty and adjusts it every 12 days to ensure that is takes roughly 10 minutes to mine a BitCoin (solve the puzzle).

So what have we accomplished so far?

  • We have defined our audit log record as a JSON document.
  • We have discovered what the blockchain should look like.
  • We have decided what our block in the blockchain should look like.
  • We have written simple JavaScript to create a block.
  • We have also written the code to encrypt the block and ensure it has the necessary proof of work.

So really there is just creating the chain and adding blocks. The code below covers this and is included for completeness

Warning! I am a novice programmer so take this code with a bit of caution but enjoy none the less.

const SHA256 = require("crypto-js/sha256");

class Block {
  constructor(index, timestamp, data, previousHash = '') {
    this.index = index;
    this.previousHash = previousHash;
    this.timestamp = timestamp;
    this.data = data;
    this.hash = this.calculateHash();
    this.nonce = 0;
    this.type = "BLOCK";  
  }

  calculateHash() {
      return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString();
  }

  mineBlock(difficulty) {
    while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
        this.nonce++;
        this.hash = this.calculateHash();
    }

    console.log("BLOCK MINED: " + this.hash);
  }
} //end class Block /////////////

class Blockchain{
    constructor() {
        this.chain = [this.createGenesisBlock()];
        this.difficulty = 5;
    }

    createGenesisBlock() {
        return new Block(0, Date.now(), "Genesis block", "0");
    }

    getLatestBlock() {
        return this.chain[this.chain.length - 1];
    }

    addBlock(newBlock) {
        newBlock.previousHash = this.getLatestBlock().hash;
        newBlock.mineBlock(this.difficulty);
        this.chain.push(newBlock);
        
}

    calculateMerkl() {
        var hashString ="";
        for (let i = 1; i < this.chain.length; i++){
            const currentBlock = this.chain[i];
            hashString = hashString + currentBlock.hash;
        }
        console.log(JSON.stringify(hashString));

        return SHA256(hashString).toString();
    }
    
    isChainValid() {
        for (let i = 1; i < this.chain.length; i++){
            const currentBlock = this.chain[i];
            const previousBlock = this.chain[i - 1];

            if (currentBlock.hash !== currentBlock.calculateHash()) {
                return false;
            }

            if (currentBlock.previousHash !== previousBlock.hash) {
                return false;
            }
        }

        return true;
    }
} //End class Blockchain  /////////////

2 Comments