08. Unforgeable fingerprints

November 7, 2017 ยท View on GitHub

Solution to problem 07
// bank.js

Phew, now we are just getting started trying to grok basic cryptography. In the previous exercise we made sure that our banking application can verify if there has been any corruption of it's transaction log using hashing.

However for our previous scheme to be secure it would require some operational measures that may not be desirable. If the latest hash is distributed securely to competing parties (customers, staff, directors) and they have the opportunity to verify it at their discretion, this scheme is actually pretty safe. This is the same idea behind distributing SHASUM file for binary releases, like how Node.js does it. You download the SHASUM file from an official source, here nodejs.org, and then you can download the binaries from any source and verify that the SHA sums match.

In practise, there are still some major flaws present, since it is too hard to enforce the above scheme for a bank where the hash changes all the time. A very practical attack vector is for an adversary to simply modify some values in the transaction log and then re-generate another valid hash chain. Then the bank will never know it has been attacked! Let's try to fix this.

Basically we need a way for the bank to verify that it was the one that wrote the hashes of the transaction log in the first place. That way an attacker couldn't simply rewrite the transaction log and generate another hash-chain. This property is called non-repudiation in crypto-lingo.

Digital signatures and Key-pairs

A cryptographic technique for achieving non-repudiation is digital signatures. A digital signature is a cryptographic primitive that has two operations, sign and verify, and works just like a real-world signature, but without any practical uncertainty.

To be able to create signatures you need what is called a key-pair. If you have an account on Github you probably already have a SSH key pair, which is the same concept but based on some very different mathematics. A key-pair consists of a public key and a secret key. These are also sometimes known as the identity and private key. The idea is that you distribute your public key to everyone, and they use this key to verify any signature that you've made. The secret key is never to be revealed as this is like a pen used to make signatures. If anyone steals your secret key, they can impersonate you and it's game over.

Signing with libsodium and sodium-native

sodium-native exposes digital signatures under the crypto_sign_* namespace. The most important ones are:

  • sodium.crypto_sign_keypair(publicKey, secretKey) Generates a key pair, where publicKey and secretKey are the output Buffers. They need to be exactly sodium.crypto_sign_PUBLICKEYBYTES and sodium.crypto_sign_SECRETKEYBYTES long.
  • sodium.crypto_sign_detached(signature, message, secretKey) The detached API writes the digital signature to signature Buffer, for message using secretKey. The signature Buffer needs to be crypto_sign_BYTES long. Like with hashing, this signature may contain non-printable bytes, so it is a good idea to convert to hex or base64 if you want to work with it outside Buffers.
  • var bool = sodium.crypto_sign_verify_detached(signature, message, publicKey) To verify a signature, you pass in signature Buffer, the original message Buffer and the corresponding publicKey. It will return bool whether the signature could be verified or not.

This primitive will allow us to upgrade our bank's transaction log with an unforgeable fingerprint (or signature) so we can verify that only the bank is updating it. This will prevent anyone without access to the private key from modifying the transaction log in any way.

Let's first do a basic exercise to get a bit more familiar with signing.

Problem

Use the crypto_sign APIs to make two new programs sign.js and verify.js. sign.js should generate a key-pair, sign a message from the command line and print out the message, public key and signature. verify.js should accept the public key, message and signature and verify that it is correct.

Testing

Try generating a few messages and keys and verify them. Then try tampering with the signature and messages and verify that the verify script rejects them.

For extra credit try sending your signed messages to other people in the workshop and have them verify it. You may also think about the security around how to establish trust in a public key, ie. how do you know the public key belongs to the person you think owns it.

Continue to problem 09