fjh658 / signal-decryption-tool

The signal-decryption-tool is a utility used for decrypting the Signal Desktop database (db) password, allowing access to encrypted message data. Signal Desktop uses SQLCipher, an encrypted SQLite database, to store message data, and this tool is intended to retrieve the decryption key.
MIT License
3 stars 0 forks source link

IV derivation seems to be wrong (?) #1

Open x3ro opened 1 week ago

x3ro commented 1 week ago

Hi and thanks for writing this, it was very helpful that someone already did most of the reserach 😅

I gave your code a try and found that it does indeed return a decrypted key, but this key did not successfully decrypt the test database. I played around with it a little bit and eventually noticed that the key does not have the right length: 48 bytes as opposed to the expected 64 bytes.

I eventually narrowed this down to decrypt_string, where you take the ciphertext and treat the first 16 bytes as the IV. However, looking at the OSCrypt code from Chromium, I noticed that it just uses an IV made up of 0x20 instead.

My resulting decrypt_string function looks like this, and works at least on my machine:

    fn decrypt_string(&self, encrypted_hex: &str) -> Result<String, Box<dyn std::error::Error>> {
        let encrypted_data = decode(encrypted_hex)?; // Decode the hex string
        if !encrypted_data.starts_with(ENCRYPTION_VERSION_PREFIX.as_bytes()) {
            return Err("Invalid encryption version prefix".into()); // Validate encryption version prefix
        }

        let encrypted_text = &encrypted_data[ENCRYPTION_VERSION_PREFIX.len()..]; // Extract encrypted text
        // Create AES-CBC cipher with empty IV
        // https://chromium.googlesource.com/chromium/src/+/refs/tags/130.0.6686.2/components/os_crypt/sync/os_crypt_mac.mm#208
        let cipher = Aes128Cbc::new_from_slices(&self.aes_key, &[b' '; KEY_LENGTH])?; 
        let decrypted = cipher.decrypt_vec(encrypted_text)?; // Decrypt the text
        String::from_utf8(decrypted).map_err(|e| e.into()) // Convert decrypted bytes to a UTF-8 string
    }
fjh658 commented 4 days ago

Hi and thanks for writing this, it was very helpful that someone already did most of the reserach 😅

I gave your code a try and found that it does indeed return a decrypted key, but this key did not successfully decrypt the test database. I played around with it a little bit and eventually noticed that the key does not have the right length: 48 bytes as opposed to the expected 64 bytes.

I eventually narrowed this down to decrypt_string, where you take the ciphertext and treat the first 16 bytes as the IV. However, looking at the OSCrypt code from Chromium, I noticed that it just uses an IV made up of 0x20 instead.

My resulting decrypt_string function looks like this, and works at least on my machine:

    fn decrypt_string(&self, encrypted_hex: &str) -> Result<String, Box<dyn std::error::Error>> {
        let encrypted_data = decode(encrypted_hex)?; // Decode the hex string
        if !encrypted_data.starts_with(ENCRYPTION_VERSION_PREFIX.as_bytes()) {
            return Err("Invalid encryption version prefix".into()); // Validate encryption version prefix
        }

        let encrypted_text = &encrypted_data[ENCRYPTION_VERSION_PREFIX.len()..]; // Extract encrypted text
        // Create AES-CBC cipher with empty IV
        // https://chromium.googlesource.com/chromium/src/+/refs/tags/130.0.6686.2/components/os_crypt/sync/os_crypt_mac.mm#208
        let cipher = Aes128Cbc::new_from_slices(&self.aes_key, &[b' '; KEY_LENGTH])?; 
        let decrypted = cipher.decrypt_vec(encrypted_text)?; // Decrypt the text
        String::from_utf8(decrypted).map_err(|e| e.into()) // Convert decrypted bytes to a UTF-8 string
    }

The decryption key is x'decrypt_key', including x and the quotation marks on both sides. For example, if decrypt_key=1234, then the complete key would be x'1234'.

SignalDecryption
Config file: /Users/hacker/Library/Application Support/Signal/config.json
Encrypted key: 7631307d7b10316d9f44d3381af53523b696324dcbb7f6dab87df980b18392d9d3e1cd435c85d3a032b9dfdcf55219cba6ae81c3ec2d3972d1408f6ddeaddfa3fef3bf66ba1f8bdc6f30486abe8a7a5e57fd6b
Decrypted key: e44131c698c3623fc526e95410f6c520eaef7cd7987e426a03fb7a50988f6b83

1. Open db file

db file: '~/Library/Application Support/Signal/sql/db.sqlite' image

2. Input decrypted key

3. Show result

image
x3ro commented 4 days ago

Hi and thanks for the overview :) I'm aware of how to use the decrypted key, and I already have a working implementation of this. Maybe there are differences between Signal versions, but in your example the decrypted key is 64 bytes long. This was not the case for me, when I ran your code without modification, where I only got a 48 byte key, which naturally didn't work.