From your first print('Hello') to Argon2id, key commitment, rollback-proof state, Shamir secret sharing and ransomware decoys. Every idea explained, with the real code.

Who this post is for

This post has two readers in mind.

Reader A just typed print('Hello, world!') for the first time and is wondering what all the fuss about "cryptography" is.

Reader B writes code for a living and wants to know exactly how a serious, single-file security tool is put together: where the keys come from, what the file format looks like, and what happens when the power dies halfway through encrypting your only copy of a file.

I wrote it so both can finish it. Part 0 is gentle. From Part 2 onward it gets deep. Everything below comes from the real source of ATLOCK v5, an Akhouri Systems product by Akhouri Anmol Kumar(Me): one Python file, 10,011 lines, Windows-first.

Part 0: The five-minute foundation

Python in one paragraph

Python is a programming language designed to read almost like English. This is a complete program:

print('Hello, world!') 

That is the same language ATLOCK is written in. The only difference is scale. Python is used to build websites (Django, Flask), analyse data, train AI models, automate boring office work, write games, and build desktop apps. It is also very good at security tooling, because the heavy lifting is done by fast libraries written in C and Rust while Python glues everything together.

ATLOCK is a desktop application with a graphical interface (built with customtkinter) and a command-line tool, all in one .py file packed into (.exe) & Shipped as (.zip)

What is cryptography?

Cryptography is the craft of making information useless to anyone who is not supposed to have it, and provably unaltered for those who are. Modern cryptography makes three promises:

Promise Plain English Real-world picture
Confidentiality Outsiders cannot read it A locked box
Integrity Nobody changed it without you noticing A wax seal on the box
Authenticity It really came from who it claims A signature

Here is the smallest real example, using the same cipher ATLOCK uses (AES-256-GCM):

import os from cryptography.hazmat.primitives.ciphers.aead import AESGCM key = AESGCM.generate_key(bit_length=256) # 32 random bytes = the secret nonce = os.urandom(12) # a one-time number; never reuse with the same key box = AESGCM(key) sealed = box.encrypt(nonce, b'hello', b'label') # b'label' = extra data that must match print(box.decrypt(nonce, sealed, b'label')) # b'hello' 

Flip a single bit of sealed and decrypt raises InvalidTag instead of returning garbage. That is integrity built into the cipher. Ciphers with this property are called AEAD (Authenticated Encryption with Associated Data), and nearly every design decision in ATLOCK is about using AEAD correctly.

The vocabulary you need (and no more)

Term Meaning Why it matters here
Plaintext / ciphertext Readable data / scrambled data Your file before and after locking
Key The secret number that locks and unlocks 32 bytes for AES-256
Salt Random bytes mixed into password hashing Stops attackers re-using precomputed tables
Nonce A number used once per encryption Re-using one with the same key breaks GCM
Hash (SHA-256) A one-way fingerprint of data Detect changes; cannot be reversed
MAC / HMAC A keyed fingerprint Proves someone with the key made it
KDF Turns a human password into a strong key, slowly on purpose Argon2id
Signature Proof made with a private key, checked with a public key Ed25519

The golden rule: never invent your own cipher. ATLOCK does not. It calls vetted primitives from the cryptography and argon2-cffi packages: AES-256-GCM, Argon2id, HKDF, HMAC-SHA256, X25519 and Ed25519. The original work is in how they are assembled, and the rest of this post is about that assembly. (Two things are written by hand, because the specs are small and public: TOTP codes and Shamir secret sharing. We will look at both.)

What is a "security suite"?

An antivirus does one job. A security suite bundles many defensive tools behind one front door. ATLOCK's suite covers:

  • File Guard: encrypt files and whole folders in place, with lockout rules.
  • Vault: a password manager with import from Bitwarden, Chrome, Firefox, KeePass and 1Password exports.
  • Security Guard: a Windows Defender dashboard, VirusTotal scanning, a startup watchdog, ransomware decoy files, and a device-health check.
  • Intruder response: photo/video capture and Telegram alerts after wrong attempts.
  • Public-key tools: encrypt a file to a friend, sign files, verify releases.
  • A big command line (--selftest, --fuzz, --shamir-split, --pk-encrypt and about forty more flags).

ATLOCK deliberately does not reimplement an antivirus engine. Its own comment says it gives Defender's real engine and Windows' audit trails "a cockpit". Knowing what not to build is a security skill too.

Part 1: ATLOCK from 30,000 feet

The file is organised in lettered sections:--

Layer What it does Key functions / classes
Bootstrap DLL-planting defence, crash tracing, dependency check _atl_trace, _atl_fatal, check_dependencies
Crypto core Password to key, chunked authenticated container atlock_kdf, atlock_seal_stream_key, _v3_chunks
Safe I/O Atomic writes, shred, crash-safe swap atlock_atomic_write, atlock_shred, _swap_and_shred
Sealed state Encrypted, rollback-proof local database SealedStore
Lockout engine Backoff, hard locks, clock-tamper resistance atl_lock_fail, atl_eff_now
Factors TOTP, FIDO2, Windows Hello, keyfile atlock_totp_verify, atl_bind_secret
Recovery Recovery keys, codes, Shamir M-of-N atl_shamir_split, atl_shamir_combine
Public-key mode X25519 encryption, Ed25519 signatures atl_pk_encrypt_file, atl_pk_sign_file
Defence tools Canary, watchdog, Defender, intruder ops RansomwareCanary, StartupWatchdog
UI Panels built on customtkinter FileGuardPanel, VaultPanel, ATLockApp
Self-test Dozens of checks plus a fuzzer atl_selftest, atl_fuzz

The design philosophy is visible in one sentence from the module docstring: "the cryptographic, storage and anti-tamper core underneath has been rebuilt" while the UI stayed identical. In other words, v5 is a secure core transplant, not a repaint.

Part 2: From password to key

Why you cannot just hash a password

Suppose your password is sunshine42. You could run SHA-256 on it and use that as the AES key. This is a famous mistake. SHA-256 is fast, and fast is the attacker's friend: a GPU can try billions of guesses per second.

A password-based KDF is deliberately slow and, in the best designs, deliberately memory-hungry, so that GPUs and custom chips lose their advantage. ATLOCK uses Argon2id, the winner of the Password Hashing Competition and the current standard recommendation.

The floor parameters in the source:

_ATL_ARGON2 = dict(time_cost=3, memory_cost=65536, parallelism=4, hash_len=32) # 64 MiB _ATL_ARGON2_STRONG = dict(time_cost=4, memory_cost=131072, parallelism=4, hash_len=32) # 128 MiB (vault, recovery) def atl_argon2_raw(secret: bytes, salt: bytes, params: dict) -> bytes: return hash_secret_raw(secret, salt, type=Type.ID, **params) 

Every guess costs an attacker 64 to 128 MiB of RAM and several passes over it. That is the whole point.

Tuning to the machine

atl_benchmark_argon2() times one hash on your computer and scales the memory cost up (never below the floor) until a single hash takes about 0.6 seconds, capped at a fraction of your RAM:

scale = max(1.0, min(4.0, target_seconds / dt)) params['memory_cost'] = int(min(mem_cap, max(floor, floor * scale))) 

A fast PC gets stronger protection automatically; a weak laptop never gets less than the floor. The parameters used are written into each file's header, so the file can always be reopened later even if you buy a faster machine.

A header you cannot weaponise

Here is a subtle attack. If the file header says how much memory Argon2 should use, an attacker can edit the header to demand 500 GB and freeze your computer, or demand almost nothing to weaken the key derivation. ATLOCK defends in both directions:

KDF_MAX = dict(time_cost=6, memory_cost=524288, parallelism=8) # ceilings for UNTRUSTED headers ... if kp['memory_cost'] > KDF_MAX['memory_cost'] ...: raise AtlockDamaged('This container asks for more Argon2 work than ATLOCK ever produces...') kp['memory_cost'] = max(_ATL_ARGON2['memory_cost'], kp['memory_cost']) # and floors 

Ceiling stops denial of service. Floor stops downgrade. Treat every number read from an untrusted file as hostile; this is a recurring theme in ATLOCK.

One password, many independent keys (HKDF)

Argon2 yields one 32-byte "master" secret. ATLOCK never uses that directly. It expands it with HKDF using a different label for every purpose:

def atlock_kdf(secret, salt, params=None): master = atl_argon2_raw(atl_secret_bytes(secret), salt, params or _ATL_ARGON2) def sub(info): return HKDF(algorithm=hashes.SHA256(), length=32, salt=None, info=info).derive(master) return sub(b'atlock/file-enc/v3'), sub(b'atlock/file-verify/v3') # (enc_key, verifier) 

This is called domain separation. The encryption key and the verifier come from the same master but cannot be converted into each other. The same trick yields separate keys for the vault, recovery wrap, state MAC, audit log and TOTP seed wrap. If one key leaks, the others stay safe.

One more detail that saves real users from real pain: atl_secret_bytes runs the password through Unicode NFC normalisation, so é typed as one character or as e + accent produces the same key on any keyboard.

Part 3: The container, byte by byte

A locked file is not just "ciphertext". It is a structured container. ATLOCK's current format is v3.1, with a fixed 114-byte header:

Bytes Field Purpose
8 magic ATLOCK\x03\x01 Identify the format version
16 salt Random, for Argon2
6 KDF params time (1) + parallelism (1) + memory KiB (4)
4 chunk size Big-endian; default 8 MiB
8 nonce base Random per seal
32 key commitment Proves which key this file belongs to
8 plaintext length Detect truncation
32 header hash SHA-256 of everything above

After the header come the chunks: chunk_len (4 bytes) | AES-GCM(chunk), repeated.

Why chunks?

If you encrypted a 6 GB video in one piece you would need 6 GB of RAM and could not verify anything until the very end. Chunking (8 MiB each) means ATLOCK holds one chunk in memory at a time, can show a progress bar, and can fail fast.

But chunking creates new attacks. An adversary who cannot read your data could still reorder, drop, duplicate or truncate chunks. Each chunk is individually valid, so a naive design would happily decrypt a scrambled file. ATLOCK's answer is to bind position into every chunk's associated data:

nonce = nonce_base + struct.pack('>I', idx) # unique per chunk aad = header_hash + struct.pack('>I', idx) + (b'\x01' if is_final else b'\x00') ct = aes.encrypt(nonce, chunk, aad) 

Now chunk 7 only authenticates as "chunk 7 of this file". Swap two chunks, delete one, or chop the end off and the final-flag no longer matches: InvalidTag. Because the header hash is inside every AAD, even editing the header invalidates every chunk.

Key commitment: a subtle one

AES-GCM has a quirky weakness: in rare, crafted situations one ciphertext can decrypt successfully under two different keys. To be safe, ATLOCK stores a commitment in the header, HMAC-SHA256(enc_key, 'atlock-commit-v3'), and checks it before releasing a single byte of plaintext:

if not hmac.compare_digest(_commit(enc_key), h['commitment']): raise AtlockAuthError('Wrong password, or this file was made with a different key.') 

Two benefits: a wrong password is reported cleanly (not as scary "corruption"), and the key-commitment attack class is closed. Note hmac.compare_digest: a constant-time comparison, so timing differences cannot leak how many bytes matched.

v3.1: a fresh subkey for every seal

GCM's one hard rule is never repeat a (key, nonce) pair. ATLOCK re-seals working copies of files again and again. With a random 8-byte nonce base this would be probably fine, but "probably" is not a cryptographic standard. So v3.1 derives a per-seal key:

def _v3_subkey(enc_key, nonce_base): return hmac.new(enc_key, b'atlock-chunk-v3.1|' + nonce_base, hashlib.sha256).digest() 

Every seal encrypts under a brand-new subkey, so a nonce collision across seals cannot hurt.

Hiding the size: Padmé padding

Even encrypted, a file's length leaks information (a 3 KB file is probably not a movie). An optional setting pads containers to a Padmé bucket, which hides the size with at most about 12% overhead, using random filler bytes that are checked on open so padding cannot be abused to smuggle data.

def atl_padme(n): e = n.bit_length() - 1; s = e.bit_length(); last = e - s if last <= 0: return n mask = (1 << last) - 1 return (n + mask) & ~mask 

Three lines of bit twiddling replace "round to the nearest power of two" (which can double your size) with something far cheaper.

Part 4: Never lose a file: crash-safe encryption

The scariest moment in any encryption tool is the instant it replaces your original. Power cut, antivirus lock, disk full: any of them could destroy the only copy. ATLOCK's in-place locking (atlock_seal_file) is a careful little state machine:

  1. Hash the original (SHA-256) and remember its size and modification time.
  2. Encrypt to a temporary file next to the original, then fsync so it is really on disk.
  3. Read it back from disk and decrypt it while hashing. If the hash differs from step 1, abort. The original is untouched.
  4. Check the original did not change during encryption (size and mtime_ns). If you edited it meanwhile, refuse rather than shred your newer edits.
  5. Record the key info first. The on_ready callback writes the database record before the swap, so there is never ciphertext without a record of how to open it. If the swap fails, on_abort rolls the record back.
  6. Swap atomically: move the original aside to .atlock_old, move the container into place.
  7. Shred the old plaintext.

The swap function's own comment states the invariant best: "A crash between steps leaves BOTH files, never neither." On the next start, atlock_recover_leftovers() finishes the job.

The shredder is intentionally modest and honest:

for p in range(passes): f.seek(0) ... f.write(b'\x00' * n if p == 0 else secrets.token_bytes(n)) # zero pass, then random pass f.flush(); os.fsync(f.fileno()) os.replace(path, '.' + secrets.token_hex(8) + '.shred') # scrub the file name too os.remove(...) 

It also refuses to overwrite through a symlink or Windows junction (atl_is_reparse): shredding "through" a link could destroy a file outside the folder you chose. Even folder walking is careful, because os.walk happily descends into junctions.

And the honest caveat from the source: on SSDs, journaling and copy-on-write filesystems, overwriting cannot be guaranteed to destroy old blocks. BitLocker full-disk encryption is the real fix, and ATLOCK's Device Health check tells you whether it is on.

Part 5: A database an attacker cannot rewind

ATLOCK keeps settings, the vault, guard records, lockout counters and an audit log on disk. Everything lives in SealedStore, and its design answers a question most apps never ask: what if the attacker doesn't break the encryption, but simply puts an old copy of the file back?

Layer 1: encrypted and name-bound

Each state file is AES-256-GCM encrypted, and the file's name is bound into the AAD:

AESGCM(self.derive('state-enc')).encrypt(nonce, data, b'ATLOCK-STATE|' + name.encode()) 

So an attacker cannot rename settings.atl over vault.atl: authentication fails.

Layer 2: where does the key live?

The state key is generated randomly and wrapped with Windows DPAPI, which ties it to your Windows account. Optionally, a second secret held by the TPM (the chip on your motherboard) is mixed in via _hw_mix. If the TPM is later cleared, ATLOCK fails closed instead of quietly falling back to weaker protection, and tells you exactly why. The source admits the limit plainly: DPAPI defends against another user account or a stolen disk, but not against other software already running as you. That is precisely the gap the TPM option narrows.

Layer 3: the rollback problem

Imagine you change your vault password because it leaked. An attacker who earlier copied your old vault.atl can later put it back. Every file is validly encrypted, so the old master password works again. This is a rollback attack.

ATLOCK's defence is a monotonic counter:

  1. Every write stores _ctr inside the encrypted payload and increments it.
  2. The latest counter per file is stored as an anchor in two independent places: a DPAPI-sealed anchor.bin and a DPAPI-sealed registry value.
  3. On read, if the file's counter is lower than the anchor, it is a rollback.
rolled_back = ctr < a if rolled_back and name in self.ROLLBACK_FAILCLOSED: # ('vault', 'guard', 'settings') self._quarantine_locked(name) # moved aside, NOT deleted ... hard-lock 'global' ... return default # never serve the stale copy 

The stale file is quarantined (so a legitimate restore from backup is still possible via an explicit accept command) and the whole app gets a penalty lock. Deleting a sealed file that is known to have existed also fails closed, and for the lockout record specifically, deletion triggers a hard lock rather than a convenient reset to "zero failed attempts". The pattern is: absence of evidence is treated as evidence of tampering.

Cross-process safety

With a GUI, a background agent and a right-click handler all touching the same files, ATLOCK uses a cross-process lock (_XProcRLock) and a single-instance guard (atl_single_instance). Atomic writes (atlock_atomic_write: write temp, fsync, os.replace) mean a crash mid-save leaves either the old or the new file, never half of each, and an .atl.bak copy is kept as a fallback.

Part 6: Wrong guesses cost time, and time cannot be cheated

Offline attacks against your files are limited by Argon2. But the app itself can be attacked online (someone sitting at your PC typing guesses), so ATLOCK has a lockout engine with per-subsystem policies:

# free, hard_at, hours, delay_base, delay_cap LOCK_POLICIES = { 'vault': (1, VAULT_LOCKOUT_TRIGGER, LOCK_HOURS, 4, 60), 'file': (1, MAX_FILE_ATTEMPTS, LOCK_HOURS, 3, 30), 'pin': (0, MAX_FILE_ATTEMPTS, 24, 10, 120), # tiny keyspace => harsher 'recovery': (2, 6, 6, 30, 900), 'totp': (2, 5, 1, 5, 120), ... } 

The defaults in the file are MAX_FILE_ATTEMPTS = 3, VAULT_LOCKOUT_TRIGGER = 4 and LOCK_HOURS = 10. Between free attempts and the hard lock, delay grows exponentially with random jitter (+0..25%, from secrets.randbelow). Hard locks double with each repeat strike, capped at 336 hours (two weeks).

Two design choices deserve applause:

Failures never decay. The code comment explains: otherwise a patient attacker could try N guesses, wait, and repeat forever without reaching the hard lock. Only a real authenticated unlock clears the counter.

The clock cannot be used as a weapon. The obvious attack on any timed lockout is: set the system clock backwards. ATLOCK defends with several mechanisms at once:

  • atl_eff_now() returns max(wall clock, highest wall time ever seen), persisted sealed. Time can only move forward, and a backwards jump is recorded as tamper.
  • Lock remaining time is computed as max(remaining by wall clock, remaining by monotonic clock). To shorten a wait, an attacker must defeat both.
  • A monotonic clock restarts at reboot, which would wrongly revive old locks. So ATLOCK stamps each record with the Windows boot ID (read from the Prefetch BootId registry counter, no admin rights needed) and only trusts monotonic values from the same boot.

Finally, tamper tightens the rules: if the store has flagged tampering, hard_at drops by one, so the attacker gets fewer guesses.

Part 7: A tamper-evident diary

atl_audit appends events to a log in which every entry carries an HMAC of the previous entry plus its own content:

e['h'] = hmac.new(st.derive('audit-hmac'), (prev + json.dumps({...}, sort_keys=True)).encode(), hashlib.sha256).hexdigest() 

This is a tiny cousin of a blockchain: edit, insert or delete any event in the middle and every later hash breaks. --audit-verify walks the chain and reports the first broken event number. Callers pass masked data only; even a wrong password attempt is stored as •••• (9 chars), never the characters, never a hash.

The same philosophy covers self-integrity: ATLOCK hashes its own source file and compares it to a baseline stored sealed and in the registry. And the file's docstring is candid: a same-user attacker can edit both, so this "raises the cost" and catches accidents and commodity malware, nothing more.

Part 8: Second factors, written from the RFC

TOTP (the 6-digit codes in your authenticator app)

ATLOCK implements RFC 6238 in about fifteen lines:

def _hotp(secret, counter, digits=6): h = hmac.new(secret, struct.pack('>Q', counter), hashlib.sha1).digest() offset = h[-1] & 0x0F code = (struct.unpack('>I', h[offset:offset + 4])[0] & 0x7FFFFFFF) % (10 ** digits) return str(code).zfill(digits) # counter = int(time.time() // 30) 

(SHA-1 here is mandated by the spec for compatibility with Google Authenticator and friends; HMAC-SHA1 remains secure for this use.) It accepts one 30-second step either side for clock drift, compares in constant time, and keeps a set of used counters so an intercepted code cannot be replayed inside its validity window. The built-in self-test verifies the official RFC vector: at T=59, 8 digits, the answer must be 94287082. Testing against published test vectors is how you know hand-written crypto-adjacent code is right.

FIDO2 hardware keys and Windows Hello

With the optional fido2 package, a YubiKey-style device can act as a second factor, with a 60-second timeout, a cancel dialog, and a cached result so one touch covers verify and decrypt. Windows Hello (face, fingerprint, PIN) is reached through winrt. Several auth policies exist (password, password+hello, password+totp, ...), and any second-factor failure fails closed.

The extra lock that is part of the key

Most "2FA" is only a gate: the app checks your code, then opens the file. A gate can be bypassed by anyone who skips the app. ATLOCK's File Guard offers a keyfile or hardware key that is mixed into the cryptographic key itself:

def atl_bind_secret(secret, factor: bytes) -> str: mac = hmac.new(factor, b'atlock-factor-bind-v1|' + atl_secret_bytes(secret), hashlib.sha512).digest() return base64.urlsafe_b64encode(mac).decode('ascii'<...
Source:https://dev.to/akhourianmolkumar/atlock-v5-inch-by-inch-how-a-10000-line-pure-python-security-suite-actually-works-4kb2