How to implement hybrid encryption with AES and asymmetric keys
Introduction
A few years ago, while working on a project that required a high level of security for data transmission, I found myself needing to implement a hybrid encryption system. This approach combines the speed of symmetric encryption with the security of asymmetric encryption, and in this post I'll share how I achieved it using AES for symmetric encryption and asymmetric keys using RSA for secure information exchange.
At that time, the approach was implemented using the Java & Spring Boot duo to achieve it, however, to share this experience I used Nest.js (An excellent framework!) as my backend, which was my preferred tool to perform the proof of concept for the challenge I had in mind.
Key Concepts
Before diving into the implementation, let's briefly review the key concepts:
-
Symmetric Encryption: Uses the same key to encrypt and decrypt data. It's fast and efficient, ideal for large volumes of data, but relies on the security of the shared key.
-
AES (Advanced Encryption Standard): is a widely used symmetric encryption algorithm due to its speed and security.
-
Asymmetric Encryption: Uses a pair of keys (public and private). The public key encrypts the data, while the private key decrypts it. It's more secure for key exchange, but slower.
-
RSA: is a commonly used asymmetric encryption algorithm for secure key exchange.
Now that we know the basic concepts, it's important to understand that in practice combining both methods is ideal to achieve the goal of security and efficiency. This is where hybrid encryption comes into play.
Hybrid Encryption Implementation
Prerequisites
- An RSA key pair (public and private) is generated from the server.
(Optional) If you want to generate an RSA key pair, you can use
opensslfrom the terminal. For Linux it's straightforward. However for Windows, you can install dependencies like Openssl for Windows.
After that, you can run the following commands:
To generate the private key:
To generate the public key from the private key:
Encryption Flow
The hybrid encryption flow consists of the following steps which will be commented in the following code:
If you're more visual, the following diagram illustrates the described hybrid encryption flow.
Now, as seen in the snippet, additional steps are included such as HMAC generation, which allows verifying data integrity, ensuring that data hasn't been altered during transmission, and data compression, which reduces the size of encrypted data, optimizing network performance respectively.
An important feature to highlight is that every buffer generated in the encryption process is converted to a base64 string to facilitate transmission supporting different mediums (HTTP, WebSockets, etc.).
Decryption Flow
The reverse decryption flow consists of the following steps which will be commented in the following code:
If you're more visual, the following diagram illustrates the described hybrid decryption flow.
Again, optional steps for integrity verification via HMAC and data decompression are included to optimize performance.
Although the implementation is oriented towards a backend environment with Nest.js, the principles and techniques described are applicable to any environment that supports the necessary cryptographic operations.
Additional Resources
Now, if you want to dive deeper into the implementation of each of the dependencies mentioned above (AES, RSA, HMAC, Compression), in the following repository, you can find a fully functional proof of concept of this hybrid encryption approach:
Mandatory
- AESEncryptor: Contains the implementation of AES encryption/decryption and methods to generate keys and IVs.
- RSAKeyManager: Contains the implementation of RSA encryption/decryption and methods to load public and private keys.
Optional
- HMACGenerator: Contains the implementation to generate and verify HMACs.
- CompressionService: Contains the implementation to compress and decompress data using different algorithms. It's built under the
Strategypattern, allowing selection of the compression algorithm at runtime, for our practical case gzip.
Summary
If you want to implement a hybrid encryption system:
- Generate a unique AES key for each encryption session.
- Generate a random initialization vector (IV).
- Encrypt the data using the AES key.
- Encrypt the AES key using the server's RSA public key.
- Gather both the encrypted data and the encrypted AES key for the server.
- Return the encrypted and compressed information to the client.
If you want to implement the reverse decryption flow:
- Parse the decompressed data to extract the encrypted payload, the encrypted AES key, and the IV.
- Decrypt the AES key using the server's RSA private key.
- Decrypt the data using the decrypted AES key and the IV.
Conclusion
That's how I implemented the hybrid encryption system in the project I was developing. If you have any questions or suggestions, don't hesitate to contact me through my social networks. Thanks for reading!
