Hardware-bound AES-256-GCM encryption with USB key enforcement
Project description
AVGVSTO USB
Hardware-bound AES-256-GCM encryption — your USB drive is the key.
AVGVSTO fuses your password with the physical fingerprint of any USB drive. Without the exact hardware, decryption is impossible — even with the correct password. Features anti-bruteforce progressive cooldown, duress/plausible deniability mode, and tamper-evident encryption.
Quick start
pip install avgvstousb
import avgvsto
# 1. Plug in a USB drive and bind it
avgvsto.save_usb_config("E:\\") # Windows
# avgvsto.save_usb_config("/media/usb") # Linux
# avgvsto.save_usb_config("/Volumes/USB")# macOS
usb_id = avgvsto.load_usb_id()
# 2. Encrypt a file
avgvsto.encrypt_file("classified.docx", "my-password", usb_id)
# 3. Decrypt it
avgvsto.decrypt_file("classified.docx.avgvsto", "my-password", usb_id)
Table of contents
- Why AVGVSTO?
- Installation
- Security model
- Library API reference
- CLI reference
- Full examples
- API stability
- License
Why AVGVSTO?
| Feature | What it means |
|---|---|
| USB hardware binding | The physical USB drive is mixed into the encryption key. A cloned drive won't work. |
| AES-256-GCM | Authenticated encryption — tamper-evident. Military-grade standard. |
| ChaCha20-Poly1305 | Modern stream cipher. Faster on devices without AES-NI. |
| Anti-bruteforce | Progressive cooldown: 5s → 20s → 40s → 60s+ per wrong guess. Persists across restarts. |
| Attempt limits | Per-file max-attempt counter. File self-destructs after N wrong guesses. |
| Duress mode | Two passwords in one file. One reveals a decoy, the other reveals the real data. Forensically indistinguishable. |
| Zero dependencies on cloud | 100% offline. No telemetry. No accounts. |
| Secure deletion | 3-pass random overwrite + zero pass before unlinking the original file. |
Installation
pip install avgvstousb
Requires Python 3.9+. Works on Windows, macOS, and Linux.
System dependencies:
pycryptodome— AES-256-GCM / ChaCha20-Poly1305 / PBKDF2psutil— USB drive detection
These are installed automatically.
Verify installation
avgvsto status
[--] No USB binding configured.
Files encrypted: 0
Files decrypted: 0
Bytes encrypted: 0.0 B
Security model
User password ─┐
├──→ PBKDF2-HMAC-SHA256 (1M iterations) ──→ 256-bit key
USB hardware ID ─┘
- No key is stored on disk. The USB ID is not a key — it's mixed into the key derivation so that decryption requires both the password AND the exact physical USB drive.
- No cloud. No telemetry. No backdoors. Every cryptographic primitive is from pycryptodome, a well-audited open-source library.
- Attempt limits are stored locally in
~/.avgvsto/. They survive restarts and can only be reset with the reset password (stored on the USB itself).
What an attacker needs
| Scenario | Outcome |
|---|---|
| Has the encrypted file, NO USB drive | Cannot decrypt — hardware fingerprint is missing from key derivation |
| Has the file + USB drive, WRONG password | Cannot decrypt — PBKDF2 produces wrong key, GCM auth tag check fails |
| Has everything, but attempt limit hit | File is permanently locked — must use reset password |
| Both correct, no limit | Only way to decrypt |
Library API reference
Encryption & decryption
avgvsto.encrypt_file(src_path, password, usb_id, max_attempts=0, duress_password=None, duress_data=b"", cipher_id=0) → str
Encrypts a single file. The original file is securely overwritten and deleted. Returns the path to the encrypted .avgvsto file.
avgvsto.encrypt_file("photo.jpg", "s3cret", usb_id)
# → "photo.jpg.avgvsto"
avgvsto.encrypt_file("photo.jpg", "s3cret", usb_id, max_attempts=5)
# → "photo.jpg.avgvsto" (locked after 5 wrong passwords)
avgvsto.encrypt_file("photo.jpg", "s3cret", usb_id, cipher_id=avgvsto.CIPHER_CHACHA20)
# → "photo.jpg.avgvsto" (uses ChaCha20-Poly1305 instead of AES-256-GCM)
Parameters:
| Arg | Type | Default | Description |
|---|---|---|---|
src_path |
str |
— | Path to the file to encrypt |
password |
str |
— | Encryption password |
usb_id |
str |
— | USB hardware identifier (from save_usb_config() or load_usb_id()) |
max_attempts |
int |
0 |
Max decryption attempts (0 = unlimited) |
duress_password |
str|None |
None |
Second password that decrypts to decoy data |
duress_data |
bytes |
b"" |
Decoy content revealed by duress password |
cipher_id |
int |
CIPHER_AES |
CIPHER_AES (0) or CIPHER_CHACHA20 (1) |
Raises: ValueError if the file is already encrypted (has .avgvsto extension).
avgvsto.decrypt_file(src_path, password, usb_id) → tuple[str, int]
Decrypts an .avgvsto file. The encrypted file is securely deleted after successful decryption. Returns (result_path, max_attempts).
result, max_att = avgvsto.decrypt_file("photo.jpg.avgvsto", "s3cret", usb_id)
# result → "photo.jpg"
# max_att → 5
Parameters:
| Arg | Type | Description |
|---|---|---|
src_path |
str |
Path to the .avgvsto file |
password |
str |
Decryption password |
usb_id |
str |
USB hardware identifier |
Returns: (path_to_decrypted_file, max_attempts_from_header)
Raises: ValueError if authentication fails (wrong password, wrong USB, or corrupted file).
avgvsto.verify_file(src_path, password, usb_id) → tuple[bool, str]
In-memory integrity check. No files are written or modified. Returns (valid, detail_message).
ok, msg = avgvsto.verify_file("photo.jpg.avgvsto", "s3cret", usb_id)
print(ok) # True
print(msg) # "[OK] Integrity OK · Format v1 · AES-256-GCM · Attempts limit: 5 · Payload: 1.2 MB"
Parameters:
| Arg | Type | Description |
|---|---|---|
src_path |
str |
Path to the .avgvsto file |
password |
str |
Password to verify against |
usb_id |
str |
USB hardware identifier |
avgvsto.encrypt_folder(folder_path, password, usb_id, max_attempts=0, duress_password=None, duress_data=b"", on_progress=None, cipher_id=0) → tuple[int, list[str]]
Recursively encrypts all files in a folder. Non-.avgvsto files are processed. Returns (success_count, error_messages).
ok, errors = avgvsto.encrypt_folder("./docs", "s3cret", usb_id)
print(f"{ok} files encrypted") # "47 files encrypted"
# With progress callback
def progress(done, total, name):
print(f"[{done+1}/{total}] {name}")
ok, errors = avgvsto.encrypt_folder("./docs", "s3cret", usb_id, on_progress=progress)
avgvsto.decrypt_folder(folder_path, password, usb_id, on_progress=None) → tuple[int, list[str]]
Recursively decrypts all .avgvsto files in a folder.
ok, errors = avgvsto.decrypt_folder("./docs", "s3cret", usb_id)
USB management
avgvsto.list_usb_drives() → list[str]
Detects connected removable drives. Returns a list of mount points / drive letters.
drives = avgvsto.list_usb_drives()
print(drives) # ['E:\\'] on Windows
# ['/media/user/USB'] on Linux
# ['/Volumes/Untitled'] on macOS
avgvsto.save_usb_config(usb_path) → str | None
Reads the hardware fingerprint of a USB drive and stores it in ~/.avgvsto/usb_secure.key. Returns the USB ID string, or None on failure.
usb_id = avgvsto.save_usb_config("E:\\")
if usb_id:
print(f"USB bound: {usb_id[:16]}...")
avgvsto.load_usb_id() → str | None
Loads the previously saved USB identifier. Returns None if no binding exists.
usb_id = avgvsto.load_usb_id()
if not usb_id:
print("No USB binding configured.")
avgvsto.find_authorized_usb(saved_id) → str | None
Searches currently connected drives for one matching the saved ID. Returns the mount path or None.
path = avgvsto.find_authorized_usb(usb_id)
if path:
print(f"Authorised USB connected at {path}")
else:
print("Plug in the correct USB drive.")
avgvsto.get_usb_identifier(mount_path) → str | None
Reads the raw hardware fingerprint of a drive without saving it. Useful for one-shot checks.
uid = avgvsto.get_usb_identifier("E:\\")
Attempt tracking & brute-force protection
avgvsto.get_attempt_count(file_path) → int
Returns the number of recorded wrong-password attempts for a specific file.
count = avgvsto.get_attempt_count("photo.jpg.avgvsto")
if count >= 3:
print("Too many wrong attempts!")
avgvsto.increment_attempt_count(file_path) → int
Records one more failed attempt for a file. Returns the new count.
avgvsto.increment_attempt_count("photo.jpg.avgvsto")
avgvsto.reset_attempt_count(file_path) → None
Clears the attempt counter for a file. Useful after successful decryption.
avgvsto.reset_attempt_count("photo.jpg.avgvsto")
avgvsto.brute_remaining() → float
Returns the number of seconds remaining in the progressive cooldown. 0.0 means free to proceed.
import time
rem = avgvsto.brute_remaining()
if rem > 0:
print(f"Wait {rem:.0f}s before trying again.")
time.sleep(rem)
Cooldown progression:
| Wrong attempt # | Cooldown |
|---|---|
| 1st | 5 seconds |
| 2nd | 20 seconds |
| 3rd | 40 seconds |
| 4th | 60 seconds |
| 5th+ | (n-3) × 60 seconds (capped at 1 month) |
The cooldown is persistent across restarts (saved to ~/.avgvsto/brute_state.json).
avgvsto.brute_mark_fail() → None
Records a wrong-password event and starts the cooldown timer. Call this after a failed authentication.
try:
avgvsto.decrypt_file("file.avgvsto", "wrong", usb_id)
except ValueError:
avgvsto.brute_mark_fail()
avgvsto.brute_mark_success() → None
Resets the cooldown counter to zero after a successful decryption.
avgvsto.decrypt_file("file.avgvsto", "correct", usb_id)
avgvsto.brute_mark_success() # reset progressive counter
Duress / plausible deniability
Encrypt a file that has two independent passwords. One reveals innocent decoy data, the other reveals the real data. An adversary cannot tell which password is real.
# Encrypt with duress password
avgvsto.encrypt_file(
"real_budget.xlsx",
"real-password",
usb_id,
max_attempts=0,
duress_password="decoy-password",
duress_data=b"This is my homework assignment, nothing interesting here."
)
# Decrypt with real password → real data
data, _ = avgvsto.decrypt_file("real_budget.xlsx.avgvsto", "real-password", usb_id)
# Decrypt with duress password → decoy data
data, _ = avgvsto.decrypt_file("real_budget.xlsx.avgvsto", "decoy-password", usb_id)
Both passwords produce a valid decryption. The file format (v3 dual-slot) ensures both ciphertexts are mathematically independent and forensically identical.
Reset password system
When you set max_attempts > 0, files can become permanently locked after too many wrong guesses. The reset password (stored on the USB drive itself) lets you clear all attempt counters.
usb_mount = "E:\\"
# Create a reset password on the USB
avgvsto.create_reset_password(usb_mount, "my-reset-pw")
# Check if a reset password exists
avgvsto.has_reset_password(usb_mount) # True
# Check if reset is available
ok, reason = avgvsto.can_reset(usb_mount)
print(reason) # "Resets used: 0 / 3"
# Perform the reset — clears ALL attempt counters on this machine
success, msg = avgvsto.do_reset_counters(usb_mount, "my-reset-pw")
print(msg) # "14 locked counter(s) cleared. (Reset 1/3 used)"
# Wipe the reset config from USB entirely
avgvsto.full_clear_usb_reset(usb_mount)
Rules:
- Max 3 wrong reset-password attempts → reset permanently locked
- Max 3 successful resets total → use
full_clear_usb_reset()to start over - One reset password covers ALL files encrypted with this USB drive
- The reset password is stored on the USB itself (
avgvsto_reset.json), not on your computer
Utilities
avgvsto.scan_usb_for_avgvsto(usb_path) → dict
Scans a directory (typically a USB drive) for .avgvsto files. Returns stats and a per-file list.
result = avgvsto.scan_usb_for_avgvsto("E:\\")
print(result["total"]) # 47
print(result["valid"]) # 45
print(result["corrupted"]) # 2
print(result["total_bytes"]) # 104857600
for f in result["files"]:
print(f["path"], f["status"], f["max_attempts"])
Result structure:
{
"total": 47,
"valid": 45,
"corrupted": 2,
"total_bytes": 104857600,
"files": [
{
"path": "E:\\docs\\report.pdf.avgvsto",
"size": 2048000,
"status": "valid",
"max_attempts": 5,
"mtime": "2026-07-15 14:30"
},
# ...
]
}
avgvsto.get_locked_attempt_files() → list[dict]
Returns all attempt counter files stored on this machine.
locked = avgvsto.get_locked_attempt_files()
for item in locked:
print(item["slot"], item["count"])
avgvsto.secure_delete(path) → None
Overwrites a file with random data (3 passes) then zeros, then unlinks it. Used internally by encrypt_file and decrypt_file.
avgvsto.secure_delete("temp_secret.txt")
Note: On SSD / flash storage, wear-levelling means the OS may write to a different physical sector. This provides strong protection on HDDs and is significantly better than os.remove() on any storage.
Constants
| Constant | Value | Description |
|---|---|---|
avgvsto.CIPHER_AES |
0x00 |
AES-256-GCM (default) |
avgvsto.CIPHER_CHACHA20 |
0x01 |
ChaCha20-Poly1305 |
avgvsto.APP_NAME |
"AVGVSTO" |
Application name |
avgvsto.APP_VERSION |
"4.0" |
Current version |
avgvsto.ENC_EXT |
".avgvsto" |
Encrypted file extension |
avgvsto.MAGIC |
b"AVGVSTO2" |
File header magic bytes |
avgvsto.FORMAT_VER |
1 |
v1 header (legacy, AES-256 only) |
avgvsto.FORMAT_VER_3 |
3 |
v3 header (any cipher + optional duress slot) |
avgvsto.PBKDF2_ITERS |
1_000_000 |
PBKDF2 iterations |
CLI reference
Usage
avgvsto encrypt <path> [--usb PATH] [--attempts N] [--duress]
avgvsto decrypt <path> [--usb PATH]
avgvsto verify <path> [--usb PATH]
avgvsto status
avgvsto bind-usb <path>
Commands
encrypt
Encrypt a file or folder.
avgvsto encrypt report.pdf --usb E:\ --attempts 5
Encryption password: ********
Confirm password: ********
[OK] Encrypted -> report.pdf.avgvsto
With duress mode:
avgvsto encrypt secret.docx --usb E:\ --attempts 3 --duress
Encryption password: ********
Confirm password: ********
Duress password: ********
Confirm duress: ********
[OK] Encrypted -> secret.docx.avgvsto
decrypt
Decrypt an .avgvsto file or a folder of encrypted files.
avgvsto decrypt report.pdf.avgvsto --usb E:\
Decryption password: ********
[OK] Decrypted -> report.pdf
verify
Check file integrity without writing anything to disk.
avgvsto verify report.pdf.avgvsto
Password: ********
[OK] [OK] Integrity OK · Format v1 · AES-256-GCM · Attempts limit: 5 · Payload: 1.2 MB
status
Display USB binding status and statistics.
avgvsto status
[OK] USB binding: 3a8f2c... (connected at E:\)
Files encrypted: 42
Files decrypted: 17
Bytes encrypted: 1.2 GB
bind-usb
Save a USB drive's hardware fingerprint for future encryption/decryption.
avgvsto bind-usb E:\
[OK] USB bound — ID: 3a8f2cd1e4b9...
Full examples
Example 1: Encrypt a document with attempt limit
import avgvsto
# Bind your USB drive (do this once)
usb_id = avgvsto.save_usb_config("E:\\")
if not usb_id:
raise SystemExit("USB binding failed")
# Encrypt with a 5-attempt limit
avgvsto.encrypt_file("will.docx", "secure-password-123", usb_id, max_attempts=5)
print("File encrypted and original securely deleted.")
Example 2: Decrypt with brute-force protection
import avgvsto
usb_id = avgvsto.load_usb_id()
if not usb_id:
raise SystemExit("No USB binding found. Run bind-usb first.")
# Check cooldown
if avgvsto.brute_remaining() > 0:
print(f"Cooldown active. Wait {avgvsto.brute_remaining():.0f}s.")
exit(1)
password = input("Password: ")
try:
result, _ = avgvsto.decrypt_file("will.docx.avgvsto", password, usb_id)
print(f"Decrypted to {result}")
except ValueError as e:
avgvsto.brute_mark_fail() # this starts a cooldown
print(f"Failed: {e}")
Example 3: Batch encrypt a folder
import avgvsto
usb_id = avgvsto.load_usb_id()
ok, errors = avgvsto.encrypt_folder(
"./project",
"s3cret",
usb_id,
on_progress=lambda done, total, name: print(f"{done+1}/{total} {name}"),
)
print(f"{ok} files encrypted, {len(errors)} errors")
Example 4: Scan USB for encrypted files
import avgvsto
scan = avgvsto.scan_usb_for_avgvsto("E:\\")
print(f"Found {scan['total']} encrypted files ({scan['corrupted']} corrupted)")
for f in scan["files"]:
print(f" {f['path']} — {f['status']}")
Example 5: Use ChaCha20-Poly1305 instead of AES
import avgvsto
usb_id = avgvsto.load_usb_id()
avgvsto.encrypt_file(
"data.bin", "strong-password", usb_id,
cipher_id=avgvsto.CIPHER_CHACHA20,
)
Example 6: Create and use a reset password
import avgvsto
usb_id = avgvsto.save_usb_config("E:\\")
# Encrypt with attempt limit
avgvsto.encrypt_file("important.pdf", "mypassword", usb_id, max_attempts=3)
# Create reset password on the USB
avgvsto.create_reset_password("E:\\", "reset-123")
# ... later, after too many wrong passwords ...
success, msg = avgvsto.do_reset_counters("E:\\", "reset-123")
print(msg) # "3 locked counter(s) cleared. (Reset 1/3 used)"
Example 7: Full CLI workflow
# 1. Insert USB drive and bind it
avgvsto bind-usb E:\
# 2. Check status
avgvsto status
# 3. Encrypt a folder
avgvsto encrypt ./documents --attempts 5
# 4. Decrypt a single file
avgvsto decrypt documents.avgvsto
# 5. Verify an encrypted file
avgvsto verify documents.avgvsto
API stability
The public API exposed at avgvsto.* level follows semantic versioning. Private functions (prefixed with _) may change between minor versions without notice.
Stable public functions:
| Module | Functions |
|---|---|
avgvsto |
encrypt_file, decrypt_file, verify_file, encrypt_folder, decrypt_folder, verify_password_against_file |
avgvsto.usb |
list_usb_drives, get_usb_identifier, save_usb_config, load_usb_id, find_authorized_usb |
avgvsto.attempts |
get_attempt_count, increment_attempt_count, reset_attempt_count, brute_remaining, brute_mark_fail, brute_mark_success, create_reset_password, do_reset_counters, can_reset, has_reset_password, full_clear_usb_reset |
avgvsto.stats |
stats_inc_encrypt, stats_inc_decrypt |
avgvsto.utils |
scan_usb_for_avgvsto, get_locked_attempt_files, secure_delete |
License
GNU General Public License v3.0 or later — see LICENSE.
Copyright (c) 2026 Roy Merlo & RPX. Open source, auditable, sovereign.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file avgvstousb-4.0.1.tar.gz.
File metadata
- Download URL: avgvstousb-4.0.1.tar.gz
- Upload date:
- Size: 16.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c167c1f283c7628b6bd2148e30cb2c31946f42e60e1fe0ab6557ed59c5c3f19
|
|
| MD5 |
9cb119f44b9644a19fc5b7b46d5a3175
|
|
| BLAKE2b-256 |
e6fd06df33bb1c2203cab0606c6ee321549ae4619994dc281cb633bed15a505e
|
File details
Details for the file avgvstousb-4.0.1-py3-none-any.whl.
File metadata
- Download URL: avgvstousb-4.0.1-py3-none-any.whl
- Upload date:
- Size: 19.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cdb39c553dbb74ae60787438dc2831afaf95d260ef98639c32e26f6675924fd5
|
|
| MD5 |
815ac01a9102712c8ead7a25ba512e7b
|
|
| BLAKE2b-256 |
1a7a5d15c77542fecceb78f00cbfb744aa2a252ac26661f6d6e72a6186660030
|