DEV Community

Cover image for 8 Python Cryptography Techniques Every Developer Needs for Secure Systems
Nithin Bharadwaj
Nithin Bharadwaj

Posted on

8 Python Cryptography Techniques Every Developer Needs for Secure Systems

As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

Building secure systems is one of the most important and fascinating parts of modern software development. Today, I want to walk you through eight essential techniques you can use in Python to protect data and communications. We'll start with the basics and work our way up to complex protocols. I'll explain each concept as if we're sitting together, and I'll share plenty of working code you can try yourself.

I remember when I first started, terms like "cryptographic hashing" or "public-key infrastructure" seemed intimidating. My goal here is to remove that mystery. We'll build understanding from the ground up, using Python's excellent libraries to make these concepts tangible. You don't need to be a math genius or a security expert to follow along. You just need curiosity.

Let's begin with the absolute foundation of all cryptography: good randomness.


Generating Strong Random Numbers

Think of randomness as the raw material for security. If an attacker can guess the random numbers you use for keys or passwords, your entire system is broken. In Python, you should never use the basic random module for security. Instead, we use the secrets module. It gives us random data strong enough for protecting secrets.

I'll show you a class I often use. It wraps the secrets module to handle common tasks like creating tokens, passwords, and cryptographic keys.

import secrets
import os

class SecureRandomGenerator:
    def __init__(self):
        # Uses the operating system's secure random source
        self.entropy_source = secrets.SystemRandom()

    def generate_token(self, byte_length=32):
        """Create a random token of given byte length."""
        return secrets.token_bytes(byte_length)

    def generate_password(self, length=16):
        """Make a secure password with letters, numbers, and symbols."""
        alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
        # Use secrets.choice for each character
        password = ''.join(secrets.choice(alphabet) for _ in range(length))
        return password

    def generate_cryptographic_key(self, key_length=256):
        """Generate raw bytes suitable for a cryptographic key."""
        # Key length is in bits, so we divide by 8 to get bytes
        key_material = os.urandom(key_length // 8)
        return key_material

# Let's see it in action
generator = SecureRandomGenerator()
print("A secure token:", generator.generate_token(16).hex())
print("A strong password:", generator.generate_password(20))
print("An AES-256 key:", generator.generate_cryptographic_key(256).hex())
Enter fullscreen mode Exit fullscreen mode

The key takeaway is simple: always reach for secrets or os.urandom() when security is involved. This habit ensures your keys, passwords, and initialization vectors start from a place of strength.


Symmetric Encryption: Sharing a Secret Key

Once we have good random keys, we can use them to lock up data. Symmetric encryption uses the same key to both lock (encrypt) and unlock (decrypt) information. It's fast and efficient for protecting large amounts of data, like files or database entries.

The gold standard is AES (Advanced Encryption Standard). We'll look at two modes: GCM and CBC. GCM is generally preferred because it provides both confidentiality (the data is secret) and authenticity (we can tell if it was tampered with). CBC is older but still widely used.

Here’s a practical class for symmetric encryption. I've used variations of this in file storage systems and for encrypting messages before sending them over a network.

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
import os

class SymmetricEncryption:
    def __init__(self):
        self.backend = default_backend()

    def encrypt_aes_gcm(self, plaintext: bytes, key: bytes):
        """Encrypt using AES-GCM. Returns ciphertext, nonce, and tag."""
        # A nonce is a "number used once". It should be random for each encryption.
        nonce = os.urandom(12)

        # Build the cipher object
        cipher = Cipher(
            algorithms.AES(key),
            modes.GCM(nonce),
            backend=self.backend
        )

        encryptor = cipher.encryptor()
        ciphertext = encryptor.update(plaintext) + encryptor.finalize()

        # The tag is for verifying the data hasn't been changed
        return ciphertext, nonce, encryptor.tag

    def decrypt_aes_gcm(self, ciphertext: bytes, key: bytes, nonce: bytes, tag: bytes) -> bytes:
        """Decrypt and verify AES-GCM data."""
        cipher = Cipher(
            algorithms.AES(key),
            modes.GCM(nonce, tag),  # The tag is needed here for verification
            backend=self.backend
        )

        decryptor = cipher.decryptor()
        return decryptor.update(ciphertext) + decryptor.finalize()

# Example: Encrypting a message
crypto = SymmetricEncryption()
key = os.urandom(32)  # A 256-bit (32-byte) key for AES-256

message = b"My secret project plan"
ciphertext, nonce, tag = crypto.encrypt_aes_gcm(message, key)

print("Original:", message)
print("Ciphertext (hex):", ciphertext.hex()[:50], "...")

# Decrypting it
decrypted = crypto.decrypt_aes_gcm(ciphertext, key, nonce, tag)
print("Decrypted:", decrypted)
print("Matches original?", message == decrypted)
Enter fullscreen mode Exit fullscreen mode

Notice how we need to store or transmit three things: the ciphertext, the nonce, and the tag. The key, of course, must remain secret. If you alter even one bit of the ciphertext during decryption, the verification will fail. This built-in tamper detection is a major benefit of authenticated modes like GCM.


Asymmetric Encryption: The Magic of Key Pairs

Symmetric encryption requires both parties to already share a secret key. But how do you establish that shared secret over an insecure channel like the internet? This is where asymmetric, or public-key, cryptography shines.

It uses a pair of keys: a public key you can share with anyone, and a private key you keep absolutely secret. Data encrypted with the public key can only be decrypted with the private key. This lets you set up secure communication with someone you've never met before.

The two main families are RSA (based on factoring large numbers) and ECC (Elliptic Curve Cryptography, which is more modern and uses smaller keys for equivalent strength). Let's look at both.

from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
from cryptography.hazmat.primitives import hashes, serialization

class AsymmetricCryptography:
    def __init__(self):
        self.default_hash = hashes.SHA256()

    def generate_rsa_keypair(self, key_size=2048):
        """Create an RSA public/private key pair."""
        private_key = rsa.generate_private_key(
            public_exponent=65537,  # A standard, secure value
            key_size=key_size,
        )
        return private_key, private_key.public_key()

    def rsa_encrypt(self, plaintext: bytes, public_key) -> bytes:
        """Encrypt a small message with an RSA public key."""
        # RSA can only encrypt data smaller than the key size.
        # It's often used to encrypt a symmetric key, not the data itself.
        ciphertext = public_key.encrypt(
            plaintext,
            padding.OAEP(  # Optimal Asymmetric Encryption Padding
                mgf=padding.MGF1(algorithm=self.default_hash),
                algorithm=self.default_hash,
                label=None
            )
        )
        return ciphertext

    def rsa_decrypt(self, ciphertext: bytes, private_key) -> bytes:
        """Decrypt with the RSA private key."""
        plaintext = private_key.decrypt(
            ciphertext,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=self.default_hash),
                algorithm=self.default_hash,
                label=None
            )
        )
        return plaintext

    def generate_ecc_keypair(self):
        """Generate a key pair using elliptic curve cryptography (P-256 curve)."""
        private_key = ec.generate_private_key(ec.SECP256R1())
        return private_key, private_key.public_key()

    def ecdsa_sign(self, data: bytes, private_key) -> bytes:
        """Create a digital signature using ECDSA."""
        signature = private_key.sign(
            data,
            ec.ECDSA(self.default_hash)
        )
        return signature

    def ecdsa_verify(self, data: bytes, signature: bytes, public_key) -> bool:
        """Verify an ECDSA signature with the public key."""
        try:
            public_key.verify(signature, data, ec.ECDSA(self.default_hash))
            return True
        except Exception:  # The signature is invalid
            return False

# Let's see RSA in action
asym = AsymmetricCryptography()
rsa_private, rsa_public = asym.generate_rsa_keypair(2048)

# Encrypt a short message (like a 32-byte AES key)
aes_key = os.urandom(32)
encrypted_key = asym.rsa_encrypt(aes_key, rsa_public)
print("Encrypted AES key (hex):", encrypted_key.hex()[:50], "...")

decrypted_key = asym.rsa_decrypt(encrypted_key, rsa_private)
print("Decrypted AES key matches?", aes_key == decrypted_key)

# Now let's sign a document with ECC (more efficient for signing)
ecc_private, ecc_public = asym.generate_ecc_keypair()
document = b"Agreement to pay $100."

signature = asym.ecdsa_sign(document, ecc_private)
print("\nDocument signature created.")

# Anyone with the public key can verify it came from the private key holder
is_valid = asym.ecdsa_verify(document, signature, ecc_public)
print("Signature is valid?", is_valid)

# Let's try to verify with tampered data
is_valid_tampered = asym.ecdsa_verify(b"Agreement to pay $1000.", signature, ecc_public)
print("Signature valid for tampered doc?", is_valid_tampered)
Enter fullscreen mode Exit fullscreen mode

This ability to verify authorship is powerful. Signatures are used everywhere, from software updates (to ensure you're installing the real package) to legal documents.


Cryptographic Hashing and Key Derivation

Hashing is a one-way street. You take data of any size, run it through a hash function like SHA-256, and get a fixed-size "fingerprint" (a digest). You cannot reverse this process to get the original data. If you change the input even slightly, the fingerprint changes completely.

This is perfect for verifying file integrity, storing passwords (you store the hash, not the password itself), and creating unique identifiers. Let's explore hashing and a crucial related concept: deriving keys from passwords.

from cryptography.hazmat.primitives import hashes, hmac
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2
import hashlib
import os

class HashFunctions:
    def compute_hash(self, data: bytes, algorithm='sha256') -> bytes:
        """Compute a cryptographic hash."""
        if algorithm == 'sha256':
            digest = hashes.Hash(hashes.SHA256())
            digest.update(data)
            return digest.finalize()
        # You can add more algorithms like SHA-512, SHA3-256, BLAKE2

    def compute_hmac(self, data: bytes, key: bytes, algorithm='sha256') -> bytes:
        """Hash-based Message Authentication Code.
        It's a hash that requires a secret key, proving both integrity and authenticity.
        """
        h = hmac.HMAC(key, hashes.SHA256())
        h.update(data)
        return h.finalize()

    def pbkdf2_derive_key(self, password: bytes, salt: bytes = None, iterations=100000):
        """Derive a cryptographic key from a password.
        PBKDF2 makes brute-force attacks much slower.
        """
        if salt is None:
            salt = os.urandom(16)  # A unique salt for each user

        kdf = PBKDF2(
            algorithm=hashes.SHA256(),
            length=32,  # Output a 32-byte (256-bit) key
            salt=salt,
            iterations=iterations  # Work factor: higher is slower but more secure
        )

        key = kdf.derive(password)
        return {'key': key, 'salt': salt}

# Using hashes
hasher = HashFunctions()
data = b"Hello, this is important data."
hash_digest = hasher.compute_hash(data)
print(f"SHA-256 of data: {hash_digest.hex()}")
print(f"Length is always {len(hash_digest)} bytes, regardless of input size.\n")

# Using HMAC
secret_shared_key = os.urandom(32)
mac = hasher.compute_hmac(data, secret_shared_key)
print(f"HMAC of data: {mac.hex()[:32]}...")
print("Without the secret key, an attacker cannot generate a valid HMAC.\n")

# Deriving a key from a password
password = b"myNotSoStrongPassword"
derivation_result = hasher.pbkdf2_derive_key(password, iterations=310000)
print(f"Derived key from password: {derivation_result['key'].hex()[:32]}...")
print(f"Salt used: {derivation_result['salt'].hex()}")
print("The salt ensures the same password yields different keys, defeating pre-computed attack tables.")
Enter fullscreen mode Exit fullscreen mode

A key point about password storage: never hash passwords with a single fast hash like SHA-256 directly. Always use a dedicated, slow Key Derivation Function (KDF) like PBKDF2, Scrypt, or Argon2 with a high iteration count (work factor). This is what makes checking a single password acceptable for a user, but trying billions of passwords (a brute-force attack) impractical for an attacker.


Digital Signatures and Certificate Handling

We saw a basic signature with ECDSA earlier. In the real world, we need a way to trust that a public key actually belongs to the person or organization it claims to. This is the problem solved by Public Key Infrastructure (PKI) and X.509 certificates.

A certificate is essentially a document that says, "I, a trusted Certificate Authority (CA), certify that this public key belongs to example.com." It contains the public key, identity information, and is itself digitally signed by the CA. Your web browser comes pre-loaded with public keys from trusted CAs.

Let's create a simple CA and issue a certificate. This is simplified, but it shows the principles behind the HTTPS padlock in your browser.

from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import datetime

class SimpleCertificateAuthority:
    def create_root_ca(self):
        """Creates a self-signed root CA certificate."""
        # 1. Generate a key pair for the CA itself
        ca_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)

        # 2. Define who the CA is
        subject = issuer = x509.Name([
            x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Test CA"),
            x509.NameAttribute(NameOID.COMMON_NAME, u"My Test Root CA"),
        ])

        # 3. Build and sign the CA certificate
        ca_cert = x509.CertificateBuilder().subject_name(
            subject
        ).issuer_name(
            issuer
        ).public_key(
            ca_key.public_key()
        ).serial_number(
            x509.random_serial_number()
        ).not_valid_before(
            datetime.datetime.utcnow()
        ).not_valid_after(
            datetime.datetime.utcnow() + datetime.timedelta(days=3650)  # 10 years
        ).add_extension(
            x509.BasicConstraints(ca=True, path_length=None),  # Marks it as a CA
            critical=True
        ).sign(ca_key, hashes.SHA256())

        return ca_cert, ca_key

    def issue_server_certificate(self, ca_cert, ca_key, server_name):
        """The CA issues a certificate for a server."""
        # 1. Server generates its own key pair
        server_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

        # 2. Define the server's identity
        subject = x509.Name([
            x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
            x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"Example Inc."),
            x509.NameAttribute(NameOID.COMMON_NAME, server_name),
        ])

        # 3. CA builds and signs the server's certificate
        server_cert = x509.CertificateBuilder().subject_name(
            subject
        ).issuer_name(
            ca_cert.subject  # The issuer is the CA
        ).public_key(
            server_key.public_key()
        ).serial_number(
            x509.random_serial_number()
        ).not_valid_before(
            datetime.datetime.utcnow()
        ).not_valid_after(
            datetime.datetime.utcnow() + datetime.timedelta(days=365)  # 1 year
        ).add_extension(
            x509.BasicConstraints(ca=False, path_length=None),  # Not a CA
            critical=True
        ).add_extension(
            x509.SubjectAlternativeName([x509.DNSName(server_name)]),  # For HTTPS
            critical=False
        ).sign(ca_key, hashes.SHA256())

        return server_cert, server_key

# Let's see it work
ca = SimpleCertificateAuthority()
root_cert, root_key = ca.create_root_ca()
print("Root CA Created.")
print(f"  Subject: {root_cert.subject}")
print(f"  Valid until: {root_cert.not_valid_after}\n")

# Issue a cert for our server
server_cert, server_key = ca.issue_server_certificate(root_cert, root_key, "myserver.example.com")
print("Server Certificate Issued.")
print(f"  Subject: {server_cert.subject}")
print(f"  Issuer (the CA): {server_cert.issuer}")
print(f"  Valid until: {server_cert.not_valid_after}")

# Serialize to common formats
server_cert_pem = server_cert.public_bytes(serialization.Encoding.PEM)
server_key_pem = server_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)
print(f"\nServer Certificate (PEM, first line): {server_cert_pem.splitlines()[0].decode()}")
Enter fullscreen mode Exit fullscreen mode

When your browser connects to https://myserver.example.com, the server sends its certificate. Your browser checks that a trusted CA (like the one we just created, if you had installed it) signed it, that the certificate's "Common Name" or "Subject Alternative Name" matches the website, and that the certificate hasn't expired. If all checks pass, you get the padlock.


Secure Communication Protocols (TLS/SSL)

Certificates come alive in the Transport Layer Security (TLS) protocol, the 'S' in HTTPS. TLS uses all the techniques we've discussed—randomness, symmetric encryption (for speed), asymmetric encryption (for key exchange), hashing, and certificates—to create a secure tunnel between a client and a server.

Python's ssl module lets us work with TLS. Here's a simplified example of a TLS server and client. In practice, you'd use higher-level libraries like requests for clients or frameworks for servers, but understanding the underlying ssl context is valuable.

import ssl
import socket
import threading
import time

def simple_tls_server():
    """A minimal TLS echo server."""
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    # In reality, load a real certificate and private key here.
    # We'll create a self-signed one for demonstration.
    context.load_cert_chain('server.crt', 'server.key')

    # Configure secure settings
    context.minimum_version = ssl.TLSVersion.TLSv1_2
    context.set_ciphers('ECDHE+AESGCM')

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.bind(('localhost', 4443))
        sock.listen()
        print("Server: Listening on localhost:4443")

        with context.wrap_socket(sock, server_side=True) as tls_sock:
            conn, addr = tls_sock.accept()
            with conn:
                print(f"Server: Connection from {addr}")
                print(f"Server: Using {conn.version()} with cipher {conn.cipher()[0]}")
                data = conn.recv(1024)
                print(f"Server: Received: {data}")
                conn.send(b"TLS Echo: " + data)

def simple_tls_client():
    """A client that connects to our TLS server."""
    time.sleep(1)  # Give server a moment to start
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.check_hostname = False  # For our self-signed demo cert only!
    context.verify_mode = ssl.CERT_NONE  # Don't verify cert in this demo

    with socket.create_connection(('localhost', 4443)) as sock:
        with context.wrap_socket(sock, server_hostname='localhost') as tls_sock:
            print(f"Client: Connected using {tls_sock.version()}")
            tls_sock.send(b"Hello Secure World!")
            response = tls_sock.recv(1024)
            print(f"Client: Server replied: {response}")

# Note: This code requires 'server.crt' and 'server.key' files.
# You can generate them using the certificate code from the previous section.
# For a real test, uncomment the following lines after generating the files:
# server_thread = threading.Thread(target=simple_tls_server, daemon=True)
# server_thread.start()
# simple_tls_client()
Enter fullscreen mode Exit fullscreen mode

The handshake is the clever part. The client and server negotiate which cryptographic algorithms to use, the server proves its identity with its certificate, and they use asymmetric encryption to securely agree on a new symmetric session key. All this happens before any application data is sent. After that, all communication is encrypted and authenticated with that fast symmetric key.


Putting It All Together: A Secure Messaging Pattern

Let's combine these pieces into a common pattern: sending a secure message. We'll use:

  1. A random symmetric key for speed (AES-GCM).
  2. RSA to securely send that symmetric key to the recipient.
  3. A digital signature (ECDSA) to prove who sent it.
from cryptography.hazmat.primitives import serialization
import json

def send_secure_message(recipient_public_key, sender_private_key, message):
    """Simulate preparing a secure message for a recipient."""

    # 1. Create a random one-time symmetric key for this message
    sym_key = os.urandom(32)

    # 2. Encrypt the actual message with AES-GCM
    crypto = SymmetricEncryption()
    ciphertext, nonce, tag = crypto.encrypt_aes_gcm(message.encode(), sym_key)

    # 3. Encrypt the symmetric key with the recipient's RSA public key
    asym = AsymmetricCryptography()
    encrypted_sym_key = asym.rsa_encrypt(sym_key, recipient_public_key)

    # 4. Sign the whole package with the sender's private key (using ECC)
    # We sign a hash of the critical components.
    hasher = HashFunctions()
    components_to_sign = encrypted_sym_key + ciphertext + nonce + tag
    package_hash = hasher.compute_hash(components_to_sign)

    # Generate an ECC key for the sender for signing (in reality, this is long-term)
    ecc_sender_private, _ = asym.generate_ecc_keypair()
    signature = asym.ecdsa_sign(package_hash, ecc_sender_private)

    # 5. Package everything for transport
    package = {
        'encrypted_key': encrypted_sym_key.hex(),
        'ciphertext': ciphertext.hex(),
        'nonce': nonce.hex(),
        'tag': tag.hex(),
        'signature': signature.hex()
    }
    return json.dumps(package)

def receive_secure_message(package_json, recipient_private_key, sender_public_key):
    """Receive and decrypt/verify the message."""
    package = json.loads(package_json)

    # Convert from hex
    encrypted_sym_key = bytes.fromhex(package['encrypted_key'])
    ciphertext = bytes.fromhex(package['ciphertext'])
    nonce = bytes.fromhex(package['nonce'])
    tag = bytes.fromhex(package['tag'])
    signature = bytes.fromhex(package['signature'])

    asym = AsymmetricCryptography()

    # 1. Decrypt the symmetric key with our RSA private key
    sym_key = asym.rsa_decrypt(encrypted_sym_key, recipient_private_key)

    # 2. Verify the signature
    hasher = HashFunctions()
    components_to_verify = encrypted_sym_key + ciphertext + nonce + tag
    package_hash = hasher.compute_hash(components_to_verify)

    if not asym.ecdsa_verify(package_hash, signature, sender_public_key):
        raise ValueError("Message signature is invalid! Message may be forged.")

    # 3. Decrypt the message with the symmetric key
    crypto = SymmetricEncryption()
    plaintext = crypto.decrypt_aes_gcm(ciphertext, sym_key, nonce, tag)

    return plaintext.decode()

# --- Simulate a conversation between Alice and Bob ---
print("\n=== Secure Messaging Simulation ===\n")

# Setup: Alice and Bob generate long-term RSA key pairs for encryption
asym = AsymmetricCryptography()
alice_rsa_private, alice_rsa_public = asym.generate_rsa_keypair()
bob_rsa_private, bob_rsa_public = asym.generate_rsa_keypair()

# Setup: They also generate long-term ECC key pairs for signing
alice_ecc_private, alice_ecc_public = asym.generate_ecc_keypair()
bob_ecc_private, bob_ecc_public = asym.generate_ecc_keypair()

# Alice sends a message to Bob
message_from_alice = "Bob, the meeting is at 3 PM tomorrow at the usual place."
print(f"Alice's original message: {message_from_alice}")

secure_package = send_secure_message(
    recipient_public_key=bob_rsa_public,
    sender_private_key=alice_ecc_private,  # Alice signs with her private key
    message=message_from_alice
)
print(f"\nAlice sends this secure package (JSON):\n{secure_package[:200]}...\n")

# Bob receives and processes it
decrypted_message = receive_secure_message(
    package_json=secure_package,
    recipient_private_key=bob_rsa_private,  # Bob decrypts with his private key
    sender_public_key=alice_ecc_public      # Bob verifies with Alice's public key
)
print(f"Bob decrypts and verifies the message: {decrypted_message}")
print(f"Message is authentic and intact? {decrypted_message == message_from_alice}")
Enter fullscreen mode Exit fullscreen mode

This pattern provides a strong combination: confidentiality (only Bob can read it), integrity (Bob knows it wasn't altered), and authenticity (Bob knows it came from Alice). This is the essence of secure communication.


The journey from a simple random number to a fully authenticated, encrypted conversation shows how these cryptographic building blocks fit together. Each technique has its role, and their combination is what makes modern digital security possible.

The most important lesson is to use well-established libraries, like Python's cryptography, and to follow current best practices. Never roll your own core cryptographic algorithms. The field evolves, so what's secure today (like 2048-bit RSA) might need upgrading tomorrow (to larger keys or different algorithms like those based on elliptic curves).

Start by integrating these techniques one at a time. Add secure random token generation to your API. Hash and salt your user passwords with a slow KDF. Experiment with encrypting a configuration file. As you build confidence, you'll find that implementing cryptographic systems becomes a natural part of designing robust, trustworthy applications.

📘 Checkout my latest ebook for free on my channel!

Be sure to like, share, comment, and subscribe to the channel!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | Java Elite Dev | Golang Elite Dev | Python Elite Dev | JS Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Top comments (0)