Hi% username%!
What could be easier than sending an encrypted message to the other party?
We take its public key, its private key, we consider Diffie-Hellman, after the KDF we drive the result into some AES, and it’s ready. But, if one of the private keys is stolen, everything, all the traffic intercepted before, will be decrypted without any problems.
')
We will tell you how modern instant messengers and transport protocols are struggling with this, as well as give you a tool for implementing PFS in your products.
To understand PFS, the key point is an ephemeral key pair, then for simplicity, the public key of the ephemeral pair I will simply call the
ephemeral key . The ephemeral key pair is generated immediately before the session is established, lives only until the end of the connection setup process (
handshake ) and is destroyed immediately after use. This achieves the
uniqueness of the session symmetric keys used after the handshake. About how asymmetric keys turn into symmetric, a little later.
Immediately there are several questions about the use of ephemeral keys:
- How to trust the ephemeral key?
- How to transfer your interlocutor ephemeral key?
Consider them in more detail.
Confidence in the ephemeral key
Aby what key can not be taken, because it is not tied to the interlocutor at the end of the wire. We have to authenticate the key and can do it in several ways:
- Sign the ephemeral key. So does TLS.
- Use multiple Diffie Hellman operations between different keys. So does the noise protocol.
- Combined approach (Signal, Whatsapp).
In the first case, everything is clear, before connecting, we generate an ephemeral key pair and sign it with a private key, for example, a certificate. The client makes sure that the key is “from where it is necessary” and uses it.
In some cases, you can do without a digital signature. Depending on the specific pattern, the
Noise Protocol “mixes” the DH results between the ephemeral and static keys to a common state. In this way, connectivity between the main static key and the ephemeral key is achieved.
Signal developers have gone a little further. They decided to almost give up digital signatures, replacing them with several DH, which also occur between different static and ephemeral keys. Only one of the long-lived ephemeral keys is signed, therefore the scheme is called combined. It looks like this:
called
X3DH .
It uses several types of keys:
- IK - Identity keys. These are permanent static keys that are generated once.
- EK is an ephemeral key that is generated by the sender before the session begins.
- SPk is an ephemeral key signed by Identity with an average lifetime of about a week.
- OPK is a one-time ephemeral key.
We also see 4 DH operations, one of which, with a one-time key, is optional.
DH are held between keys of different types, both ephemeral and static. The combination of different DH allows you to achieve both mutual authentication and protection against the consequences of the theft of the main static (Identity) private keys.
Next, let's talk why we need as many as 2 types of ephemeral keys.
Delivery of ephemeral keys
The easiest way is to connect online, when we can transfer the ephemeral key directly during the handshake. TLS, NoiseSocket do just that. But what if our interlocutor is offline, and you still need to send a message?
You can try using the ECIES schema,
But then PFS is provided only by the sender, since it generates an ephemeral pair and sends an ephemeral key with each message. Theft of the recipient's primary private key makes all protection useless.
We need a reliable delivery mechanism to senders of ephemeral keys of recipients regardless of their location in the network!
You can ask the client to generate, say, a hundred one-time ephemeral keys and put them on the server. When the interlocutor wants to start a new session, he will ask the server for the client key, he will give him one and immediately delete it.
The sender takes this one-time key, generates an ephemeral pair himself, and makes several DHs as indicated in the diagram above.
But what if the one-time ephemeral keys run out and the client does not have time to reload new ones? Here in this extreme case, and there is a
second reusable ephemeral key . It also provides PFS, but only within not one session, but several.
Signal and WhatsApp used to do this.
And now anyone can do .
Virgil PFS
We liked the idea of ​​preloading one-time keys to the server and it was decided to make for everyone a universal PFS, similar to the one in Signal. Since we already have Virgil Cards, it was not difficult to do that. What are the parts of our PFS?
Virgil Cards Service
This is our usual card service. It stores long-term Identity cards.
PFS Service
Responsible for storing and issuing one-time and reusable ephemeral cards signed with private keys of Identity cards.
PFS Protocol
The above described X3DH + generation of session symmetric keys + generation of keys for encrypting individual messages.
Sdk
IOS, Android and C # (+ Xamarin) are supported. The SDK is responsible for managing the lifecycle of ephemeral keys, sessions, encryption, and decryption of messages. All SDK compatible with each other.
A little more about the protocol, because besides the possibility of receiving one-time keys from the server, this is the main component of information security.
Virgil PFS Protocol
It starts with X3DH on the same principle as in Signal. After performing DH, we destroy the corresponding private ephemeral keys.
There are two differences.
- Not just bare keys are used, but Virgil Cards
- All ephemeral keys are signed by private Identity key
Relevant Virgil Cards:
- IC - Identity Card. Main user card or device.
- OTC - One Time Card. Disposable ephemeral card.
- LTC - Long Term Card, reusable ephemeral card.
- EK - Ephemeral key, which is transmitted in the first message.
Signing the user of all their keys is necessary in order not to have to trust the services of Virgil. After all, we are not engaged in the transmission of messages, only encryption.
Session setup
Alice wants to talk to Bob. Her actions:
- Get a Bob card from Virgil Cards Service Identity, verify the digital signature of the application. This is done once.
- Get from the PFS service bandl ephemeral maps. Either LTC + OTC, or only LTC if there is no OTC.
- Generate an ephemeral key pair.
- Calculate all DH according to the scheme above.
- Turn these DH into a session.
After that, Alice sends Bob the first message containing the following data:
- Alice's Identity Card ID
- Bob's Identity Card ID (Bob may have several Identity Cards)
- Bob's Long Term Card ID
- Bob’s One Time Card ID (if available)
- Ephemeral key
- Signature Ephemeral Key
Bob, having received this message, gets the corresponding private OTC, LTC, Identity keys and repeats DH according to the scheme above. Immediately after this, the private OTC key is destroyed.
Further, we can not directly use the results of DH, it is not safe. Therefore, we run them through Key Derivation, namely
HKDF . This is a special feature that turns some “weak key information”, in this case, DH results, into “strong key information” by using HMAC-Hash.
From the first HKDF, we get 4 values, each 32 bytes:
- The secret of the sender is SKa
- SKb Recipient Secret
- Session id key
- Additional data key
Then we get the Session ID and Additional Data, running the Session ID key and the Additional data key each again via HKDF. This is necessary to remove the dependence of key data on additional service data.
Thus, any session consists of four values:
2 different secrets for sending and receiving messages, a unique session ID and an additional 32 bytes that will participate as Additional data in the
AEAD encryption scheme with direct encryption of messages.
As I said before, Virgil does not handle messaging. Therefore, we must add the Session ID to each of them so that our SDK will understand what message to decrypt with the key.
I called the first two values ​​in the session secrets for a reason. They are not used directly when encrypting messages, but they are also run through HKDF every time. The message encryption algorithm is as follows:
- We generate 16 random bytes of salt.
- We get an individual key and nonce to encrypt the message, banishing the corresponding Secret with salt through HKDF.
- Encrypt the message with the received key, nonce. As Additional data we transfer value from session.
- We send Session ID, salt, ciphertext.
Thus, each message is encrypted with its unique key.
Management of ephemeral keys and sessions
This turned out to be the most difficult moment, since it was necessary to find a universal option suitable for most potential developers. In the end, we seem to have succeeded.
There were two problems. The first is what to do with ephemeral cards that were taken but not used. We do not delete them immediately, but some time after we learned that they are no longer on the server. This gives a chance to those messages that have not yet arrived but will soon come.
The second is updating sessions. For greater security, there is a need to periodically create new sessions with the same interlocutor and drop old ones. If the session “leaks”, then the attacker will be able to decipher only a small amount of data. So that everything happens transparently, we give some time to old sessions before dropping them forever.
Code!
We have made great
learning materials to help you master PFS and embed it in your applications.
Hope you enjoyed it. Questions?