use blowfish::Blowfish;
use cipher::{Block, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
const PADDING_BYTE: u8 = 2;
const BLOCK_LEN: usize = 8;
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
pub fn encrypt(key: &str, input: &str) -> String {
let mut inputbytes = input.as_bytes().to_vec();
let padded_len = round_len(inputbytes.len(), BLOCK_LEN);
inputbytes.resize(padded_len, PADDING_BYTE);
let encryptor: Blowfish =
Blowfish::new_from_slice(key.as_bytes()).expect("Invalid key: unsupported key length");
let mut cipherbytes = Vec::with_capacity(inputbytes.len());
for chunk in inputbytes.chunks_exact(BLOCK_LEN) {
let mut block =
Block::<Blowfish>::try_from(chunk).expect("Error encrypting input: invalid block size");
encryptor.encrypt_block(&mut block);
cipherbytes.extend_from_slice(&block);
}
let mut output = String::with_capacity(cipherbytes.len() * 2);
for b in cipherbytes {
output.push(HEX_CHARS[(b >> 4) as usize] as char);
output.push(HEX_CHARS[(b & 0xf) as usize] as char);
}
output
}
pub fn decrypt(key: &str, hex_input: &str) -> Vec<u8> {
use std::str;
let mut inputbytes = Vec::with_capacity(hex_input.len().div_ceil(2));
for chunk in hex_input.as_bytes().chunks(2) {
let fragment = unsafe { str::from_utf8_unchecked(chunk) };
let byte = u8::from_str_radix(fragment, 16).unwrap_or_default();
inputbytes.push(byte);
}
let decryptor: Blowfish =
Blowfish::new_from_slice(key.as_bytes()).expect("Invalid key: unsupported key length");
assert!(
inputbytes.len() % BLOCK_LEN == 0,
"Error decrypting input: input is not block-aligned"
);
let mut cipherbytes = Vec::with_capacity(inputbytes.len());
for chunk in inputbytes.chunks_exact(BLOCK_LEN) {
let mut block =
Block::<Blowfish>::try_from(chunk).expect("Error decrypting input: invalid block size");
decryptor.decrypt_block(&mut block);
cipherbytes.extend_from_slice(&block);
}
if let Some(index) = cipherbytes.iter().position(|&b| b == PADDING_BYTE) {
cipherbytes.truncate(index);
}
cipherbytes
}
fn round_len(len: usize, block_size: usize) -> usize {
let remainder = len % block_size;
if remainder == 0 {
len
} else {
len + block_size - remainder
}
}
#[cfg(test)]
mod tests {
use super::encrypt;
struct Test {
key: String,
plain_text: String,
cipher_text: String,
}
fn get_test_vector() -> Vec<Test> {
vec![Test {
key: "R=U!LH$O2B#".to_owned(),
plain_text: "è.<Ú1477631903".to_owned(),
cipher_text: "4a6b45612b018614c92c50dc73462bbd".to_owned(),
}]
}
#[test]
fn encrypt_test_vector() {
for test in get_test_vector() {
let cipher_text = encrypt(&test.key, &test.plain_text);
assert_eq!(test.cipher_text, cipher_text);
}
}
}