Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
101 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,66 @@ | ||
# 09. Signing the transaction log | ||
|
||
Now that we have a basic understanding on how digital signatures work we can start securing the transaction log with this new primitive. | ||
<details> | ||
<summary>Solution to problem 08</summary> | ||
|
||
Remember, since we already have a hash-chain of all transactions the only thing missing is making sure we, the bank, are the only ones that are generating the hash-chain. | ||
```js | ||
// sign.js | ||
var sodium = require('sodium-native') | ||
|
||
var publicKey = Buffer.alloc(sodium.crypto_sign_PUBLICKEYBYTES) | ||
var secretKey = Buffer.alloc(sodium.crypto_sign_SECRETKEYBYTES) | ||
sodium.crypto_sign_keypair(publicKey, secretKey) | ||
|
||
var message = Buffer.from('Hello world!') | ||
var signature = Buffer.alloc(sodium.crypto_sign_BYTES) | ||
|
||
sodium.crypto_sign_detached(signature, message, privateKey) | ||
|
||
console.log('Public key:': publicKey.toString('hex')) | ||
console.log('Message:': message.toString()) | ||
console.log('Signature:': signature.toString('hex')) | ||
``` | ||
|
||
```js | ||
// verify.js | ||
|
||
``` | ||
|
||
</details> | ||
|
||
Now that we have a basic understanding on how digital signatures work we can | ||
start securing the transaction log with this new primitive. | ||
|
||
Remember, since we already have a hash chain of all transactions the only thing | ||
missing is making sure we, the bank, are the only ones that are generating the | ||
hash chain. | ||
|
||
## Problem | ||
|
||
Using the signature APIs you learned about in the previous excercise, extend the bank to: | ||
Using the signature APIs you learned about in the previous exercise, extend the | ||
bank to: | ||
|
||
1. Check if a previous keypair is stored on disk, if so load it. | ||
1. If not, generate a new keypair and store it. | ||
1. When you generate a new hash for a transaction, sign it using the secret key and store the signature as a new property `signature`, next to the `hash` and `value` property. | ||
1. When loading the transaction log remember to verify the last signature to see if the log has been tampered with. | ||
1. Check if a existing key-pair is stored on disk, if so load it. | ||
2. If not, generate a new key-pair and store it. | ||
3. When you generate a new hash for a transaction, sign it using the secret key | ||
and store the signature as a new property `signature`, next to the `hash` and | ||
`value` property. | ||
4. When loading the transaction log extend verification to validate the | ||
signatures in addition to the hashes. | ||
|
||
Note that in a real life application, we ofcourse wouldn't store the keypair on disk but instead on an external device like a usb stick or a hardware backed secure vault. | ||
You might be thinking that storing the key-pair right next to the transaction | ||
log is not very safe, but this is much more of an operational problem (which | ||
also has technical mitigations), but for the purpose of our workshop it will | ||
suffice. In a real bank you might use a Hardware Secure Module (HSM), which is a | ||
logical separate computing unit in the server, from which the secret key never | ||
leaves. As of current, [AWS has a CloudHSM Classic](https://aws.amazon.com/cloudhsm/pricing-classic/) | ||
product which gives you a dedicated machine with it's own HSM, however the | ||
upfront cost is $5,000 USD and around $1,500 USD monthly maintenance cost. | ||
|
||
## Testing | ||
|
||
Make sure your bank works the same way as before. Then stop the bank and try tampering with the log. The bank should reject the bad transaction log, even is the hashes are correct. | ||
Make sure your bank works the same way as before. Then stop the bank and try | ||
tampering with the log. The bank should reject the bad transaction log, even if | ||
the hashes are correct. | ||
|
||
[Continue to problem 10](10.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,59 @@ | ||
# 10. Updating the threat model | ||
|
||
By now our bank is actually pretty secure when looking at our original threat model. | ||
The transaction log is signed and contains integrity checks, meaning you have to access to a well guarded secret key in order to modify the banks state. | ||
|
||
Let's update our threat model with another requirement. | ||
|
||
In this age of data leaks, we want to make sure that if an agent from a big government spy agency breaks into our bank server and steals the transaction log, as little data is leaked as possible about how much money the bank is storing etc. | ||
|
||
To support this, let's introduce a new cryptographic primitive, symmetric encryption. | ||
|
||
Symmetric encryption is a scheme where if you have another secret key (unrelated to the signing secret key from before) and a random nonce (a short single used key), you can encrypt a message securely. | ||
|
||
You don't even need to keep the nonce secret, only the secret key. | ||
|
||
Using `sodium-native` this functionality is exposed through the `crypto_secretbox` APIs | ||
By now our bank has mitigated the original threat model to a degree where the | ||
threat has shifted from the transaction log itself, and unto the key-pair. This | ||
means that we have centralised security onto something that is easier to reason | ||
about and has a much smaller attack surface, albeit being even more sensitive | ||
to the operation of our bank. However, this problem is now more a question of | ||
operations and policy than cryptography. | ||
|
||
This also means that our priorities shift as we now have other threats that | ||
pose higher risk. In this age of data leaks, we want to make sure that if an | ||
adversary, eg. a three letter gov't agency, breaks into our bank server at night | ||
and steals the transaction log, they stand to learn very little about the banks | ||
business. | ||
|
||
To achieve this, we need to introduce a new cryptographic primitive, symmetric | ||
encryption. | ||
|
||
Symmetric crypto dates all the way back to at least Julius Caesar, who used it | ||
to communicate securely with his generals. Much has happened since then, but the | ||
basic idea is the same, you have a key that is used for both the `encrypt` and | ||
`decrypt` operations. In modern schemes you often also need a `nonce` which is | ||
often a random piece of data, that is not required to be secret, but protects | ||
against a several classes of attacks. | ||
|
||
Using `sodium-native` this functionality is exposed through the | ||
`crypto_secretbox` APIs: | ||
|
||
* `sodium.crypto_secretbox_easy(cipher, message, nonce, secretKey)` | ||
* `var valid = sodium.crypto_secretbox_open_easy(message, cipher, nonce, secretKey)` | ||
|
||
The `crypto_secretbox_easy` api will encrypt a message buffer into the cipher buffer using the nonce and secretKey. The `secretKey` should be a `sodium.crypto_secretbox_KEYBYTES` bytes long buffer containing very random data. You can generate a secure one using the `sodium.randombytes_buf` API. `cipher` is the buffer where the encrypted message is saved. This buffer should be `message.length + crypto_secretbox_MACBYTES` long, and finally `nonce` should be another random buffer of size `crypto_secretbox_NONCEBYTES`. It is important that you never re-use a nonce to encrypt more than a single message. | ||
Encrypt `message` `Buffer` into `cipher` `Buffer` with `nonce` and | ||
`secretKey`. The secret key must be `sodium.crypto_secretbox_KEYBYTES` and is | ||
best generated using the `sodium.randombytes_buf` API. This key must be | ||
persisted somehow. `nonce` should be another random buffer of size | ||
`crypto_secretbox_NONCEBYTES`. The `cipher` `Buffer` should be | ||
`message.length + crypto_secretbox_MACBYTES` long. It is important that you | ||
never re-use a nonce to encrypt more than a single message. | ||
* `var bool = sodium.crypto_secretbox_open_easy(message, cipher, nonce, secretKey)` | ||
Decrypt `cipher` `Buffer` into `message` `Buffer` using `nonce` and `secretKey`. | ||
Will return a `boolean` depending on whether the cipher text could be decrypted. | ||
|
||
## Problem | ||
|
||
Use the APIs described above to make three new programs, `secret-key.js` `encrypt.js` and `decrypt.js`. | ||
Use the APIs described above to make three new programs, `secret-key.js` | ||
`encrypt.js` and `decrypt.js`. | ||
|
||
* `secret-key.js` should generate a secret key using the randombytes_buf api of the correct length | ||
* `encrypt.js` should accept a secret key and a message and print out the encrypted message and the random nonce used to encrypt it. | ||
* `decrypt.js` should accept the encrypted message, secret key and nonce and print out the plaintext message if valid. | ||
* `secret-key.js` should generate a secret key using the `randombytes_buf` api of | ||
the correct length | ||
* `encrypt.js` should accept a secret key and a message and print out the | ||
encrypted message and the random nonce used to encrypt it. | ||
* `decrypt.js` should accept the encrypted message, secret key and nonce and | ||
print out the plaintext message if valid. | ||
|
||
## Testing | ||
|
||
Try running a couple of test messages like `Hello, World` through your encrypter and try decrypting them to see that it works. Then try tampering with some of the encrypted messages to see that decryption fails. | ||
Try running a couple of test messages like `Hello, World` through your encrypter | ||
and try decrypting them to see that it works. Then try tampering with some of | ||
the encrypted messages to see that decryption fails. | ||
|
||
[Continue to problem 11](11.md) | ||
|