X-Git-Url: http://git.euphorik.ch/?p=rup.git;a=blobdiff_plain;f=backend%2Fsrc%2Fcrypto.rs;fp=backend%2Fsrc%2Fcrypto.rs;h=16fbaa0c6b8094fc26a9c2e7537391ee11562b7e;hp=0000000000000000000000000000000000000000;hb=1f84b22050cf470b00aef6b3c3ecb7ae70242e1a;hpb=295ca1daf01ff83964812c50fe1374dd679a0559 diff --git a/backend/src/crypto.rs b/backend/src/crypto.rs new file mode 100644 index 0000000..16fbaa0 --- /dev/null +++ b/backend/src/crypto.rs @@ -0,0 +1,127 @@ +use openssl::{symm, sha::sha256}; +use rand::prelude::*; + +#[derive(Debug)] +pub enum KeyError { + UnableToDecodeBase64Key, + WrongKeyLength, +} + +#[derive(Debug)] +pub enum EncryptError { + KeyError(KeyError), + UnsupportedVersion(u8), + UnableToEncrypt, +} + +#[derive(Debug)] +pub enum DecryptError { + KeyError(KeyError), + UnableToParseVersion, + UnsupportedVersion(u8), + MessageToShort, + UnableToDecodeBase64Message, + UnableToDecrypt, + UnableToDecodeMessageAsUTF8String, + HashMismatch, +} + +fn decode_key(key: &str) -> Result, KeyError> { + match base64::decode(key) { + Ok(k) => if k.len() != 16 { Err(KeyError::WrongKeyLength) } else { Ok(k) }, + Err(_e) => Err(KeyError::UnableToDecodeBase64Key) + } +} + +/// Return a random key encoded in base64. +pub fn generate_key() -> String { + let key = rand::thread_rng().gen::<[u8; 16]>(); + base64::encode(key) +} + +/// Encrypt the given text with the given key (first version). The key length must be 128 bits encoded in base64. +/// Ouput formats: +/// * 'version' = 1: "1" + base_64( + hash(message) + aes(message)) +/// * 'version' = 2: "2" + base_64( + aes(hash(message) + message)) +/// IV: 16 bytes randomized. +/// Mode : CBC. +pub fn encrypt(key: &str, plain_text: &str, version: u8) -> Result { + let key_as_bytes = decode_key(key).map_err(EncryptError::KeyError)?; + + let text_as_bytes = plain_text.as_bytes(); + let hash_text = sha256(&text_as_bytes); + let iv = rand::thread_rng().gen::<[u8; 16]>(); + + let cipher_text = + if version == 1 { + symm::encrypt(symm::Cipher::aes_128_cbc(), &key_as_bytes, Some(&iv), text_as_bytes) + .map_err(|_e| EncryptError::UnableToEncrypt)? + } else if version == 2 { + symm::encrypt(symm::Cipher::aes_128_cbc(), &key_as_bytes, Some(&iv), &[&hash_text, text_as_bytes].concat()) + .map_err(|_e| EncryptError::UnableToEncrypt)? + } else { + return Err(EncryptError::UnsupportedVersion(version)) + }; + + let mut result: Vec = Vec::new(); + result.extend(&iv); + + if version == 1 { + result.extend(&hash_text); + } + + result.extend(&cipher_text); + + Ok(version.to_string() + &base64::encode(&result)) +} + +/// Decrypt the given text with the given key. The key length must be 128 bits encoded in base64. +/// Input formats: +/// * version 1: "1" + base_64( + hash(message) + aes(message)) +/// * version 2: "2" + base_64( + aes(hash(message) + message)) +pub fn decrypt(key: &str, cipher_text: &str) -> Result { + let key_as_bytes = decode_key(key).map_err(DecryptError::KeyError)?; + + // Can't decrypt a message with the wrong version. + let first_char = &cipher_text[..1]; + let version: u8 = first_char.parse().map_err(|_e| DecryptError::UnableToParseVersion)?; + + if version != 1 && version != 2 { + return Err(DecryptError::UnsupportedVersion(version)) + } + + let cipher_text_bytes = + base64::decode(&cipher_text.as_bytes()[1..]) + .map_err(|_e| DecryptError::UnableToDecodeBase64Message)?; + + if cipher_text_bytes.len() <= 48 { return Err(DecryptError::MessageToShort) } + + let iv = &cipher_text_bytes[0..16]; + + let (plain_message_bytes, hash) = + if version == 1 { + let encrypted_message = &cipher_text_bytes[48..]; + ( + symm::decrypt(symm::Cipher::aes_128_cbc(), &key_as_bytes, Some(iv), encrypted_message) + .map_err(|_e| DecryptError::UnableToDecrypt)?, + cipher_text_bytes[16..48].to_vec() + ) + } else { + let encrypted_message = &cipher_text_bytes[16..]; + let plain_text = + symm::decrypt(symm::Cipher::aes_128_cbc(), &key_as_bytes, Some(iv), encrypted_message) + .map_err(|_e| DecryptError::UnableToDecrypt)?; + ( + plain_text[32..].to_vec(), + plain_text[0..32].to_vec() + ) + }; + + if sha256(&plain_message_bytes) != hash[..] { return Err(DecryptError::HashMismatch) } + + let plain_message = + String::from_utf8(plain_message_bytes) + .map_err(|_e| DecryptError::UnableToDecodeMessageAsUTF8String)?; + + Ok(plain_message) +}