Turms AMT (Anonymous Message Transport) is a peer-to-peer messaging system for sending encrypted messages between Ethereum addresses (See Introducing Turms Anonymous Message Transport).
Encrypted messaging on a public, un-encrypted blockchain is a challenge. So in this article I’m going to explain how we did it in Turms AMT.
We had the following encryption goals in Turms AMT:
- Both the sender and receiver should be able to decrypt messages with the same key — a key that only they can derive.
- The key derivation must not require a “handshake”; that is, it needs to work even when one side is offline.
- The encryption technology should encompass “perfect forward secrecy”; that is, the key to decrypt one message should be unable to decrypt any future messages.
- encrypted messages should be un-hackable with modern, state of the art hardware.
Perfect Forward Secrecy
Perfect Forward Secrecy is the idea that a decryption key changes with every message — so that even if a decryption key for one message is compromised, it only facilitates decrypting a single message (and not an entire thread of messages). This is particularly useful for Turms AMT: We imagined that people would use Turms AMT to store important messages on the blockchain, like enforceable contracts or financial obligations; And, at some point people might want to reveal the contents of one of those messages to a third party, for example to prove what was sent. Perfect Forward Secrecy would make it possible to share the encryption key of one message while still keeping all other messages secure — even messages from the same Turms account.
To achieve our encryption goals we settled on using a sort of Diffie-Hellman-Merkle key exchange between the sender and receiver to generate a PMK (Pairwise-Master-Key); and then hash a nonce (a value updated with each message) together with the PMK to generate a PTK (Pairwise Temporal Key), which is used to AES encrypt each message.
That’s a mouthful!
I promise I’ll break it all down more simply in the coming paragraphs.
Basic Encryption Concepts
In an encrypted communication a plain-text message is encoded, using an encryption-key, into a cipher-text message that can only be decoded using a decryption-key. There are many different encoding/decoding algorithms, called ciphers. But broadly speaking ciphers can be classified as either symmetrical, meaning that the encryption-key is the same as the decryption-key, or asymmetrical, meaning that the two keys are different. In the case of asymmetrical encryption each side of the communication actually has a key-pair; that is, two keys that work together, one public and one private. A message that is encrypted with the public-key can only be decrypted with the private-key, and vice-versa. Each side of the encrypted channel shares its public key, which is used by the other side to encrypt messages before sending them. Upon receiving an encrypted message each side uses its private key, which is never shared, to decrypt the message.
The Key Distribution Problem
It’s not enough for an encryption cipher to make a conversation opaque to nefarious eavesdroppers who do not posses the appropriate decryption keys. There’s an additional problem, known as the “Key Distribution Problem”, which is how to get the decryption key to the intended recipient of the message in such a way that it can’t be intercepted by a third party. The Key Distribution Problem is not such an issue when using asymmetrical encryption — each side only gives out its public encyrption key. So assuming that you know that the public key really belongs to the other endpoint, you can encrypt messages, secure in the knowledge that only the holder of the corresponding private key is able to decrypt the cipher text.
But when using symmetrical encryption, the key that encrypts a message is the same key that is used to decrypt it. In that case there’s a very real problem of getting the endpoints to agree on what key to use, in a way that prevents a third party, who could be monitoring the key negotiations, from learning the key. This problem is analogous to me wanting to send you a treasure, but not trusting the mailman. I can send the treasure locked in a box; but how can I send you the key to the lock in such a way that the mailman can’t make a copy of it?
Diffie-Hellman-Merkle key exchange
Diffie-Hellman-Merkle key exchange is a clever system of negotiating private keys, such that even if a nefarious third party is able to observe the entire negotiation (because it’s on the public blockchain), they still can’t acquire the negotiated symmetrical keys! The Diffie-Hellman protocol relies on a mathematical one-way function:
f(g,s,p) = g^s % p
In the formula above the hat is the exponentiation operator, and the percent is the modulo operator; that is, the formula means “raise
g to the power
s, and then find the remainder when you divide by
In this context we call the function one-way because, even if you know
p, and also know the result,
(g^s % p), it is still not possible to derive
s. The reason is that exponentiation and division operations are very fast in modern computers, even for crazy-large numbers. But knowing the remainder of a division doesn’t tell you what the dividend was, even if you know the divisor; and checking every possible divisor
(g^s) can be prohibitively time-consuming. For a typical example,
g will be set to 2,
s will be a secret number and
p will be a known prime number. Both
p are 2,048 bits, which is pretty huge. But with
s being a 2048 bit number,
g^s is super huge (for comparison, consider that there are only about 2²⁵⁶ atoms in the universe).
The Key Derivation
In a Diffie-Hellman-Merkle key exchange, s is a secret that is never revealed. But it is not the encryption key. Each side (side a and side b) has their own secret,
s — we’ll call them
sb. And each side computes their own public key,
fb as shown below, using their secret, and predefined values of
fa = (g^sa) % p
fb = (g^sb) % p
Each side announces their public keys (
fb), in the open! To compute the private key, which will be known only to the two sides, each side uses the same one-way function, but this time replaces
g with the public key that was announced by the other side:
key(a) = (fb^sa)%p = ([(g^sb)%p] ^ sa) % p
key(b) = (fa^sb)%p = ([(g^sa)%p] ^ sb) % p
Because of a wonderful theorem of modular mathematics, it turns out that
key(a) will be equal to
key(b). That is to say, swapping the order of the exponentiation operations doesn’t change the final result.
Diffie-Hellman-Merkle key exchange In Turms AMT
We start by generating a super private key-encryption-key. This is a 256 bit symmetrical key that will never be saved on your computer, and never be transmitted anywhere, to anyone. This is the key that is derived from the Keccak signature that you generate whenever you start Turms AMT.
Note that no one else has the ability to generate your super private key-encryption-key. To do so they would need to be able to forge your Keccak signature — and if they could do that, then the security of the entire Ethereum blockchain could be compromised — so the point is: they can’t.
But 256 bits is not enough to create a future-proof, secure Diffie-Hellman secret. So when you register your Ethereum address with Turms Anonymous Message Transport, we generate a random 2048 bit Diffie-Hellman secret,
s. We use the super secret key-encryption-key to encrypt that secret, and store the encrypted secret on the blockchain. Also when you register your Turms AMT account, we generate a Diffie Hellman public key,
(g^s) % p, and store that on the blockchain (unencrypted), in the Turms AMT smart contract.
Whenever you start Turms AMT we read your encrypted secret from the the blockchain, and decrypt it using your super secret key-encryption-key. That’s how you get
When you want to encrypt or decrypt a message to/from another address you first compute a PMK (Pairwise Master Key), which is unique to the combination of your address and the other address. You compute this using the Diffie-Hellman technique: you have your own Diffie-Hellman secret,
s, and the other side’s Diffie-Hellman public key, which was stored in the Turms AMT smart contract. So now both you and the other side compute the same key, which is the PMK; But neither of you will ever use the PMK to actually encyrpt or decrypt anything — and you certainly never send it anywhere or save it.
Instead, you use the PMK to compute a PTK (Pairwise Transient Key), which is only used to encrypt/decrypt a single message. You compute the PTK by creating a hash of the PMK and your Ethereum address, and the other side’s Ethereum address, and the number of messages that have been sent from the sending side. For every message received you both compute the same PTK, which can be used to both encrypt and decrypt one message. This is how we achieve “Perfect Forward Secrecy” — even if the PTK used to encrypt one message was somehow hacked, or intentionally revealed — it would still be impossible to decrypt any of the other messages that were sent between the same two parties.
In strict Perfect Forward Secrecy it’s necessary for the sender and receiver to use new values of
g for every “session”. But this is not practical in Turms AMT, because of the fact that there is no handshake between the sender and receiver. This added security of strict Perfect Forward Secrecy only makes sense if there are multiple “sessions”, since it protects one session even when the PMK of a different session is exposed. The Perfect Forward Secrecy that Turms AMT implements only protects a message when the PTK of a different message is exposed. This is in line with the intended goal of being able to expose the PTK of an individual message without compromising any others. This limitation underscores the importance of keeping your Ethereum private key secure.
Turms AMT lets you send encrypted messages between Ethereum address. It’s released now on ipfs:
Go ahead and send me a message — My Ethereum address is imontoya.eth and I’d love to hear from you!