End-to-End Encryption: Implementation, and Practical Code


In an age where digital communication is ubiquitous, ensuring the privacy and security of data has become paramount. One of the most effective methods to secure data is end-to-end encryption (E2EE). This technique ensures that only the communicating users can read the messages, preventing unauthorized access by intermediaries. This article will explore the concept of end-to-end encryption, its importance, and provide a practical guide with code examples to implement it using Python.

Learning Objectives

By the end of this article, you will:

  1. Understand what end-to-end encryption is.
  2. Recognize the importance of end-to-end encryption in securing digital communication.
  3. Learn how to implement end-to-end encryption using Python with a focus on AES encryption.
  4. Gain practical experience with encryption and decryption code examples.

What is End-to-End Encryption and Why is it Important?

End-to-end encryption (E2EE) is a method of data transmission where only the communicating parties can read the messages. In E2EE, the data is encrypted on the sender’s side and only decrypted on the recipient’s side. This means that during transmission, even if the data is intercepted, it remains unreadable to unauthorized parties(File Encryption and Decryption for Data Security ).

The importance of end-to-end encryption includes:

  • Privacy: Ensures that the content of the communication remains confidential.
  • Security: Protects data from being accessed or altered by third parties.
  • Integrity: Ensures that the data received is exactly what was sent, without any tampering.

Let’s Start Writing Our Code

To illustrate the implementation of end-to-end encryption, we will use Python along with the cryptography library. This library provides a rich set of cryptographic recipes and primitives. Below is a step-by-step guide and code examples for encrypting and decrypting messages(Python in Cybersecurity: Exploring Popular Modules).

Amazon Product
Programming Symbols Stickers

A Practical Introduction to Modern Encryption

This practical guide to modern encryption breaks down the fundamental mathematical concepts at the heart of cryptography without shying away from meaty discussions of how they work.

-10% $32.83 on Amazon

Step 1: Install the cryptography library
First, ensure that the cryptography library is installed. You can install it using pip:

pip install cryptography

Step 2: Import necessary modules

We need to import several modules from the cryptography library and some standard Python libraries for this implementation:

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import os
import base64
  • PBKDF2HMAC is used for deriving a secure key from a password.
  • hashes provides the hashing algorithms.
  • default_backend is required for cryptographic backend operations.
  • Cipher, algorithms, and modes are used for encryption and decryption operations.
  • os is used for generating secure random numbers (salt and IV).
  • base64 is used for encoding and decoding the encrypted messages.

Step 3: Define the key derivation function

We need a secure key derivation function to convert a password into a cryptographic key. We use PBKDF2HMAC for this purpose:

def derive_key(password: str, salt: bytes) -> bytes:
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),  # Use SHA-256 as the hashing algorithm
        length=32,                  # The length of the derived key (256 bits)
        salt=salt,                  # The salt for the key derivation
        iterations=100000,          # Number of iterations to make the process slower and more secure
        backend=default_backend()   # The backend for cryptographic operations
    key = kdf.derive(password.encode())  # Derive the key from the password
    return key

Step 4: Encryption function

The encryption function will use AES (Advanced Encryption Standard) in CFB (Cipher Feedback) mode. We generate a random salt and IV (Initialization Vector) for each encryption operation:

def encrypt(message: str, password: str) -> str:
    salt = os.urandom(16)  # Generate a random 16-byte salt
    key = derive_key(password, salt)
    iv = os.urandom(16)  # Generate a random 16-byte IV
    cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend())
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(message.encode()) + encryptor.finalize()
    # Encode the salt, IV, and ciphertext in base64 for easy storage and transmission
    return base64.b64encode(salt + iv + ciphertext).decode('utf-8')

Step 5: Decryption function

Amazon Product
Programming Symbols Stickers

Practical Binary Analysis

Stop manually analyzing binary! Practical Binary Analysis is the first book of its kind to present advanced binary analysis topics, such as binary instrumentation, dynamic taint analysis, and symbolic execution, in an accessible way.

-33% $39.99 on Amazon

The decryption function will reverse the process, extracting the salt and IV from the encoded message and using them to decrypt the ciphertext(Decrypting Encrypted Network Traffic with Python and Scapy):

def decrypt(ciphertext: str, password: str) -> str:
    data = base64.b64decode(ciphertext.encode('utf-8'))
    salt = data[:16]  # Extract the salt (first 16 bytes)
    iv = data[16:32]  # Extract the IV (next 16 bytes)
    actual_ciphertext = data[32:]  # The remaining bytes are the actual ciphertext
    key = derive_key(password, salt)
    cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    decrypted_message = decryptor.update(actual_ciphertext) + decryptor.finalize()
    return decrypted_message.decode('utf-8')

Step 6: Usage example

Finally, we can demonstrate the encryption and decryption process with a usage example:

if __name__ == "__main__":
    original_message = "This is a secret message."
    password = "StrongPassword123"

    encrypted_message = encrypt(original_message, password)
    print(f"Encrypted Message: {encrypted_message}")

    decrypted_message = decrypt(encrypted_message, password)
    print(f"Decrypted Message: {decrypted_message}")

In this example:

  • original_message is the plaintext message we want to encrypt.
  • password is the secret password used for key derivation.
  • encrypt function encrypts the original message.
  • decrypt function decrypts the encrypted message back to its original form.


End-to-end encryption is a crucial technology for securing digital communications. By encrypting data on the sender’s side and decrypting it only on the recipient’s side, E2EE ensures that data remains confidential and secure from unauthorized access. This article has provided an overview of end-to-end encryption, its importance, and a practical guide to implementing it in Python. By following the steps and code examples provided, you can implement robust encryption mechanisms to protect sensitive data in your applications.

2 thoughts on “End-to-End Encryption: Implementation, and Practical Code”

  1. Point of feedback / question: why opt for a derived key from a hard-coded passphrase? I have seen too many implementations where this went badly and encrypted message could be cracked by reverse engineering the code.

    I will always recommend people to use externally generated keys (and certificates), which ideally should be stored in a safe/secure manner.

    • To answer your question in a nutshell: In the article, the use of a hardcoded password was for demonstration purposes to simply demonstrate the basic principles of end-to-end encryption.
      If you want, we can give an example like this in our next article:

      def generate_key() -> bytes:
      return os.urandom(32) # Generate a random 32-byte (256-bit) key


Leave a Comment

Join our Mailing list!

Get all latest news, exclusive deals and academy updates.