Decryption issue with blowfish cipher

Hi,

I'm trying to use the blowfish cipher to decrypt a dataset, however I can't seem to get it to work. I use the blowfish 0.9.1 crate and made some attempts to combine it with the cbc 0.1.2 crate (which breaks).

What am I doing wrong here/what did I overlook ?

Example run :

  • Encrypted input bytes: [53, 57, 48, 48, 55, 53, 53, 57, 49, 52 ]
  • Key: [208, 206, 67, 128, 13, 143, 177, 114, 88, 83, 168, 180, 252, 133, 206, 223, 150, 35, 72, 247, 242, 57, 200, 252 ]
  • C# reference output: [82, 14, 92, 149, 178, 23, 237, 141, 253, 203, 27, 72, 14, 180, 197, 239 ]
  • Rust output with Blowfish: [189, 58, 125, 67, 140, 176, 130, 207, 71, 93, 147, 195, 98, 47, 197, 83]
  • Rust output with Blowfish: [112, 159, 6, 104, 247, 24, 207, 66, 158, 25, 67, 157, 112, 91, 30, 10]

The decryption code I use is the following:

   // key: the cipher's key
   // data_store: a Vec to decrypt with 0s as padding so the length is multiple of 8
    let mut decrypt = Blowfish::<byteorder::LittleEndian>::new_from_slice(key).unwrap();
    for i in (0..data_store.len()).step_by(8) {
        let mut dataset = GenericArray::from_mut_slice(&mut data_store[i..i+8]);
        decrypt.decrypt_block_mut(&mut dataset);
    }

Or with ecb (does not seem maintained, but cbc breaks at new_from_slice as it seems to try to use the default implementation for KeyInit instead of the specific implementation provided by blowfish):

    let mut decrypt = ecb::Decryptor::<BlowfishLE>::new_from_slice(&key).unwrap();

    for i in (0..data_store.len()).step_by(8) {
        let mut dataset = GenericArray::from_mut_slice(&mut data_store[i..i+8]);
        decrypt.decrypt_block_mut(&mut dataset);

Every simple example I found is outdated and relies on a crate that has been deprecated in favor of ecb/cbc (and does not seem to do much more that what I'm doing).

The reference implementation I have for my tests is in C#, uses bouncycastle's implementation of blowfish (bc-csharp/BlowfishEngine.cs at master · bcgit/bc-csharp · GitHub) and produces verifiably valid results when decrypting data.

If anyone can help me figure out what is wrong with my code, I'd be grateful. Thank you in advance

I'm assuming you swapped the key and ciphertext, since the ciphertext must be a multiple of 8 bytes in length. If I use the big-endian Blowfish cipher, I can reproduce your plaintext:

use blowfish::Blowfish;
use cipher::{BlockDecrypt, KeyInit};

fn main() {
    let ciphertext = [
        208, 206, 67, 128, 13, 143, 177, 114, 88, 83, 168, 180, 252, 133, 206, 223, 150, 35, 72,
        247, 242, 57, 200, 252,
    ];
    let key = [53, 57, 48, 48, 55, 53, 53, 57, 49, 52];
    let mut plaintext = [0; 24];
    let cipher: Blowfish = Blowfish::new_from_slice(&key).unwrap();
    for (in_block, out_block) in ciphertext.chunks(8).zip(plaintext.chunks_mut(8)) {
        cipher.decrypt_block_b2b(in_block.into(), out_block.into());
    }
    println!("{plaintext:?}");
}
[82, 14, 92, 149, 178, 23, 237, 141, 253, 203, 27, 72, 14, 180, 197, 239, 8, 8, 8, 8, 8, 8, 8, 8]

I'm not sure how you're invoking Bouncy Castle to drop the last 8 bytes of the plaintext. In any case, the first 16 bytes match with your reference output.

1 Like

I spent so much time looking for a mistake and didn't see this... thank you

KeyIvInit::new_from_slices should work without any issues. You can test it with the following code:

let key = [0u8; 4];
let iv = [0u8; 8];
let cipher = cbc::Encryptor::<BlowfishLE>::new_from_slices(&key, &iv).unwrap();

Note that you do not need the ecb crate. In the current crate versions this mode is "implemented" directly by block cipher implementations.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.