← Projects

stockholm

January 2026

GoCryptographyChacha20-Poly1305Argon2idDocker42
github

Part of the 42 Cybersecurity Piscine. A nice introduction to the cryptography world, which I hope to continue exploring.

Overview

Implements the core mechanics of a ransomware in a controlled Docker sandbox - selective file targeting by extension (mirroring WannaCry’s target list), authenticated encryption, and full reversibility with the same key.

The cryptographic stack was a deliberate choice. Having already worked aith more classical schemes and a previous exercice in this piscine (ft_otp, One Time Password generator), I wanted to explore modern AEAD (authenticated encryption with associated data) constructions and memory-hard key derivation. Each file is encrypted with ChaCha20-Poly305, keyed from an Argon2id derivation of the user password. The on-disk format is self-contained: MAGIC | Salt | Nonce | ciphertext - all material needed for decryption is embedded in the file itself, with no external state. The magic bytes (STOCK1) double as AEAD additional data, authenticating the header and making tampering or format mismatch detectable at decryption time.

Go was chosen over Rust or C++ for this project - and for most of the cybersecurity piscine - primarily for its crypto and HTTP standard libraries, which are well-supplied and well-documented for this kind of work.

How it works

Key derivation with Argon2id

Stockholm uses Argon2id, which combines the properties of its two siblings: Argon2i, which accesses memory in a data-independent pattern (resistant to side-channel attacks), and Argon2d, which uses data-dependant access patterns (resistant to GPU-based attacks) => Argon2id does both: applying data-independant access in the first half of its computation and data-dependant in the second.

The key parameters are 64MB of memory, 3 iterations, and 1 thread. These control how expensive the derivation is for an attacker trying to brute-force the password (more memory and more iterations = more time + hardware per guess). The salt (16 random bytes generated fresh for each file) ensures that two files encrypted with the same password produce different keys, preventing percomputation attacks.

Authenticated encryption with Chacha20-Poly1305

ChaCha20-Poly1305 is an AEAD cipher. It provides two guarantees simultaneously: confidentiality (nobody can read the plaintext without the key) and authenticity (nobody can tamper with the ciphertext withtout detection).

ChaCha20 is a stream cipher. Given a 32-byte key and a 12-byte nonce, it generates a pseudo-random keystream of arbitrary length. Encrypting = XORing the plaintext with this keystream - trivially reversible with the same key and nonce. The nonce must never be reused with the same key, which is why Stockholm generates a fresh random nonce for every file.

Poly1305 is a one-time message authentication code. It takes the ciphertext and the additional data (the STOCK1 magic bytes) and computes a 16-byte authentication tag using a key derived from the same ChaCha20 keystream. On decryption, the tag is recomputed and compared - if anything has been modified (the ciphertext,the header), decryption fails. This is what makes the magic bytes meaningful beyond juste format identification: they’re cryptographically bound to the ciphertext, so a file that wasn’t encrypted by Stockholm can’t pass authentication.

On-disk format

[ MAGIC (6B) | Salt (16B) | Nonce (12B) Ciphertext + Tag (N+16B) ]

Everything needed to decrypt is self-contained in the file. The salt feeds Argon2id to redeerive the key. The nonce feeds ChaCha20-Poly1305 to decrypt and authenticate.