DEV Community

Cover image for ATLOCK v5, Inch by Inch: How a 10,000-Line Pure-Python Security Suite Actually Works
Akhouri Anmol Kumar
Akhouri Anmol Kumar

Posted on Edited on AI-assisted

ATLOCK v5, Inch by Inch: How a 10,000-Line Pure-Python Security Suite Actually Works

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.


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!')
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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.')
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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(...)
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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),
    ...
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

(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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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, ...)
Enter fullscreen mode Exit fullscreen mode

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.')
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Derive, don't reuse: one master secret, many HKDF-labelled subkeys.
  2. Bind context into AAD: file name, chunk index, final flag, header hash.
  3. Validate before you decrypt: commitments and size ceilings first.
  4. Verify before you destroy: read the new file back from disk before touching the original.
  5. Fail closed: missing or stale state is a signal, not a default.
  6. Treat time as hostile: monotonic clock, highest-seen wall clock, boot ID.
  7. Test with vectors and fuzz: RFC vectors for known answers, mutations for unknown ones.
  8. 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'
Enter fullscreen mode Exit fullscreen mode

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

Top comments (28)

Collapse
 
effessdev profile image
EffessDev •

Broooo! THE post i have been waiting for 🤯

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

yeaaaaaaa ☠️

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

@effessdev in APIC v2 I'm adding layers, masks, broader file support, HSL, retouching tools phase by phase 🔥

Thread Thread
 
effessdev profile image
EffessDev •

💀

Thread Thread
 
effessdev profile image
EffessDev •

Selections?

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

APIC WEB: 🔥🔥
APIC v2: 💀

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

yeah selections too

Thread Thread
 
effessdev profile image
EffessDev •

👍

What about that magic wand thing?

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

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.

Thread Thread
 
effessdev profile image
EffessDev • • Edited

Bro but what will be our selling point against GIMP and Affinity? It looks like its turning into a generic photo editor.

Thread Thread
 
effessdev profile image
EffessDev •

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.

Thread Thread
 
technogamerz profile image
𝐓𝐡𝐞 𝐋𝐚𝐳𝐲 𝐆𝐢𝐫𝐥 •

What project are you working on, @effessdev bro?

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

he is offline didi I guess

Thread Thread
 
effessdev profile image
EffessDev •

@technogamerz Currenty ReptClip. After that, i will work on esp-idf-reference using it. When are you starting your company didi?

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

I think after she complete her MBA

Thread Thread
 
effessdev profile image
EffessDev •

bruh after MBA?

Thread Thread
 
effessdev profile image
EffessDev •

maybe she's going to start BIG

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

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

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar • • Edited

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.

Thread Thread
 
effessdev profile image
EffessDev •

🤣🤣🤣

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 👀

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

youtube.com/shorts/neY98b-8aNw

"dead economy" ☠️☠️☠️☠️ 🔥🔥🔥

Thread Thread
 
effessdev profile image
EffessDev •

Because of population? 🤔

Thread Thread
 
technogamerz profile image
𝐓𝐡𝐞 𝐋𝐚𝐳𝐲 𝐆𝐢𝐫𝐥 •

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.

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

just reminding you we've donation button in akhouri systems website
🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣 just kidding

Thread Thread
 
technogamerz profile image
𝐓𝐡𝐞 𝐋𝐚𝐳𝐲 𝐆𝐢𝐫𝐥 •

Par abhi toh saare paise kharch ho gaye!😔

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

lol 😂 I was just joking hahahaha

Thread Thread
 
technogamerz profile image
𝐓𝐡𝐞 𝐋𝐚𝐳𝐲 𝐆𝐢𝐫𝐥 •

Bhai yah dekho.

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

😂

Collapse
 
effessdev profile image
EffessDev •

Uh I wanted to see 10,000 lines lol

Collapse
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

next time i will do ctrl+c 10k loc and then ctrl+v to dev.to post 😂

Thread Thread
 
effessdev profile image
EffessDev •

And DEV.to will be broken again 🤣🤣

Thread Thread
 
akhourianmolkumar profile image
Akhouri Anmol Kumar •

also they will introduce then a new badge: LONGEST POST OF THE WEEK

Thread Thread
 
effessdev profile image
EffessDev •

🤣