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: one Python file, 10,011 lines, Windows-first.
- Microsoft Store: https://apps.microsoft.com/detail/9P08S539NTLF
- Source repo: https://github.com/Akhouri-Systems/ATLOCK
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-encryptand 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:
- Hash the original (SHA-256) and remember its size and modification time.
-
Encrypt to a temporary file next to the original, then
fsyncso it is really on disk. - Read it back from disk and decrypt it while hashing. If the hash differs from step 1, abort. The original is untouched.
-
Check the original did not change during encryption (size and
mtime_ns). If you edited it meanwhile, refuse rather than shred your newer edits. -
Record the key info first. The
on_readycallback 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_abortrolls the record back. -
Swap atomically: move the original aside to
.atlock_old, move the container into place. - 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:
- Every write stores
_ctrinside the encrypted payload and increments it. - The latest counter per file is stored as an anchor in two independent places: a DPAPI-sealed
anchor.binand a DPAPI-sealed registry value. - 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()returnsmax(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
BootIdregistry 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') # THIS is what is fed to Argon2
The keyfile's SHA-512 digest (or a FIDO2 hmac-secret output) keys an HMAC over your password. The result goes into Argon2. Without the factor, a copied container is mathematically unopenable even if the attacker knows your password. This is the difference between a lock on the door and a lock on the vault.
Part 9: Shamir's Secret Sharing: split a key so no single paper matters
Your recovery key on one sheet of paper is a single point of failure: lose it and you are locked out, leak it and you are open. Shamir's Secret Sharing splits a secret into N shares so that any K of them rebuild it, while K-1 shares reveal nothing (not "little", literally nothing).
The idea is school algebra. A line is fixed by two points; a parabola by three. Hide the secret as the intercept of a random polynomial of degree K-1, hand out points on it. ATLOCK works byte-by-byte over the finite field GF(256), the same field AES uses, with the same reduction polynomial 0x11B:
# build log/exp tables once, using generator 3
x2 = x << 1
if x2 & 0x100: x2 ^= 0x11B
x = x2 ^ x
# share for point x: evaluate each byte's polynomial with Horner's rule
for a in reversed(coeffs): y = _gf_mul(y, x) ^ a
Recombination is Lagrange interpolation at zero, again in GF(256). Look at how carefully the shares are built:
- Each share looks like
ATLS2-3OF5-02-A1B2C3-<base32 data>-<CRC16>: human-friendly Base32, and a CRC-16 that catches a typo and says which share has the typo. - A random set ID prevents mixing shares from different splits.
- A 4-byte integrity tag is appended to the secret before splitting, so it is shared too. The source records why: the older ATLS1 format stored a hash of the secret in every share, which leaked 24 bits to anyone holding shares. The fix is documented in a comment, which is exactly how mature security code should evolve.
The CLI exposes it as --shamir-split and --shamir-join, and the garbage-in path is fuzz-tested.
Part 10: Public-key mode: encrypt to someone who is not in the room
Passwords need both people to share a secret first. Public-key cryptography removes that: you publish a public key, keep a private key, and anyone can encrypt to you, but only you can decrypt. ATLOCK's identity is two keypairs: X25519 (for receiving) and Ed25519 (for signing). The private halves are stored in identity.atlkey, wrapped with Argon2id (the strong profile) and AES-GCM under your passphrase. Your shareable key looks like ATLPUB1:... with a built-in checksum and a short fingerprint you can read aloud over the phone.
Encrypting to a public key (the ECIES pattern)
eph = x25519.X25519PrivateKey.generate() # throw-away key, one per file
shared = eph.exchange(recipient_public_x25519) # Diffie-Hellman
key = HKDF(SHA256, length=32, salt=eph_pub + recip_x, info=b'atlock-pk-v1').derive(shared)
# then reuse the SAME chunked container as for passwords:
atlock_seal_stream_key(fin, fout, key, ...)
The sender and recipient both arrive at the same shared secret without ever sending it. A fresh ephemeral key per file means that compromising one file doesn't expose others. And notice the reuse: the public-key mode doesn't invent a new file format. It derives a key a different way and feeds the same battle-tested container.
Signing, and the "pinned sender" policy
If you pass --sign, an Ed25519 signature over the file is embedded. On decrypt you can pin the sender's key. This is a policy, and it fails closed:
if expected_sender_pub and not signed: raise AtlockSenderMismatch('...Nothing was decrypted.')
if signed and not matches: raise AtlockSenderMismatch('SENDER MISMATCH...Nothing was decrypted.')
The check happens before any plaintext is produced. Standalone --sign FILE writes a .atlsig JSON file; --check-sig verifies it, and distinguishes "mathematically valid but a different key than you expected" from "invalid". That nuance matters: a valid signature from the wrong person is still an attack.
Verifying ATLOCK itself
A security tool you cannot verify is a liability. --verify-self hashes the script (with the one line that holds the publisher's public key blanked, so the key can be embedded without changing what is signed) and checks an Ed25519 signature against a pinned publisher key. If the signature is valid but the key is not the one you trust, it says so and refuses to claim verification. That is how you stop a perfectly signed malicious copy.
Part 11: Passwords, honestly
All that Argon2 cost is wasted if your password is Anmol@123. So ATLOCK includes its own pattern-aware strength estimator (atlock_password_strength) instead of the usual "one uppercase, one symbol" rules, which famously rate Passw0rd! as strong.
It solves the password as a minimum-cost decomposition (dynamic programming): the cheapest way an attacker's guesser could produce your string from known building blocks.
- dictionary words, names and leaked passwords, with case and leet-speak (
p4ssw0rd) surcharges, - sequences (
abc,4321), repeats (aaaa,abcabc), keyboard walks (qwerty), years and dates, - pronounceable "text" that Markov guessers rank early,
- and only then, plain brute-force characters.
The score is in bits of entropy, and policy is explicit: file passwords need about 40 bits (MIN_FILE_BITS), vault master and backup passwords 50 (MIN_VAULT_BITS). The built-in generator (--gen-passphrase) draws words from a bundled 2,048-word list using secrets.choice: 11 bits per word, so 5 words give 55 bits and remain typeable by a human.
Optionally it also checks the Have I Been Pwned database using k-anonymity: only the first 5 hex characters of the password's SHA-1 ever leave your machine; the service returns every suffix in that bucket and the comparison happens locally. You learn if the password is breached without telling anyone what it is.
Part 12: The defence tools
Ransomware canaries
Ransomware encrypts every file it can reach, including files you never open. RansomwareCanary exploits that. It plants decoy files with random realistic names across Desktop, Documents, Pictures and Downloads, each holding a unique hidden token, and records their SHA-256. A thread polls every 4 seconds. One edited decoy could be you; the alert fires only when two different decoys change inside a 20-second window. Tripped decoys are re-armed with new random names. Cheap, low-noise, and effective against a class of malware that antivirus signatures may not recognise yet.
Startup watchdog
Malware needs to survive reboot. StartupWatchdog baselines every classic persistence location, then reports only what is new: Registry Run/RunOnce (user and machine), Startup folders, auto-start services, scheduled tasks, AppInit_DLLs, Image File Execution Options debugger hijacks, and Winlogon Shell/Userinit, with SHA-256 and Authenticode signature status where reachable.
Intruder response
A wrong attempt can trigger a sound, a camera photo, and, at critical level, a short video plus a Telegram alert (uploads capped at 49 MB to respect Telegram's limit, with captured media then encrypted). There is also a silent duress path: no alarm sound, so the intruder isn't warned.
Hardening the process itself
At the very top of the file, before almost any import, ATLOCK calls SetDllDirectoryW('') and SetDefaultDllDirectories to defeat DLL planting (where malware drops a fake DLL next to the program). Later it enables heap-corruption termination and an image-load mitigation policy, and atl_debugger_present() checks four independent debugger indicators. Secrets can sit in a SecureBuffer: memory locked with VirtualLock (not swapped to disk) and zeroed with RtlSecureZeroMemory. The docstring is again honest: Python may already have copied your password; this is best-effort.
Defensive parsing
Folders are locked using a custom streaming format (AFA1): a sequence of records, each file followed by its SHA-256, ending in a manifest, so a 100 GB folder never needs 100 GB of RAM. Opening archives is guarded against zip bombs (a compression ratio cap of 300, a 200,000-entry cap, an 8 GiB cap) and path traversal (_atl_zip_safe_name). A malicious archive should be rejected before it can hurt you.
Part 13: How you know it works: self-test and fuzzing
The most convincing part of ATLOCK is that it ships its own proof. --selftest runs dozens of checks: round-trip encryption, wrong key rejected before any plaintext, chunk tampering detected, the RFC TOTP vector, recovery-key round trip, DPAPI round trip, zip-slip rejection, and more.
Then there is --fuzz. Fuzzing means attacking your own parser with random garbage to see if it ever crashes or, worse, silently returns wrong data:
kind = rnd.choice(('flip', 'flips', 'trunc', 'extend', 'zero', 'chunkdup', 'swap', 'header'))
...
try:
atlock_open_stream_key(io.BytesIO(mutated), out, key)
if out.getvalue() != data: bad.append('SILENT CORRUPTION')
except AtlockError: pass # clean, expected rejection
except Exception as e: bad.append(...) # anything else is a bug
The rule it enforces is the heart of authenticated encryption: every mutated container must either decrypt to exactly the original or fail with a clean AtlockError. A crash is a bug; silent corruption would be a vulnerability. The same harness feeds garbage Shamir shares and junk password-manager exports to their parsers, and the RNG is seeded, so any failure is reproducible.
What ATLOCK does not claim (and why that is a strength)
The top of the source lists limits plainly. In my words:
- A same-user attacker who can run code as you can modify the script and read process memory.
- Python strings are immutable and copied freely, so wiping secrets from RAM is best-effort.
- Secure deletion on SSDs and snapshotted filesystems cannot be guaranteed; use BitLocker.
- A determined attacker who can reboot, roll the clock back and delete every anchor could still reset a counter. The real defence against offline guessing is Argon2id plus a strong password.
- Nothing is code-signed and there is no kernel driver; it is application-layer.
- Quick Lock is an application-level gate, not password-strength cryptography. For that, use File Guard with a password.
What you can steal for your own projects
Even if you never run ATLOCK, these patterns generalise:
- Derive, don't reuse: one master secret, many HKDF-labelled subkeys.
- Bind context into AAD: file name, chunk index, final flag, header hash.
- Validate before you decrypt: commitments and size ceilings first.
- Verify before you destroy: read the new file back from disk before touching the original.
- Fail closed: missing or stale state is a signal, not a default.
- Treat time as hostile: monotonic clock, highest-seen wall clock, boot ID.
- Test with vectors and fuzz: RFC vectors for known answers, mutations for unknown ones.
- Write your limits down: your users deserve to know them.
Try it yourself in ten lines
# pip install cryptography argon2-cffi
import os
from argon2.low_level import hash_secret_raw, Type
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
salt = os.urandom(16)
key = hash_secret_raw(b'correct horse battery staple', salt, time_cost=3,
memory_cost=65536, parallelism=4, hash_len=32, type=Type.ID)
nonce = os.urandom(12)
blob = salt + nonce + AESGCM(key).encrypt(nonce, b'my first secret', b'v1')
# later: same password + the salt stored at the front of the blob
key2 = hash_secret_raw(b'correct horse battery staple', blob[:16], time_cost=3,
memory_cost=65536, parallelism=4, hash_len=32, type=Type.ID)
print(AESGCM(key2).decrypt(blob[16:28], blob[28:], b'v1')) # b'my first secret'
That is a toy: one chunk, no commitment, no crash safety, no lockout. Now you know exactly what ATLOCK adds on top of it, and why each piece exists.
Get ATLOCK
- Microsoft Store: https://apps.microsoft.com/detail/9P08S539NTLF
- (GitHub): https://github.com/Akhouri-Systems/ATLOCK
Top comments (28)
Broooo! THE post i have been waiting for 🤯
yeaaaaaaa ☠️
@effessdev in APIC v2 I'm adding layers, masks, broader file support, HSL, retouching tools phase by phase 🔥
💀
Selections?
APIC WEB: 🔥🔥
APIC v2: 💀
yeah selections too
👍
What about that magic wand thing?
bro bro wait wait 😂 that's in the list for sure but not in current development phase program.
bro should I add that button support me like xulingfeng have but I don't have paypal or stripe so that will be a kinda button carrying my fampay id means only indians can support.
Bro but what will be our selling point against GIMP and Affinity? It looks like its turning into a generic photo editor.
I also want paypal but my PAN is e-PAN lol. So i cannot complete the KYC verification. Thats why payments still aren't active in MmOrganized.
What project are you working on, @effessdev bro?
he is offline didi I guess
@technogamerz Currenty ReptClip. After that, i will work on esp-idf-reference using it. When are you starting your company didi?
I think after she complete her MBA
bruh after MBA?
maybe she's going to start BIG
yeah she earlier said me she is pursuing MBA lol even after IIT she wants a one more degree but maybe because she is serious for her company
yeah and then she will fund akhouri systems in billions and I will partner with her company and then as CTO you'll have stakes definitely so it means we 3 will be in forbes... also if you start FS SYSTEMS along side being a CTO then you can be more rich and maybe top 100 in forbes list but still in top 200 or maybe not sure but in top 400 there is no IITian so let's see maybe didi became 1st IITian and I became first under 18, and you became the world's most powerful CTO as you will be the CTO of the one of the most biggest company akhouri systems also if you revived FS SYSTEMS so you can be more powerful
👆 peak unemployed random imaginations in my mind for no reason.
🤣🤣🤣
Honestly i am more into open source rather than company stuff lol. And working with other companies like yours! Lets see what didi's going to do 👀
youtube.com/shorts/neY98b-8aNw
"dead economy" ☠️☠️☠️☠️ 🔥🔥🔥
Because of population? 🤔
Opening a company is still just a dream for now 😂 but who knows? I've already completed the planning, and maybe I'll start something even before my MBA second year. I already has a firm, so I've gained a little experience too.
And yeah, open source is a great option, bro! But who knows, maybe you'll build something that none of us have even imagined yet. 😎
And yeah, India has a huge population, but I can confidently say there's a lot of talent here too. Btw, I bought gold for my mom for the first time, and I'm genuinely so happy about it.
just reminding you we've donation button in akhouri systems website
🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣 just kidding
Par abhi toh saare paise kharch ho gaye!😔
lol 😂 I was just joking hahahaha
Bhai yah dekho.
😂
Uh I wanted to see 10,000 lines lol
next time i will do ctrl+c 10k loc and then ctrl+v to dev.to post 😂
And DEV.to will be broken again 🤣🤣
also they will introduce then a new badge: LONGEST POST OF THE WEEK
🤣