07. Ensuring the integrity of the transaction log
November 7, 2017 ยท View on GitHub
Solution and commentary to problem 06
var sodium = require('sodium-native')
// Allocate Buffer for output hash
var output = Buffer.alloc(sodium.crypto_generichash_BYTES)
// Convert input to string and then Buffer
var input = Buffer.from("Hello, World!")
// Compute blake2b hash
sodium.crypto_generichash(output, input)
// Convert bytes to printable string
console.log(output.toString('hex'))
You might find this way of calling functions a bit foreign, since there are so
many manual steps, like allocating the output Buffer and converting a
Javascript object to a string, and then converting it to a Buffer. But all for
a reason! As mentioned in the introduction, this is because it gives you full
control of the memory used, so if you are handling sensitive data, you can
delete it as soon as you're done touching it (using .fill(0)) or you can
reuse Buffers in high performance scenarios. Also note that the data written
to the output Buffer may contain any byte, and not all bytes are ascii
friendly which is why you need to convert the Buffer to either hex or
base64 to print it.
Using a hash we can start making our transaction log much more secure to mitigate the risk outlined in our threat model, in our earlier exercise. Remember the goal is to make sure that it is hard for an adversary to tamper with the transaction log.
A first step in doing this is adding integrity checks to the log. We can use the
hashing primitive we learned about in the previous exercise to hash each entry
of the log to make it easier to detect when tampering happens. An easy way to
hash a log entry is to use JSON.stringify and adapt your solution from 06.
function appendToTransactionLog (entry) {
// entry is the messages received by the bank. We wrap it in an extra object
// as this makes verifying the hashes a lot easier
log.push({
value: entry,
hash: hashToHex(JSON.stringify(entry))
})
}
Hashing each item gives us the possibility of detecting any tampering with that particular entry. However as you may have guessed we only mitigate editing a particular record, while the most insidious attack is adding, deleting and reordering records. We could get around this by hashing the transaction log as a whole, every time we append to it, however this will become inefficient as the log grows and also seems plain wasteful. It also has some subtle security issues out of scope for now.
A solution to all the above is called a hash chain. A hash chain is a list where
each entry's hash value is the hash of the previous item's hash AND the
current value. This makes the entries "causally dependent" (not casually
dependent), and guarantees that you cannot reorder the log and it is easy to
verify.
// One edge-case with referring to the previous hash is that you need a
// "genesis" hash for the first entry in the log
var genesisHash = Buffer.alloc(32).toString('hex')
function appendToTransactionLog (entry) {
var prevHash = log.length ? log[log.length - 1].hash : genesisHash
log.push({
value: entry,
hash: hashToHex(prevHash + JSON.stringify(entry))
})
}
Problem
Convert the transaction log into a hash chain and verify the integrity when you
start the bank. A good way to approach is to feed all the entry.values to your
reduce function and checking that you get the same hash as the persisted JSON
file.
Testing
Test that the various teller.js commands still work as expected. Then try
modifying the transaction log by hand and restart bank.js. Try all the attack
scenarios (editing, adding, deleting and reordering). When restarted the bank
should detect that the transaction log has been tampered with.