Perfect Forward Secrecy: Meaning, Implementation & Tips
You probably heard of Heartbleed―a vulnerability exposing hundreds of thousands of web servers relying on the OpenSSL library to enable HTTPS, back in 2014. The bug was so serious it made the headlines of countless publications and is still considered one of the most impactful security flaws of the past decade. Heck, it’s even featured in college textbooks. But what if I told you there is a best practice known as Perfect Forward Secrecy that would have saved millions of users at the time while mitigating future risks?
At Onboardbase, our job is to create the safest online vault for developer teams to share app secrets: that’s why we designed a whole end-to-end encryption system including perfect forward secrecy from the get-go. Unfortunately, PFS is still rare, so we decided to write about it to help more engineers understand what it’s about, when to use it, and how to integrate it into their own systems. Let’s go!
What Is Perfect Forward Secrecy
Perfect Forward Secrecy (PFS) is a security practice consisting in regularly changing the keys used to encrypt and decrypt information in an automated way using the software.
For example, if you use an asymmetric encryption method like RSA, PFS will make sure the private keys used by the receivers will change on each transaction without manual intervention. Same with the OpenSSL library implements the TLS protocol used to encrypt HTTP requests.
Why Perfect Forward Secrecy
Encryption algorithms rely on private keys (passphrases, passwords) to secure data and transactions. Just like passwords, these keys need to be stored in a secure way too: if the keys are leaked, then the whole encryption system can be breached. With perfect forward secrecy, a security breach will only result in a minimal amount of compromised information since the keys change on a regular basis.
With PFS, you also maximize security because breaking the key through brute force will be near impossible and limited to a single transaction. Brute force attacks take considerable resources to pull off, so PFS makes them worthless when you consider the possible return on investment for hackers.
Even if PFS appears overkill, the Heartbleed example showed how important it is to have an extra layer of security to protect your company against such risks―don’t overlook them! You never know what kind of bugs can be introduced by new software releases so it’s always a good idea to walk the extra mile and reduce the potential attack surface. By definition, PFS is meant to be used for transmitting data on insecure networks. And the reality is you can never fully trust the network you are in―be it a library Wi-fi spot or your own internet provider’s.
Perfect Forward Secrecy With Elliptic Curve Cryptography
PFS can be broken down into 5 steps:
- Key generation - generating strong keys to use for encryption algorithms
- Key storage - storing the keys in a secure vault
- Encryption / decryption - actually performing transactions
- Key exchange - exchanging the keys over a network
- Key change - modifying the keys to increase security
In this article, we are going to use an asymmetric encryption system called Elliptic Curve Cryptography (aka ECC) to implement Perfect Forward Secrecy. Let’s dive into each step in detail.
1. Generating asymmetric keys
First, we generate our public and private keys. An asymmetric encryption system uses a public key for encryption and a secret key for decryption. Anyone can send encrypted messages but only the receiver knows how to decrypt them. The separation makes it more secure than symmetric algorithms like AES in some cases because you don’t run into the problem of having to secure the private key used to encrypt data after sharing it.
Encryption methods require a key to encrypt/decrypt data. Anybody with the private key can decrypt your data, so you need the key to be strong and hidden from everyone else but you, like a normal password―only the software program should be able to access it.
In Python, you’d generate a randomized series of characters using an approved cryptography library. Depending on the encryption algorithm, you’ll need different key lengths. We use the tinyec library to generate a key pair for ECC:
from tinyec import registry
import secrets
curve = registry.get_curve('brainpoolP256r1')
private_key = secrets.randbelow(curve.field.n)
public_key = private_key * curve.g
print(private_key)
print(public_key)
The function of the elliptic curve computes the keys. In this code, we use the predefined elliptic curve nicknamed “brainpoolP256r1”, but in your production environment, you’ll need to come up with your own curve to maximize security.
2. Storing the private and public keys
The simplest way to store a key is to use an environment variable in your program, using a .env file for example:
PK_ECC = A_PUBLIC_KEY
SK_ECC = A_PRIVATE_KEY
The .env variables can be referenced in your Python program like so:
import os
private_key = os.getenv("SK_ECC")
public_key = os.getenv("PK_ECC")
print(private_key)
print(public_key)
But the keys are the weakest link in the security chain: you need to protect them at all costs! While having your key in a .env file is a simple way to go about storing it, with a secret manager like Onboardbase you can inject passphrases in software programs as environment variables in your CI/CD process in just 3 commands.
After defining your key in your account dashboard, you can use Onboardbase CLI’s setup command to get started in a few seconds:
onboardbase setup
The command generates an onboardbase.yml file used to access the online vault:
setup:
project: projectname
environment: development
All you have left to do is to run the build command to inject keys from Onboardbase at runtime, removing the need to directly share keys or hardcode them in files:
onboardbase build --command="python3 password_generator.py"
To store several key pairs, one for each of your users, for example, you should use one master key to encrypt them using a symmetric algorithm like AES-256 and save the encrypted result to a database. The master key is saved in a secure vault-like Onboardbase.
3. Exchanging ECC Keys
Now the idea is to create a temporary key to encrypt/decrypt each new transaction―sending a text message, calling an API, etc. Even if this session key is leaked, the attacker won’t be able to reuse it for future transactions.
The sender and the receiver exchange their public keys, and thanks to a key exchange algorithm the two will be able to recognize each other and derive a session key from the public key. This way, the session key will never be transmitted in plain view―hence the term Perfect Forward Secrecy.
Modern key exchange algorithms derive from the Diffie-Hellman algorithm, which is both secure and computationally cheap. In this article we use the Elliptic Curve Diffie-Hellman algorithm (ECDH):
pk1, sk1 = generateRandomKeyPair()
pk2, sk2 = generateRandomKeyPair()
def compress(key):
return hex(key.x) + hex(key.y % 2)[2:]
receiver_shared_key = sk1 * pk2
print("Receiver shared key:", compress(receiver_shared_key1))
sender_shared_key = sk2 * pk1
print("Sender shared key:", compress(sender_shared_key))
Without going into the mathematical details, the two public keys remain different but the calculated session keys will be the same.
The shared key is a point of the elliptic curve―coordinates represented as a pair of integers―which can be compressed to a single 257-bit integer for transmission. In this example, we choose a 256-bit curve resulting in 257-bit keys, but we could have picked other types of curves with different key lengths.
4. Encryption & Decryption Using ECC Keys
The session key can now be used as a passphrase for asymmetric encryption algorithms like AES. Since the session keys are the same on each side, they can be used to both encrypt and decrypt messages. We use the pycryptodome library to encrypt/decrypt messages using the AES-256-GCM algorithm:
from Crypto.Cipher import AES
import hashlib
header = b"header"
data = b'secret data'
def ecc_point_to_256_bit_key(point):
sha = hashlib.sha256(int.to_bytes(point.x, 32, 'big'))
sha.update(int.to_bytes(point.y, 32, 'big'))
return sha.digest()
key = ecc_point_to_256_bit_key(shared_key)
#Encryption
cipher = AES.new(key, AES.MODE_GCM)
cipher.update(header)
ciphertext, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
#Decryption
decrypt_cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
decrypt_cipher.update(header)
plain_text = decrypt_cipher.decrypt_and_verify(cipher_text, tag)
At first, the generated session key corresponding to a point of the elliptic curve is encoded over 257 bits. AES-256 uses 256-bit keys, however, so you must define a standard hashing function called SHA-256 to convert the session key to 256 bits.
We then proceed with AES encryption & decryption as it’s normally done with a single private key. AES-GCM (short for AES Galois Counter Mode) is a modern flavor of AES guaranteeing data integrity, making sure the encrypted data has not been tampered with.
5. Changing the keys
The result of the key exchange has to be ephemeral: you cannot store the session key anywhere and it has to be re-computed on each transaction. This way, even if hackers somehow get hold of a session key by obtaining the private/public key pair of a user, they will be able to impersonate this specific user but no one else―drastically minimizing the impact of a data breach.
Of course, the more diversity you have regarding your key pairs, the more ephemeral your session keys will be. The only downside is the computation power it would take to rotate your key pairs, but key rotation can be a requirement for IT systems like instant messaging apps for example.
Perfect Forward Secrecy With Onboardbase
Now that you know how to use perfect forward secrecy to secure your applications, don’t wait any longer to get started and subscribe to Onboardbase. It’s free!
Onboardbase makes it easy to store encryption keys while programmatically changing and using them in your software projects: it’s your best ally to implement perfect forward secrecy. A dashboard centralizes temporary, public, and private keys for the whole team, and a command-line interface is available to integrate them into any workflow. Each one of your keys is encrypted with AES-CBC-256 and the web app is served over TLS with PFS. It’s so easy and secure to use, that you won’t pick another tool for the job: register today and never leak your app secrets ever again.
Subscribe to our newsletter
The latest news, articles, features and resources of Onboardbase, sent to your inbox weekly