Description 👇
A hands-on guide to AES-GCM, ChaCha20-Poly1305, Argon2id, HKDF, X25519 and Shamir, with a comparison table, Python code, and a real-world case study.
"Just use AES-256" is the most repeated and least useful sentence in security.
Ciphers almost never break. What breaks is everything around them: a guessable password, a reused nonce, ciphertext nobody authenticated, a key derived with the wrong function. This post is a tour of the whole stack: AES, ChaCha20, Argon2id, HKDF, HMAC, X25519, Ed25519 and Shamir's Secret Sharing. For each one: how it works, why it exists, when to pick it, and how the pieces fit together.
We'll also look at a real implementation as a worked example: ATLOCK v5, a pure-Python security suite from Akhouri Systems, packaged as a single .exe. Seeing these ideas as design decisions in shipping code beats textbook diagrams.
TL;DR
- AES-256-GCM and ChaCha20-Poly1305 are both excellent. Pick by hardware and by how safely you can manage nonces.
- Turn passwords into keys with Argon2id, never a plain hash.
- Always use AEAD (authenticated encryption). Encryption without authentication is a bug waiting for an attacker.
- Your real security is usually password entropy and nonce discipline, not key size.
1. The mental model: three jobs, three tools
Encryption is several problems wearing one name:
| Job | Question it answers | Typical tools |
|---|---|---|
| Confidentiality | Can someone else read this? | AES, ChaCha20 |
| Integrity and authenticity | Was this modified? Who sent it? | GCM tag, Poly1305, HMAC, Ed25519 |
| Key derivation | Where does the key come from? | Argon2id, HKDF |
| Key agreement | How do two parties share a key? | X25519 |
| Recovery | What if the key is lost? | Shamir's Secret Sharing |
Start with a threat model. Assume the attacker has a copy of your encrypted file, unlimited time and a rack of GPUs. Brute-forcing a 256-bit key is physically impossible, so a smart attacker goes after the password that produces the key. That single idea drives most modern design:
Encryption is only as strong as the cheapest thing an attacker can guess.
Sections 5 and 6 are all about making that "cheapest thing" as expensive as possible.
2. AES: the world's default block cipher
AES (Rijndael, FIPS 197) encrypts 128-bit blocks with a 128-, 192- or 256-bit key. It runs 10, 12 or 14 rounds of four operations on a 4x4 byte grid:
- SubBytes: every byte goes through a non-linear lookup (the S-box), which defeats algebraic attacks.
- ShiftRows: rows are rotated so bytes spread across columns.
- MixColumns: each column is multiplied by a matrix in GF(2^8), so each output byte depends on four inputs.
- AddRoundKey: XOR with a round key derived from your key.
No practical attack on full AES exists, and modern CPUs ship AES-NI instructions that execute rounds in hardware: fast and constant-time.
A block cipher only handles 16 bytes. How you chain blocks (the mode) is where security is won or lost:
| Mode | What it is | Verdict |
|---|---|---|
| ECB | Each block encrypted independently | Never. Identical blocks leak patterns (the "ECB penguin"). |
| CBC | Each block XORed with previous ciphertext | Legacy. Needs padding, no authentication, padding-oracle attacks. |
| CTR | Encrypts a counter to make a keystream | Fine, but no integrity: attackers can flip bits predictably. |
| XTS | Tweakable mode for disk sectors | Great for full-disk encryption; also unauthenticated. |
| GCM | CTR plus GHASH authentication | The modern default. |
How GCM works, and where it bites
GCM (NIST SP 800-38D) encrypts with CTR mode and computes a 128-bit tag with GHASH, a polynomial hash over GF(2^128). On decryption the tag is verified first. If one bit of the ciphertext, nonce or associated data (AAD) changed, you get an error and no plaintext.
Three rules every engineer should tattoo somewhere:
- Never reuse a (key, nonce) pair. It leaks the XOR of two plaintexts and lets an attacker recover the GHASH key, after which they can forge valid tags. This is the classic GCM disaster.
- Random 96-bit nonces have a ceiling. Because of the birthday bound, NIST advises staying under about 2^32 messages per key with random nonces.
- One message is capped at about 64 GiB per (key, nonce). Big files must be chunked.
GCM is also not key-committing: a crafted ciphertext can decrypt validly under two different keys, which enables "partitioning oracle" attacks (Len, Grubbs and Ristenpart, 2021) on password-based systems. The fix is an explicit key commitment, covered in Section 8.
3. ChaCha20-Poly1305: the software-friendly challenger
Daniel J. Bernstein's ChaCha20 is a stream cipher built only from Add, Rotate and Xor ("ARX"). No lookup tables means it is constant-time by construction and fast even on CPUs without AES hardware.
The state is a 4x4 grid of 32-bit words: four constants, the 256-bit key, a block counter and a 96-bit nonce (RFC 8439). It is scrambled by 20 rounds of the quarter-round:
a += b; d ^= a; d <<<= 16;
c += d; b ^= c; b <<<= 12;
a += b; d ^= a; d <<<= 8;
c += d; b ^= c; b <<<= 7;
The 64-byte output is a keystream block; XOR it with plaintext, bump the counter, repeat.
Poly1305 is the authenticator: it evaluates a polynomial modulo the prime 2^130 - 5 using a one-time key taken from the first keystream block (counter 0). Encryption starts at counter 1. The result is a 128-bit tag, just like GCM's.
XChaCha20-Poly1305 stretches the nonce to 192 bits by running HChaCha20 to derive a subkey from the key and the first 16 nonce bytes. With 192 bits you can pick nonces at random essentially forever, which removes GCM's biggest operational footgun.
ChaCha20-Poly1305 protects traffic in TLS 1.3, WireGuard and OpenSSH, and it is the preferred suite on many phones.
4. Head-to-head comparison
| AES-256-GCM | ChaCha20-Poly1305 | XChaCha20-Poly1305 | |
|---|---|---|---|
| Design | SPN block cipher + CTR + GHASH | ARX stream cipher + Poly1305 | HChaCha20 subkey + ChaCha20-Poly1305 |
| Key size | 256 bits | 256 bits | 256 bits |
| Nonce | 96 bits | 96 bits | 192 bits |
| Tag | 128 bits | 128 bits | 128 bits |
| Speed with AES-NI | Excellent | Very good | Very good |
| Speed without AES-NI | Slower; table-based AES can leak timing | Excellent, constant-time | Excellent |
| Random-nonce safety | Limited (~2^32 msgs/key) | Limited (96-bit) | Safe in practice |
| Max per (key, nonce) | ~64 GiB | ~256 GiB | ~256 GiB |
| Standard | FIPS 197, SP 800-38D | RFC 8439 | IETF draft |
| Best for | Servers, desktops, FIPS requirements | Phones, embedded, mixed hardware | Many messages per key with random nonces |
How to choose. On modern x86/ARM with AES instructions, AES-256-GCM is hard to beat and is what compliance regimes expect. For unknown hardware, or if you want random nonces with zero worry, choose the ChaCha family. Same security level either way. Correct usage matters far more than the brand name.
5. From password to key: why Argon2id exists
Humans can't remember 256 random bits. They remember passwords, and passwords have little entropy. So we need a function that turns correct horse battery staple into a key deliberately slowly and expensively, so every guess costs the attacker real money.
A fast hash like SHA-256 is the wrong tool: a modern GPU tries billions per second. A proper password KDF adds cost:
| KDF | Year | Cost it imposes | Notes |
|---|---|---|---|
| PBKDF2 | 2000 | CPU time only | Ubiquitous but GPU/ASIC friendly. OWASP suggests ~600,000 iterations with HMAC-SHA256. |
| bcrypt | 1999 | CPU + tiny 4 KB state | Still decent; truncates input at 72 bytes. |
| scrypt | 2009 | CPU and memory | Memory-hard; parameters are harder to reason about. |
| Argon2 | 2015 | CPU, memory, parallelism | Winner of the Password Hashing Competition; specified in RFC 9106. |
Why memory-hardness wins
GPUs and ASICs are fast because they run thousands of tiny cores, but each core has very little memory. Argon2 fills a matrix of 1 KiB blocks (tens or hundreds of MiB) and reads it back in an unpredictable pattern. If one guess needs 64 MiB, a GPU with 16 GB of VRAM runs only about 250 guesses in parallel instead of tens of thousands. Memory is the one resource attackers can't cheaply scale.
The three flavours
- Argon2d: data-dependent memory access. Strongest against GPUs, but leaks through side channels.
- Argon2i: data-independent access. Side-channel resistant, weaker against time-memory trade-offs.
- Argon2id: first half behaves like "i", second like "d". Use this one; it's what RFC 9106 recommends.
The knobs
- m (memory): the main defence. Raise this first.
- t (passes): linear time multiplier.
- p (lanes): parallelism, matched to expected cores.
- salt: at least 16 random bytes, unique per secret. Not secret, but it kills precomputed tables and makes identical passwords yield different keys.
RFC 9106 gives two profiles: t=1, p=4, m=2 GiB when memory is plentiful, and t=3, p=4, m=64 MiB as the constrained fallback. OWASP's absolute minimum is m=19 MiB, t=2, p=1. Rule of thumb: tune to roughly 0.5 to 1 second on your slowest supported machine.
The arithmetic of a strong passphrase
Four random words from a 7,776-word diceware list give log2(7776^4), about 51.7 bits. Suppose, generously, that an attacker manages 10,000 Argon2id guesses per second. Searching half the space takes roughly 5,500 years. Make the password Summer2024! and it falls in minutes, whatever the cipher. The KDF multiplies the cost of each guess; entropy decides how many guesses are needed. You need both.
6. The glue that holds it together
HKDF (RFC 5869). Argon2id is deliberately expensive, so run it once to get a master secret, then expand it into as many independent keys as you need with HKDF-SHA256 and a different info label per purpose (encryption, authentication, recovery, logging). This is domain separation: leak or misuse one key and the others stay safe.
HMAC. A keyed hash for integrity, key commitment and subkey derivation. Compare tags with a constant-time function (hmac.compare_digest), never ==.
AAD. Data that is authenticated but not encrypted, like a file header. Binding the header into the AEAD means an attacker can't swap parameters, reorder chunks or truncate the tail unnoticed.
Key commitment. Store HMAC(key, label) in the header and verify it before decrypting anything. It closes GCM's multi-key weakness and gives instant, clean "wrong password" detection.
Chunking. Encrypt big files in independent, individually authenticated chunks. Put the chunk index in the nonce and bind it with AAD so chunks can't be reordered or cut off. You also stay far below GCM's 64 GiB limit and can stream gigabytes in constant memory.
Length hiding. Ciphertext length leaks plaintext length. Padding to a scheme like Padme (at most about 12% overhead) blurs the true size.
A minimal sketch in Python
Here is the core pipeline in about 20 lines (a teaching sketch, not production code):
import os
from argon2.low_level import hash_secret_raw, Type
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def derive(password: bytes, salt: bytes):
# memory_cost is in KiB -> 64 MiB here
master = hash_secret_raw(password, salt, time_cost=3, memory_cost=65536,
parallelism=4, hash_len=32, type=Type.ID)
hk = lambda info: HKDF(hashes.SHA256(), 32, None, info).derive(master)
return hk(b"demo/enc/v1") # one label per purpose
def seal(password: bytes, data: bytes) -> bytes:
salt, nonce = os.urandom(16), os.urandom(12)
key = derive(password, salt)
header = salt + nonce # authenticated as AAD
return header + AESGCM(key).encrypt(nonce, data, header)
def open_(password: bytes, blob: bytes) -> bytes:
salt, nonce, ct = blob[:16], blob[16:28], blob[28:]
key = derive(password, salt)
return AESGCM(key).decrypt(nonce, ct, blob[:28]) # raises InvalidTag if tampered
Swap AESGCM for ChaCha20Poly1305 and the same code works. The API is identical, which is why choosing between them is mostly an operational decision.
7. Public keys and recovery
X25519 is elliptic-curve Diffie-Hellman on Curve25519: two parties swap public keys and each computes the same shared secret without ever transmitting it. Run that through HKDF and you have a symmetric key. Ed25519 is the matching signature scheme: fast, compact and deterministic, so there's no fragile per-signature randomness to get wrong.
In practice you use hybrid encryption: a random file key encrypts the data with AES or ChaCha, and X25519 wraps that small key for each recipient.
Shamir's Secret Sharing answers "what if I lose the key?" without a single point of failure. The secret becomes the constant term of a random polynomial and each share is a point on it. Any k of n shares rebuild the secret; k-1 shares reveal literally nothing. That is information-theoretic security, not just computational.
8. Case study: how ATLOCK v5 puts this into practice
Theory is cheap. Here is what these decisions look like in a shipping product.
ATLOCK v5 is the Akhouri Systems total security suite for Windows: pure Python, packaged as a single .exe inside a zip. It doesn't invent cryptography. It stands on the widely used cryptography and argon2-cffi libraries and spends its effort on the part that usually goes wrong: composition.
Here's the path your password takes when you lock a file (container format v3.1):
password (+ optional keyfile / hardware key)
|
v Argon2id (random 16-byte salt, >=64 MiB, t>=3, p=4)
master secret
|
v HKDF-SHA256 (separate labels per purpose)
enc_key --> commitment = HMAC-SHA256(enc_key, label) <- checked before any decryption
|
v per-seal subkey = HMAC(enc_key, label || random nonce_base)
AES-256-GCM on every 8 MiB chunk
nonce = nonce_base (8 bytes) || chunk_index (4 bytes)
AAD = header-bound
And the design choices, mapped to the ideas above:
| Decision in ATLOCK v5 | Problem it solves |
|---|---|
| Argon2id with auto-benchmark scaling memory toward ~0.6 s per hash, floor of 64 MiB / 3 passes / 4 lanes (128 MiB / 4 passes for vault and recovery keys) | Cost tuned to your machine, never below a safe minimum |
| HKDF domain separation: vault, recovery wrap, state MAC, audit log, TOTP wrap and more each get their own key | One compromised key doesn't cascade |
| Key commitment verified before any plaintext is produced | Blocks partitioning-oracle style attacks; instant clean "wrong password" |
Fresh random nonce_base + per-seal subkey |
Re-sealing a file can never reuse a (key, nonce) pair |
| 8 MiB chunks, index in the nonce, header-bound AAD | Streams huge files, authenticates each chunk, detects reordering and truncation |
| Optional Padme padding | Hides true file size |
| KDF ceilings and floors from the header: a file can't demand more than 512 MiB / 6 passes / 8 lanes, nor downgrade below the minimum | A tampered file can neither DoS you nor weaken the KDF |
| Constant-time comparisons, atomic writes, verify-before-shred | Mistakes and crashes don't cost you data |
Around that core sits a full suite in one program:
- File Guard and folder locking with a password, optionally plus a keyfile or hardware key, so an attacker needs both
- Password vault with TOTP codes, FIDO2 security keys (YubiKey and similar) and Windows Hello
- TPM-backed hardware key mixed into the local state key via HKDF
- Shamir M-of-N recovery and one-time recovery codes
- Public-key mode: X25519 encryption plus Ed25519 signatures
- Escalating lockouts with clock and state-rollback detection
- Secure shredding, password-strength estimation and a signed-release check
- A built-in self-test and a fuzzer that throws random corruptions at the container parser
Because it's a single readable Python program, every claim in this article is something you can follow line by line.
Try it on your own files. Download ATLOCK v5 (Windows 10/11): ATLOCK_v5.zip. Unzip, run it, lock a test folder with a four-word passphrase, then run
--selftestand--fuzzand watch the container format from Section 6 defend itself.
9. What encryption can't do (an honest section)
A good security tool states its limits. ATLOCK's own documentation does, and so should every serious guide:
- Malware running as you wins. Anyone who can execute code in your session can read process memory or alter the program. Anti-tamper and memory wiping raise the cost; they don't make it impossible.
- Python can't guarantee RAM erasure. Strings and bytes are immutable and may be copied, so wiping is best-effort.
- Shredding on SSDs isn't guaranteed. Wear-levelling and snapshots keep stale blocks. Turn on full-disk encryption (BitLocker) and treat shredding as an extra layer.
-
Weak passwords stay weak. Argon2id slows guessing; it can't rescue
password123. - Not code-signed. Windows SmartScreen may warn on first run, as it does for most independent software. Verify the download source before running.
Honesty about the threat model isn't a weakness. It's what separates engineering from marketing.
10. Cheat sheet
| If you need... | Use |
|---|---|
| Encrypt data at rest on a modern PC | AES-256-GCM, chunked, with key commitment |
| Mobile, embedded or unknown hardware | ChaCha20-Poly1305 |
| Random nonces at huge scale | XChaCha20-Poly1305 |
| Password to key | Argon2id (RFC 9106 params, tuned to ~0.5 to 1 s) |
| Many keys from one secret | HKDF-SHA256 with distinct info labels |
| Share a key over an open channel | X25519, then HKDF |
| Prove who sent it | Ed25519 |
| Safe recovery | Shamir k-of-n |
| Whole-disk protection | BitLocker or LUKS (XTS) |
Seven mistakes to avoid
- Reusing a nonce with GCM or Poly1305.
- Encrypting without authenticating (raw CBC or CTR).
- Using a fast hash for password-based keys.
- Skipping the salt, or using a short or constant one.
- Using one key for several purposes instead of HKDF labels.
- Comparing tags with
==instead of a constant-time check. - Rolling your own cipher. Compose proven primitives; don't invent them.
Final word
The cipher is the easy part. Security lives in the key derivation, the nonce discipline, the authentication, and the humility to document what you can't protect. Learn those and you can evaluate any encryption tool, including ours.
If you'd rather watch these ideas work than read about them, download ATLOCK v5 from Akhouri Systems and test them on your own data.
Your turn: do you default to AES-GCM or ChaCha20-Poly1305 in your projects, and why? What's the nastiest nonce or KDF bug you've run into? Drop it in the comments.
ATLOCK is an Akhouri Systems product. Technical details reflect ATLOCK v5's container format v3.1 and its documented security notes.
Top comments (95)
Okay 👍🏻
👍👌
Can't you make each ATLOCK mention a link so people can click it anytime?
means?
Like this: ATLOCK
oh you want that in App I add link to repo or something like that?
if yes then already v5 has buttons for it
or anything else?
🤣
I mean in this post whenever you mention ATLOCK
@technogamerz didi, listen what @effessdev bro send song to me 😂😂
bro already in post there is check it.
a download link.
Yeah saw it! 😀
😃🙃
youtube.com/shorts/n3Go4Z9l0JA?si=...
Link
JNU waale ne kya hi kya hai lol 😂
India's current foreign minister is laughing in corner
youtube.com/shorts/1dmxL3j4J9I?si=...
Peak level 🔥🔥🙌🏻 By the way, @effessdev do you know Hindi?
India's other ministers : 💩🪠
India's PM & Foreign minister representing India in global stage: 🔥☠️👑
Only a little bit 😕
No problem!
@technogamerz

didi you also got it bruhh why only I'm left behind I've also posted many great posts bruhh curators are ignoring me...
How many gems do you have? 💎
000000000000000000 multiplied by 0000000 = 0
And @effessdev ?
2 gems
Okay 👍🏻
you gave me gem?????????????????????????????????????????????????????
Yeah!
thanks thanks thanks thanks thanks didiiii
but still my badge doesn't came will it take a 24hours or a week time?
You need 2 gems for this. Do one thing, give one gem to @effessdev and they’ll give you a Gem in return. Then you’ll get this badge!
okay I'm giving him for his comment
But i dont have a vase 🤔
so I give you or not?
Give it! But saying that i dont have a vase in advance so you don't get mad 😅
then first I should confirm from didi.
No vase I mean gem!!! 🤣
bro you've gem give me bro 1 please then I will receive badge
You give me gem first 😎
I just typed it by mistake, bro. Please forgive me 🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣
Ok ok 🤣 What kind of mistake is that 😭
bruhh it is risky but if you gave me there is no risk and I will be given a badge broo please it's order of your 2nd Founder @ kitnerapps ☠️
Anmol aapko inhe gem dena hai pahle!
Bro @koda2026 gave me that gem i have to keep it 😁
hmm okay.
I gave him now?
Now what should i do? Give you a vase? 🤣🤣🤣
Please 😭🙏🏻🙏🏻🙏🏻🙏🏻
give me gem bruhh forgive her.
I am looking for a good comment of yours
okay 👌
Did you get anything?
just recieved a badge ❌ a aura ✅
Okay guys! Bye have a good day!❤️
Forgive me 😭🤣🤣🤣🤣🤣🤣
@effessdev I don’t even make this many mistakes.
Yeah 😥😅👍🏻
@star123 you mean the vase? 😂 That'd have cost a 10k rank in JEE Adv if she made it back then 🤣🤣
😒
@akhourianmolkumar I am REALLY sorry I had to leave because of an emergency 😭
where?
I had to get off the bus so I could go home.
hmm ok when you will be home come online in discord and say HI I will be online there and also bro stop that vase stuff she will get angry of you.
Ok ok no more vase from now on BUT I CAN'T STOP BECAUSE YOU GUYS KEEP MENTIONING IT 🤦🤦🤦
My colleague was saying something to me at that time, so I sent the comment without checking it. 😅
wait hold on you are in college in evening what are you doing lol does IIT have 10-12hours of college just asking because maybe in future I've to be there too.
College? Kaun?
bro she just mistakenly said vase
mistaking "vase" for a "gem" how's that possible 🤔 Whatever...
bro but where are you going you said emergency
I mean gem lol.
So gem and vase are same?
effessdev bro you've gem right? so I will give you 1 gem you will give me 1 gem so I get 2 gem (my math is not mathing)
Will that work? 🤔
bro ask from didi I don't know lol my math is not mathing
Try
try ☠️
Mindset📈📈📈
taunt or genuine respect?
😂
Huge Respect 💪🏻💯
Thanks 💯
Solid breakdown, especially the key commitment section. Most write-ups skip the partitioning-oracle angle entirely.
One question on the chunked format: index-in-nonce plus header-bound AAD stops reordering, but truncation only gets caught if the chunk count is authenticated or the final chunk carries a distinct flag. Which one does v3.1 use? If it’s neither, dropping trailing chunks would verify cleanly.
Also worth considering: since the release is an unsigned PyInstaller exe, publishing hashes plus a reproducible build recipe would let people verify that the binary matches the readable source you describe.
I do reverse engineering and crypto implementation reviews, so I’m happy to dig into the container parser if that would help.
hey Yoshiyuku bro, thaanks a lot man, realy appreciate you reading it this carefully
on truncation v3.1 uses both actualy. plain_len sits in the header and its covered by header_hash, and that header_hash goes into the AAD of every chunk along with the chunk index and a 1 byte is_final flag (aad = header_hash || idx || is_final). so if someone drops trailing chunks the decryptor already knows how many bytes to expect, and a chunck that isnt supposed to be last cant pass as final cuz the flag is authenticated. theres also a check for written != plain_len and for extra data after the last chunk, so cutting the tail gives a truncated container error and never a clean verify
Good.
Let's keep connection.
cool, I prefer to make Bros not connections
so Bros? 🤝?
As your mind.
I like build relationship with engineers.
If you are interested on me, ping me.
@effessdev bro speak something or you are becoming xulingfeng? like just reading discussions 😂
Speak what? 🤔
hmm no talk left?
Give me 30-60 minutes...
woahh for whatt to find new talks 😂
Some comments may only be visible to logged-in visitors. Sign in to view all comments.