← Back to Writeups
CUSTOMEasyCrypto

Как нужно делать пароли (How I Make My Passwords)

XESXOR9/10/20263 min read
#crypto#custom#easy#aes-cbc-decryption-with-recove#iv-matching-for-seed-verificat#prng-seed-recovery-via-timesta#timestamp-extraction-from-what

Как нужно делать пароли (How I Make My Passwords)

Platform: hackerlab | Category: Crypto | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-01-02 | Status: Solved Techniques: AES-CBC decryption with recovered key, IV matching for seed verification, PRNG seed recovery via timestamp bruteforce, Timestamp extraction from WhatsApp screenshot

Summary

Task: AES-CBC encrypted data with key generated using Python random seeded with time.time(). Solution: Extract timestamp from WhatsApp screenshot, bruteforce PRNG seeds around that time, verify by matching IV, decrypt with recovered key.

Как нужно делать пароли (How I Make My Passwords) — hackerlab

Description

Недавно я гулял по Москве. И встретил в кафе крутого тим-лида одной компании! Теперь напишу ему, как делаю свои пароли. Думаю, он никогда не создавал их так удобно и безопасно :)

Files:

  • gen_pass.py - Python password generation script
  • sample.txt - IV and encrypted data
  • whatsapp_chat.png - WhatsApp chat screenshot

Analysis

1. Generator source code (gen_pass.py)

python
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random
import time
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

KEY_LEN = 16
BS = 16

time_s = int(time.time())
random.seed(time_s)
key = bytes([random.randint(0, 255) for _ in range(KEY_LEN)])
iv = bytes([random.randint(0, 255) for _ in range(BS)])
cipher = AES.new(key, AES.MODE_CBC, iv)
final = cipher.encrypt(pad(passwd, BS))

Critical vulnerability: time.time() is used as seed for PRNG!

2. Data from sample.txt

IV: e49af1c15dad9962c45547b81414a399
Encrypted: 5239047215d18b79ffed9b8a93063e44a0ed0615ace44c8f80855775375bd85920dc6c0e37486ee2109887a55c337b66

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

3. WhatsApp screenshot

The screenshot shows:

  • Date: November 01, 2023
  • Time: ~17:10 (Moscow time)
  • Message about password generation

Vulnerability

Python random module uses Mersenne Twister PRNG. With a known seed, all subsequent values are fully deterministic.

Attack:

  1. Determine approximate encryption time from the screenshot
  2. Bruteforce timestamps around that time
  3. For each timestamp, generate key and IV
  4. Compare generated IV with the known one
  5. When IV matches, use the key for decryption ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

python
#!/usr/bin/env python3
"""
Hackerlab: Как нужно делать пароли
PRNG seed bruteforce attack on AES-CBC
"""

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import random
from datetime import datetime
import pytz

# Known values from sample.txt
iv_hex = "e49af1c15dad9962c45547b81414a399"
final_hex = "5239047215d18b79ffed9b8a93063e44a0ed0615ace44c8f80855775375bd85920dc6c0e37486ee2109887a55c337b66"

iv = bytes.fromhex(iv_hex)
final = bytes.fromhex(final_hex)

KEY_LEN = 16
BS = 16
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Time from WhatsApp screenshot: 01 November 2023, ~17:10 Moscow time
moscow_tz = pytz.timezone('Europe/Moscow')
base_time = moscow_tz.localize(datetime(2023, 11, 1, 17, 10, 0))
base_timestamp = int(base_time.timestamp())

print(f"[*] Base timestamp: {base_timestamp}")
print(f"[*] Searching in range [{base_timestamp - 300}, {base_timestamp + 300}]")

# Bruteforce timestamps around the known time
for offset in range(-300, 301):  # +/- 5 minutes
    time_s = base_timestamp + offset
    
    # Reproduce PRNG state
    random.seed(time_s)
    key = bytes([random.randint(0, 255) for _ in range(KEY_LEN)])
    test_iv = bytes([random.randint(0, 255) for _ in range(BS)])
    
    # Check if IV matches
    if test_iv == iv:
        print(f"[+] Found matching timestamp: {time_s}")
        print(f"[+] Offset from base: {offset} seconds")
        print(f"[+] Key: {key.hex()}")
        
        # Decrypt the ciphertext
        cipher = AES.new(key, AES.MODE_CBC, iv)
        decrypted = unpad(cipher.decrypt(final), BS)
        
        print(f"[+] Decrypted: {decrypted.decode()}")
        break
else:
    print("[-] No matching timestamp found in range")

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Execution result

[*] Base timestamp: 1698847800
[*] Searching in range [1698847500, 1698848100]
[+] Found matching timestamp: 1698847827
[+] Offset from base: 27 seconds
[+] Key: <recovered_key>
[+] Decrypted: CODEBY{REDACTED}

Lessons

  1. Never use random for cryptography - use secrets or os.urandom() instead
  2. Timestamp is a bad seed - easy to guess with second precision
  3. IV leak helps verification - allows checking seed correctness without decryption
  4. Metadata matters - chat screenshot provided time reference ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Alternative Approaches

  1. Without time reference: Bruteforce all timestamps for the last N years (slower but works)
  2. With known plaintext: If flag format is known, can verify decryption directly
  3. Parallel bruteforce: Split the range into parts for speedup

References