If the data you manage isn't encrypted, you can't claim to have a security strategy. That's not an opinion—it's the starting point. Every day we see companies storing passwords in plaintext, using outdated TLS, or ignoring digital signatures. We, at Meteora Web, have been working on these topics since 2017 and have seen what happens when cryptography is treated as an afterthought rather than a foundation. Here's everything you need to design systems that truly protect data.
Cryptography Fundamentals: Symmetric, Asymmetric, and Hash Functions
Cryptography is applied mathematics, not magic. Three families cover 99% of use cases:
Symmetric Cryptography
Same key for encryption and decryption. Fast, efficient, but requires a secure channel to exchange the key. AES is the standard. We use it for file encryption, disk encryption, and TLS sessions.
Asymmetric Cryptography
Two related keys: a public key to encrypt, a private key to decrypt. RSA and ECC are the pillars. Ideal for key exchange and digital signatures. Higher computational cost, but solves key distribution.
Hash Functions
They don't encrypt but produce a unique digital fingerprint. SHA-256 is the minimum. Used for integrity, password storage, verification. MD5 and SHA-1 are dead—never use them.
Common mistake: thinking hash = encryption. Hashing is one-way; encryption is reversible (with the right key).
AES, RSA, ECC: Modern Algorithms Compared
AES (Advanced Encryption Standard)
AES-256 is the symmetric encryption standard. Used by governments, banks, cloud providers. Supports modes like GCM (built-in authentication) and CBC. We recommend AES-256-GCM for data in transit and at rest. Practical Python example:
Sponsored Protocol
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
import os
key = os.urandom(32) # AES-256
iv = os.urandom(12) # GCM uses 96 bits
cipher = Cipher(algorithms.AES(key), modes.GCM(iv))
encryptor = cipher.encryptor()
plaintext = b"Sensitive client data"
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
# encryptor.tag contains authentication tag
RSA
Asymmetric, based on factorization. Keys of 2048 or 4096 bits. Slow but still used for key exchange and signatures. Problem: vulnerable to quantum attacks. For now, 4096 bits is still secure.
ECC (Elliptic Curve Cryptography)
Same security as RSA with much shorter keys (256-bit ECC ≈ 3072-bit RSA). Faster, less bandwidth. Used in TLS 1.3, Signal, Bitcoin. Recommended curve: Curve25519 (X25519 for key exchange, Ed25519 for signatures).
We, at Meteora Web, have standardized on ECC for new projects. The performance and space savings are substantial.
TLS and HTTPS: Handshake, Certificates, and Secure Versions
TLS protects communication between client and server. You should only use TLS 1.2 or 1.3 today. TLS 1.0 and 1.1 are deprecated. The handshake establishes a temporary symmetric key using asymmetric cryptography (typically ECDHE). An X.509 certificate signed by a CA guarantees server identity.
Sponsored Protocol
Best practices
- Disable TLS 1.0/1.1 and SSL 3.0
- Use certificates with ECDSA keys (Curve P-256 or P-384)
- Enable HSTS and Certificate Transparency
- Automate renewal with Let's Encrypt
We have automated SSL renewals for dozens of clients. On one server, the manual renewal broke, causing site errors. Since then, every configuration includes a cron job for Certbot with Slack notification.
Secure JWT: Signing, Verification, and Vulnerabilities
JSON Web Tokens are everywhere: authentication, API sessions, OAuth. But misused, they become a vulnerability. Key points:
Algorithms and signatures
Use only asymmetric algorithms (RS256, ES256). HMAC (HS256) with shared secret is acceptable only if the key is secret and rotated. Never accept 'none' as algorithm. Classic vulnerability: man-in-the-middle changes the alg header to 'none'.
Claims and validation
- Always verify `iss`, `aud`, `exp`, `nbf`
- Don't put sensitive data in payload (it's not encrypted)
- Use short-lived refresh tokens with rotation
import jwt
private_key = open('private.pem').read()
payload = {
"sub": "user123",
"role": "admin",
"exp": 1690000000
}
token = jwt.encode(payload, private_key, algorithm="RS256")
# Verification
try:
decoded = jwt.decode(token, public_key, algorithms=["RS256"])
except jwt.ExpiredSignatureError:
print("Token expired")
GPG and Digital Signatures: Encrypt and Sign Files and Emails
GnuPG (GPG) implements OpenPGP. Perfect for encrypting files, emails, and verifying authenticity. We use it to exchange sensitive documents with clients.
Sponsored Protocol
Basic operations
gpg --gen-key # generate key pair
chmod 700 ~/.gnupg
gpg --encrypt --recipient "client@email.com" report.pdf
gpg --decrypt report.pdf.gpg
gpg --sign report.pdf # sign
gpg --verify report.pdf.sig report.pdf
Note: Distribute public key securely (keyserver, HTTPS site, in-person). Never share private key, never on cloud.
Password Hashing: bcrypt, Argon2, and Why MD5 is Dead
Passwords must never be stored in plaintext, nor with fast hashes like MD5 or SHA-256. Use slow algorithms with salt: bcrypt, scrypt, Argon2. Argon2id is the PHC winner and most recommended today.
from argon2 import PasswordHasher
ph = PasswordHasher()
hash = ph.hash("mySecurePassword")
print(hash)
# $argon2id$v=19$m=65536,t=3,p=4$...
# Verification
try:
ph.verify(hash, "wrongPassword")
except:
print("Failed")
We still see companies using MD5 for passwords. A database leak compromises all passwords in seconds. Argon2id with unique salt per user is the only way.
End-to-End Encryption: Signal Protocol and Applications
E2EE ensures only sender and receiver can read the message. The Signal Protocol combines Diffie-Hellman curves, ratchets, and signatures. It's the standard for WhatsApp, Signal, Messenger (optional).
Key principles:
- Forward secrecy: if a key is compromised, past messages remain secret.
- Future secrecy: future keys are also protected.
- Double ratchet: continuously renews keys.
We integrated E2EE into a proprietary messaging platform for a healthcare client. Data must stay private even from our server. We used the Olm library (Matrix).
Sponsored Protocol
Quantum Computing and Cryptography: Towards Post-Quantum Algorithms
Quantum computers, if powerful enough, will break RSA, ECC, and Diffie-Hellman (Shor's algorithm). Symmetric (AES) holds up by doubling key size. The community is standardizing post-quantum algorithms: CRYSTALS-Kyber (key exchange), CRYSTALS-Dilithium (signatures), FALCON, SPHINCS+.
What to do now?
- Monitor NIST recommendations.
- Start supporting hybrid algorithms (e.g., X25519+Kyber).
- Don't wait for 2030: migration will take years.
We are already testing Kyber in test environments. Advice: keep crypto libraries updated and watch for new versions.
Key Management: Generation, Distribution, Rotation
Managing keys is harder than cryptography itself. Common mistakes: hardcoding keys in code, no rotation, insecure backups.
Minimal practices
- Generate keys with sufficient entropy (urandom, /dev/random).
- Use a vault: HashiCorp Vault, AWS KMS, Azure Key Vault.
- Rotate keys periodically (every 90 days for HMAC, yearly for asymmetric).
- Never share keys via email, chat, or code.
# Generate a secure AES-256 key on Linux
dd if=/dev/urandom bs=32 count=1 | base64 > aes-key.txt
chmod 600 aes-key.txt
We saw a client who stored their database encryption key in a .env file on a shared server. Anyone with server access could decrypt everything. Now they use Vault.
Cryptography in Python with the cryptography Library
The cryptography library is the gold standard in Python. It provides high-level and low-level APIs, secure by default.
Sponsored Protocol
Complete example: encrypt a file with AES-GCM
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import serialization
import os
def encrypt_file(key, input_path, output_path):
iv = os.urandom(12)
cipher = Cipher(algorithms.AES(key), modes.GCM(iv))
encryptor = cipher.encryptor()
with open(input_path, 'rb') as f:
plaintext = f.read()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
with open(output_path, 'wb') as f:
f.write(iv)
f.write(encryptor.tag)
f.write(ciphertext)
# Usage
key = os.urandom(32)
encrypt_file(key, 'report.pdf', 'report.pdf.enc')
See the official documentation for details.
In Summary — What to Do Now
- Audit your cryptographic practices. Where are you using weak hashes? Old TLS? Hardcoded keys?
- Standardize on AES-256-GCM for symmetric data, X25519/Ed25519 for asymmetric, Argon2id for passwords.
- Implement a key management system (Vault or KMS) with automatic rotation.
- Update libraries and watch for post-quantum migrations.
- Train your team: cryptography is an investment, not a cost. As we, coming from accounting, say: protecting data is like protecting capital.
For deeper dives, check our OSINT Reconnaissance guide to see how cryptographic flaws are discovered. Also consult MDN Web Security for updated guidelines.