I wrote the decryptor in Rust. I wanted the memory safety guarantees, but mostly, I wanted the speed. If I had to brute-force the date format, Python’s overhead would be too slow.

fn attempt_decrypt(encrypted_data: &[u8], password_guess: &str) -> Result<Vec<u8>, Error> 
    // 1. Derive the key from the password guess
    let key = derive_key_argon2(password_guess, SALT);
// 2. Initialize the cipher (AES-256-GCM was the guess)
    let cipher = Cipher::aes_256_gcm(&key);
// 3. Attempt decryption
    let decrypted = cipher.decrypt(IV, encrypted_data);
match decrypted 
        Ok(data) => 
            // Check for valid UTF-8 or file signatures (PNG, PDF, etc.)
            if looks_like_valid_file(&data) 
                Ok(data)
             else 
                Err(Error::InvalidContent)
Err(e) => Err(e),

For days, the console output was a stream of InvalidContent errors. It is a maddening process. You stare at bytes that represent failure, looking for a pattern that implies success.

  • encryptMetadata(header)
  • listSupportedAlgorithms()
  • There is a specific kind of silence that falls over a terminal when a decryption fails. It’s not the violent crash of a segfault or the noisy stack trace of a syntax error. It is a quiet, dismissive false.

    For the last week, that single word—false—was the wall I beat my head against.

    We deal in secrets. As developers, we are the architects of vaults. We build walls of AES-256 and moats of RSA keys, trusting implicitly in the mathematics of difficulty. But there is a haunting duality in cryptography: the very algorithms designed to protect data are often the most ruthless when it comes to destroying it.

    This is the story of building a custom decryption engine for a proprietary encrypted archive—let’s call it the .gem format—not to break security, but to reclaim it. It is a story about the friction between human memory and mathematical certainty, and how reading binary is less like reading code and more like reading braille in a dark room.

    In the Ruby programming world, a .gem file is a packaged library or application. It is essentially a tar archive containing:

    Before decrypting, identify:

    Check with file command or a hex editor:

    xxd encrypted.gem | head -n 5
    

    Look for recognizable magic numbers or plaintext headers like GEM0, GEM1.


    decipher = OpenSSL::Cipher.new('aes-256-gcm').decrypt decipher.key = key decipher.iv = iv decipher.auth_tag = tag decipher.auth_data = "" # Rails doesn't use additional auth data here

    plaintext = decipher.update(ciphertext) + decipher.final

    puts plaintext

    Run it with:

    ruby decrypt_gem_secrets.rb
    

    If the master key is correct, you’ll see the raw YAML secrets. If it’s wrong, you’ll get an OpenSSL::Cipher::CipherError (authentication failure).