mirror of
https://gitlab.com/fabinfra/fabaccess/nfc_rs.git
synced 2025-03-12 06:41:46 +01:00
Initial cipher implementation
This commit is contained in:
commit
3bf3698c17
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
6
.idea/.gitignore
generated
vendored
Normal file
6
.idea/.gitignore
generated
vendored
Normal 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
12
.idea/NFCCrypto.iml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
17
Cargo.toml
Normal 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
42
src/crypto/cipher/aes.rs
Normal 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
12
src/crypto/cipher/mod.rs
Normal 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
45
src/crypto/cipher/tdes.rs
Normal 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);
|
||||
}
|
||||
}
|
45
src/crypto/cipher/tdes_2k.rs
Normal file
45
src/crypto/cipher/tdes_2k.rs
Normal 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);
|
||||
}
|
||||
}
|
45
src/crypto/cipher/tdes_3k.rs
Normal file
45
src/crypto/cipher/tdes_3k.rs
Normal 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
95
src/crypto/cipher_key.rs
Normal 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
14
src/crypto/cipher_type.rs
Normal 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
44
src/crypto/crc/crc16.rs
Normal 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
44
src/crypto/crc/crc32.rs
Normal 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
2
src/crypto/crc/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod crc16;
|
||||
pub mod crc32;
|
4
src/crypto/mod.rs
Normal file
4
src/crypto/mod.rs
Normal 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
0
src/desfire/mod.rs
Normal file
58
src/error.rs
Normal file
58
src/error.rs
Normal 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
0
src/iso7816_4/mod.rs
Normal file
4
src/lib.rs
Normal file
4
src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod crypto;
|
||||
pub mod iso7816_4;
|
||||
pub mod desfire;
|
||||
pub mod error;
|
7
tests/lib.rs
Normal file
7
tests/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user