Initial cipher implementation

This commit is contained in:
Kai Jan Kriegel 2021-01-24 05:19:35 +01:00
commit 3bf3698c17
22 changed files with 512 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
Cargo.lock

6
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,6 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/NFCCrypto.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/../NFC/.idea/NFCCrypto.iml" filepath="$PROJECT_DIR$/../NFC/.idea/NFCCrypto.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

17
Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "nfc"
version = "0.1.0"
authors = ["Kai Jan Kriegel <kai@kjkriegel.de>"]
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"

42
src/crypto/cipher/aes.rs Normal file
View File

@ -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<Aes128, NoPadding>;
pub const KEY_LEN: usize = 16;
pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result<Vec<u8>> {
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<Vec<u8>> {
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);
}
}

12
src/crypto/cipher/mod.rs Normal file
View File

@ -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<Vec<u8>>;
fn decrypt(data: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>>;
}

45
src/crypto/cipher/tdes.rs Normal file
View File

@ -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<TdesEde2, NoPadding>;
pub const KEY_LEN: usize = 16;
pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result<Vec<u8>> {
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<Vec<u8>> {
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);
}
}

View File

@ -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<TdesEde2, NoPadding>;
pub const KEY_LEN: usize = 16;
pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result<Vec<u8>> {
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<Vec<u8>> {
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);
}
}

View File

@ -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<TdesEde3, NoPadding>;
pub const KEY_LEN: usize = 24;
pub fn encrypt (data: &[u8], key: [u8; KEY_LEN], iv: &[u8]) -> Result<Vec<u8>> {
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<Vec<u8>> {
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);
}
}

95
src/crypto/cipher_key.rs Normal file
View File

@ -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<CipherKey> {
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> {
CipherKey::new(&*hex::decode(key)?, cipher, key_version)
}
pub fn new_empty(cipher: CipherType) -> Result<CipherKey> {
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()
}

14
src/crypto/cipher_type.rs Normal file
View File

@ -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
}

44
src/crypto/crc/crc16.rs Normal file
View File

@ -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);
}
}

44
src/crypto/crc/crc32.rs Normal file
View File

@ -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);
}
}

2
src/crypto/crc/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod crc16;
pub mod crc32;

4
src/crypto/mod.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod cipher;
pub mod crc;
pub mod cipher_type;
pub mod cipher_key;

0
src/desfire/mod.rs Normal file
View File

58
src/error.rs Normal file
View File

@ -0,0 +1,58 @@
use std::io;
use std::fmt;
use hex::FromHexError;
#[derive(Debug)]
pub enum Error {
IO(io::Error),
Boxed(Box<dyn std::error::Error>),
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<io::Error> for Error {
fn from(e: io::Error) -> Error {
Error::IO(e)
}
}
impl From<block_modes::BlockModeError> for Error {
fn from(e: block_modes::BlockModeError) -> Error {
Error::BlockModeError(e)
}
}
impl From<block_modes::InvalidKeyIvLength> for Error {
fn from(e: block_modes::InvalidKeyIvLength) -> Error {
Error::InvalidKeyIvLength(e)
}
}
impl From<hex::FromHexError> for Error {
fn from(e: hex::FromHexError) -> Error { Error::FromHexError(e)}
}
pub(crate) type Result<T> = std::result::Result<T, Error>;

0
src/iso7816_4/mod.rs Normal file
View File

4
src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod crypto;
pub mod iso7816_4;
pub mod desfire;
pub mod error;

7
tests/lib.rs Normal file
View File

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}