commit 3bf3698c17c478a81997095552e44b61e700a5ac Author: Kai Jan Kriegel Date: Sun Jan 24 05:19:35 2021 +0100 Initial cipher implementation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..8bf4d45 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,6 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/NFCCrypto.iml b/.idea/NFCCrypto.iml new file mode 100644 index 0000000..457e3e6 --- /dev/null +++ b/.idea/NFCCrypto.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..b4580a5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..93d017a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "nfc" +version = "0.1.0" +authors = ["Kai Jan Kriegel "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +des = "0.6.0" +aes = "0.6.0" +block-modes = "0.7.0" +simple-error = "0.2.3" +hex = "0.4.2" + +[dev-dependencies] +hex-literal = "0.3.1" \ No newline at end of file diff --git a/src/crypto/cipher/aes.rs b/src/crypto/cipher/aes.rs new file mode 100644 index 0000000..17bb6b5 --- /dev/null +++ b/src/crypto/cipher/aes.rs @@ -0,0 +1,42 @@ +use aes::Aes128; +use block_modes::{BlockMode, Cbc}; +use block_modes::block_padding::NoPadding; +use crate::error::{Result, Error}; + +type Aes128Cbc = Cbc; + +pub const KEY_LEN: usize = 16; + +pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = Aes128Cbc::new_var(&key, &iv)?; + + return Ok(cipher.encrypt_vec(data)); +} + +pub fn decrypt(data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = Aes128Cbc::new_var(&key, &iv)?; + + let result = cipher.decrypt_vec(data); + return match result { + Ok(data) => {Ok(data)} + Err(err) => {Err(Error::BlockModeError(err))} + } +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + #[test] + fn encrypt() { + let data = hex!("8db1f942f2d7cc82f6fa1486a30f8c12104a3b07e8eb77a7ac00000000000000"); + let key = hex!("e7aff3361c3e85347993c3219a87d24b"); + let iv = hex!("00000000000000000000000000000000"); + + let data_enc = crate::crypto::cipher::aes::encrypt(&data, key, &iv).unwrap(); + + let data_enc_expected = hex!("3c79d74a4969ba7123e5d8f6df24493112d221fd131a4617d0eda5d92ccc1b46"); + + assert_eq!(data_enc, data_enc_expected); + } +} \ No newline at end of file diff --git a/src/crypto/cipher/mod.rs b/src/crypto/cipher/mod.rs new file mode 100644 index 0000000..4cf755d --- /dev/null +++ b/src/crypto/cipher/mod.rs @@ -0,0 +1,12 @@ +pub mod aes; +pub mod tdes; +pub mod tdes_2k; +pub mod tdes_3k; + +use crate::error::Result; + +//TODO: impl this for all the ciphers +trait Cipher { + fn encrypt (data: &[u8], key: &[u8], iv: &[u8]) -> Result>; + fn decrypt(data: &[u8], key: &[u8], iv: &[u8]) -> Result>; +} \ No newline at end of file diff --git a/src/crypto/cipher/tdes.rs b/src/crypto/cipher/tdes.rs new file mode 100644 index 0000000..29ffcbc --- /dev/null +++ b/src/crypto/cipher/tdes.rs @@ -0,0 +1,45 @@ +//FIXME: Which TDES Mode should be used by this? +use des::TdesEde2; +use block_modes::{BlockMode, Cbc}; +use block_modes::block_padding::NoPadding; +use crate::error::{Result, Error}; + +type TDesEde2Cbc = Cbc; + +pub const KEY_LEN: usize = 16; + +pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = TDesEde2Cbc::new_var(&key, &iv)?; + + return Ok(cipher.encrypt_vec(data)); +} + +pub fn decrypt(data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = TDesEde2Cbc::new_var(&key, &iv)?; + + + let result = cipher.decrypt_vec(data); + return match result { + Ok(data) => {Ok(data)} + Err(err) => {Err(Error::BlockModeError(err))} + } +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + #[test] + #[ignore] + fn encrypt() { + let data = hex!(""); + let key = hex!("00000000000000000000000000000000"); + let iv = hex!("00000000000000000000000000000000"); + + let data_enc = crate::crypto::cipher::tdes::encrypt(&data, key, &iv).unwrap(); + + let data_enc_expected = hex!(""); + + assert_eq!(data_enc, data_enc_expected); + } +} \ No newline at end of file diff --git a/src/crypto/cipher/tdes_2k.rs b/src/crypto/cipher/tdes_2k.rs new file mode 100644 index 0000000..29ffcbc --- /dev/null +++ b/src/crypto/cipher/tdes_2k.rs @@ -0,0 +1,45 @@ +//FIXME: Which TDES Mode should be used by this? +use des::TdesEde2; +use block_modes::{BlockMode, Cbc}; +use block_modes::block_padding::NoPadding; +use crate::error::{Result, Error}; + +type TDesEde2Cbc = Cbc; + +pub const KEY_LEN: usize = 16; + +pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = TDesEde2Cbc::new_var(&key, &iv)?; + + return Ok(cipher.encrypt_vec(data)); +} + +pub fn decrypt(data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = TDesEde2Cbc::new_var(&key, &iv)?; + + + let result = cipher.decrypt_vec(data); + return match result { + Ok(data) => {Ok(data)} + Err(err) => {Err(Error::BlockModeError(err))} + } +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + #[test] + #[ignore] + fn encrypt() { + let data = hex!(""); + let key = hex!("00000000000000000000000000000000"); + let iv = hex!("00000000000000000000000000000000"); + + let data_enc = crate::crypto::cipher::tdes::encrypt(&data, key, &iv).unwrap(); + + let data_enc_expected = hex!(""); + + assert_eq!(data_enc, data_enc_expected); + } +} \ No newline at end of file diff --git a/src/crypto/cipher/tdes_3k.rs b/src/crypto/cipher/tdes_3k.rs new file mode 100644 index 0000000..972f964 --- /dev/null +++ b/src/crypto/cipher/tdes_3k.rs @@ -0,0 +1,45 @@ +//FIXME: Which TDES Mode should be used by this? +use des::TdesEde3; +use block_modes::{BlockMode, Cbc}; +use block_modes::block_padding::NoPadding; +use crate::error::{Result, Error}; + +type TDesEde3Cbc = Cbc; + +pub const KEY_LEN: usize = 24; + +pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = TDesEde3Cbc::new_var(&key, &iv)?; + + return Ok(cipher.encrypt_vec(data)); +} + +pub fn decrypt(data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result> { + let cipher = TDesEde3Cbc::new_var(&key, &iv)?; + + + let result = cipher.decrypt_vec(data); + return match result { + Ok(data) => {Ok(data)} + Err(err) => {Err(Error::BlockModeError(err))} + } +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + #[test] + #[ignore] + fn encrypt() { + let data = hex!(""); + let key = hex!("00000000000000000000000000000000"); + let iv = hex!("00000000000000000000000000000000"); + + let data_enc = crate::crypto::cipher::tdes::encrypt(&data, key, &iv).unwrap(); + + let data_enc_expected = hex!(""); + + assert_eq!(data_enc, data_enc_expected); + } +} \ No newline at end of file diff --git a/src/crypto/cipher_key.rs b/src/crypto/cipher_key.rs new file mode 100644 index 0000000..b09dff0 --- /dev/null +++ b/src/crypto/cipher_key.rs @@ -0,0 +1,95 @@ +use crate::crypto::cipher_type::CipherType; +use crate::crypto::cipher_type::CipherType::*; +use crate::crypto::cipher::{aes, tdes, tdes_2k, tdes_3k}; +use crate::error::Error; +use crate::error::Result; + +use simple_error::simple_error; + +#[derive(Debug, Clone)] +struct CipherKey { + /// Key as Array + key: Box<[u8]>, + + /// CipherType of Key + cipher: CipherType, + + /// KeyVersion of Key + key_version: u8 +} + +impl CipherKey { + pub fn new(key: &[u8], cipher: CipherType, key_version: u8) -> Result { + if cipher == AES && key_version < 0x10 { + return Err(Error::Boxed(Box::new(simple_error!("KeyVersion is to low for AES Key (Minimum = 0x10)")))) + } + + if !check_key(key, cipher.clone()) { + return Err(Error::Boxed(Box::new(simple_error!("Key is not valid for CipherType")))) + } + + let mut key: Box<[u8]> = Box::from(key); + if cipher == TDES || cipher == TDES2K || cipher == TDES3K { + key = set_key_version(key.as_ref(), key_version); + } + + Ok(CipherKey { + cipher, + key_version, + key + }) + } + + pub fn new_from_str(key: &str, cipher: CipherType, key_version: u8) -> Result { + CipherKey::new(&*hex::decode(key)?, cipher, key_version) + } + + pub fn new_empty(cipher: CipherType) -> Result { + Ok(CipherKey{ + key: generate_empty_key(cipher.clone()), + key_version: if cipher == AES { 0x10u8 } else { 0x00u8 }, + cipher + }) + } +} + +/// Generate Empty Key for CipherType +pub fn generate_empty_key(cipher: CipherType) -> Box<[u8]> { + let size = get_key_size(cipher); + + vec![0u8; size].into_boxed_slice() +} + +/// Get KeySize for CipherType +pub fn get_key_size(cipher: CipherType) -> usize { + match cipher { + TDES => tdes::KEY_LEN, + TDES2K => tdes_2k::KEY_LEN, + TDES3K => tdes_3k::KEY_LEN, + AES => aes::KEY_LEN + } +} + +/// Check Key Slice +pub fn check_key(key: &[u8], cipher: CipherType) -> bool { + if key.len() != get_key_size(cipher) { false } else { true } +} + +/// Set Key Version for DES/TDES Keys +/// KeyVersion is stored in the LSBits of the first 8 Bytes +/// Parity Bits are not used from DESFire Cars + +pub fn set_key_version(key: &[u8], version: u8) -> Box<[u8]> { + let pow2 = [0x01u8, 0x02u8, 0x04u8, 0x08u8, 0x10u8, 0x20u8, 0x40u8, 0x80u8]; + + let mut new_key = key.to_vec(); + + for i in 0..8 { + if (version & pow2[i]) > 0 { + new_key[i] = (new_key[5] | 0x01u8) as u8; + } else { + new_key[i] = (new_key[5] & 0x7fu8) as u8; + } + } + new_key.into_boxed_slice() +} \ No newline at end of file diff --git a/src/crypto/cipher_type.rs b/src/crypto/cipher_type.rs new file mode 100644 index 0000000..1be2b31 --- /dev/null +++ b/src/crypto/cipher_type.rs @@ -0,0 +1,14 @@ +#[derive(Debug, PartialEq, Clone)] +pub enum CipherType { + /// DES / Triple DES + TDES, + + /// Triple DES with 2 DES Keys + TDES2K, + + /// Triple DES with 3 DES Keys + TDES3K, + + /// AES + AES +} \ No newline at end of file diff --git a/src/crypto/crc/crc16.rs b/src/crypto/crc/crc16.rs new file mode 100644 index 0000000..c6840ab --- /dev/null +++ b/src/crypto/crc/crc16.rs @@ -0,0 +1,44 @@ +use std::slice::from_ref; + +const POLY: u16 = 0x8408; +const INIT_VALUE: u16 = 0x6363; + +pub fn calculate_with_initial(data: &[u8], mut crc16: u16) -> u16 { + for b in data { + crc16 ^= *b as u16; + for _ in 0..8 { + let b_bit = (crc16 & 0x01) > 0; + crc16 >>= 1; + if b_bit { + crc16 ^= POLY; + } + } + } + crc16 +} + +pub fn calculate(data: &[u8]) -> Box<[u8]> { + let mut crc16 = INIT_VALUE; + + for d in data { + crc16 = calculate_with_initial(from_ref(d), crc16); + } + + Box::new(crc16.to_ne_bytes()) +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + #[test] + #[ignore] + fn calculate() { + let data = hex!(""); + let crc_expected = hex!(""); + + let crc = crate::crypto::crc::crc16::calculate(&data); + + assert_eq!(*crc, crc_expected); + } +} \ No newline at end of file diff --git a/src/crypto/crc/crc32.rs b/src/crypto/crc/crc32.rs new file mode 100644 index 0000000..eb4ba39 --- /dev/null +++ b/src/crypto/crc/crc32.rs @@ -0,0 +1,44 @@ +use std::slice::from_ref; + +const POLY: u32 = 0xEDB88320; +const INIT_VALUE: u32 = 0xFFFFFFFF; + +pub fn calculate_with_initial(data: &[u8], mut crc32: u32) -> u32 { + for b in data { + crc32 ^= *b as u32; + for _ in 0..8 { + let b_bit = (crc32 & 0x01) > 0; + crc32 >>= 1; + if b_bit { + crc32 ^= POLY; + } + } + } + + crc32 +} + +pub fn calculate(data: &[u8]) -> Box<[u8]> { + let mut crc32 = INIT_VALUE; + + for d in data { + crc32 = calculate_with_initial(from_ref(d), crc32); + } + + Box::new(crc32.to_ne_bytes()) +} + +#[cfg(test)] +mod tests { + use hex_literal::hex; + + #[test] + fn calculate() { + let data = hex!("c40045eeb8338ae8f49a032e85bb1114353010"); + let crc_expected = hex!("95c3894b"); + + let crc = crate::crypto::crc::crc32::calculate(&data); + + assert_eq!(*crc, crc_expected); + } +} \ No newline at end of file diff --git a/src/crypto/crc/mod.rs b/src/crypto/crc/mod.rs new file mode 100644 index 0000000..7597b18 --- /dev/null +++ b/src/crypto/crc/mod.rs @@ -0,0 +1,2 @@ +pub mod crc16; +pub mod crc32; \ No newline at end of file diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..3a47ec8 --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,4 @@ +pub mod cipher; +pub mod crc; +pub mod cipher_type; +pub mod cipher_key; \ No newline at end of file diff --git a/src/desfire/mod.rs b/src/desfire/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..c68f0df --- /dev/null +++ b/src/error.rs @@ -0,0 +1,58 @@ +use std::io; +use std::fmt; +use hex::FromHexError; + +#[derive(Debug)] +pub enum Error { + IO(io::Error), + Boxed(Box), + BlockModeError(block_modes::BlockModeError), + InvalidKeyIvLength(block_modes::InvalidKeyIvLength), + FromHexError(hex::FromHexError) +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::IO(e) => { + write!(f, "IO Error: {}", e) + }, + Error::Boxed(e) => { + write!(f, "{}", e) + }, + Error::BlockModeError(e) => { + write!(f, "CBC Error: {}", e) + }, + Error::InvalidKeyIvLength(e) => { + write!(f, "CBC InvalidKeyIvLength Error: {}", e) + } + Error::FromHexError(e) => { + write!(f, "Hex conversion Error: {}", e) + } + } + } +} + +impl From for Error { + fn from(e: io::Error) -> Error { + Error::IO(e) + } +} + +impl From for Error { + fn from(e: block_modes::BlockModeError) -> Error { + Error::BlockModeError(e) + } +} + +impl From for Error { + fn from(e: block_modes::InvalidKeyIvLength) -> Error { + Error::InvalidKeyIvLength(e) + } +} + +impl From for Error { + fn from(e: hex::FromHexError) -> Error { Error::FromHexError(e)} +} + +pub(crate) type Result = std::result::Result; diff --git a/src/iso7816_4/mod.rs b/src/iso7816_4/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3d32c68 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +pub mod crypto; +pub mod iso7816_4; +pub mod desfire; +pub mod error; \ No newline at end of file diff --git a/tests/lib.rs b/tests/lib.rs new file mode 100644 index 0000000..b62bb10 --- /dev/null +++ b/tests/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} \ No newline at end of file