Skip to main content

osdp/secure/
crypto.rs

1//! AES-128 primitives, key derivation, cryptograms.
2//!
3//! # Spec: Annex D.4
4//!
5//! Session keys are derived by AES-128 encrypting fixed templates with the
6//! SCBK:
7//!
8//! - `S-ENC  = AES_SCBK([0x01, 0x82, RND.A[0..6], 0,0,0,0,0,0,0,0])`
9//! - `S-MAC1 = AES_SCBK([0x01, 0x01, RND.A[0..6], 0,0,0,0,0,0,0,0])`
10//! - `S-MAC2 = AES_SCBK([0x01, 0x02, RND.A[0..6], 0,0,0,0,0,0,0,0])`
11//!
12//! Cryptograms:
13//!
14//! - `ClientCryptogram = AES_S-ENC(RND.A || RND.B)`
15//! - `ServerCryptogram = AES_S-ENC(RND.B || RND.A)`
16
17use aes::Aes128;
18use aes::cipher::generic_array::GenericArray;
19use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
20
21/// 128-bit AES block.
22pub type Block = [u8; 16];
23
24/// AES-128 encrypt of a single block.
25#[inline]
26pub fn aes128_encrypt(key: &[u8; 16], block: &Block) -> Block {
27    let cipher = Aes128::new(GenericArray::from_slice(key));
28    let mut buf = *GenericArray::from_slice(block);
29    cipher.encrypt_block(&mut buf);
30    let mut out = [0u8; 16];
31    out.copy_from_slice(&buf);
32    out
33}
34
35/// AES-128 decrypt of a single block.
36#[inline]
37pub fn aes128_decrypt(key: &[u8; 16], block: &Block) -> Block {
38    let cipher = Aes128::new(GenericArray::from_slice(key));
39    let mut buf = *GenericArray::from_slice(block);
40    cipher.decrypt_block(&mut buf);
41    let mut out = [0u8; 16];
42    out.copy_from_slice(&buf);
43    out
44}
45
46/// Build the 16-byte template: `[tag1, tag2, RND.A[0..6], 0,0,0,0,0,0,0,0]`.
47fn key_template(tag1: u8, tag2: u8, rnd_a: &[u8; 8]) -> Block {
48    let mut t = [0u8; 16];
49    t[0] = tag1;
50    t[1] = tag2;
51    t[2..8].copy_from_slice(&rnd_a[0..6]);
52    t
53}
54
55/// Derived session keys.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct SessionKeys {
58    /// `S-ENC` — encryption key.
59    pub s_enc: [u8; 16],
60    /// `S-MAC1` — MAC key for all blocks except the last.
61    pub s_mac1: [u8; 16],
62    /// `S-MAC2` — MAC key for the last block.
63    pub s_mac2: [u8; 16],
64}
65
66impl SessionKeys {
67    /// Derive `S-ENC`/`S-MAC1`/`S-MAC2` from `scbk` and `RND.A`.
68    pub fn derive(scbk: &[u8; 16], rnd_a: &[u8; 8]) -> Self {
69        Self {
70            s_enc: aes128_encrypt(scbk, &key_template(0x01, 0x82, rnd_a)),
71            s_mac1: aes128_encrypt(scbk, &key_template(0x01, 0x01, rnd_a)),
72            s_mac2: aes128_encrypt(scbk, &key_template(0x01, 0x02, rnd_a)),
73        }
74    }
75}
76
77/// `ClientCryptogram = AES_S-ENC(RND.A || RND.B)`.
78pub fn client_cryptogram(s_enc: &[u8; 16], rnd_a: &[u8; 8], rnd_b: &[u8; 8]) -> Block {
79    let mut block = [0u8; 16];
80    block[..8].copy_from_slice(rnd_a);
81    block[8..].copy_from_slice(rnd_b);
82    aes128_encrypt(s_enc, &block)
83}
84
85/// `ServerCryptogram = AES_S-ENC(RND.B || RND.A)`.
86pub fn server_cryptogram(s_enc: &[u8; 16], rnd_a: &[u8; 8], rnd_b: &[u8; 8]) -> Block {
87    let mut block = [0u8; 16];
88    block[..8].copy_from_slice(rnd_b);
89    block[8..].copy_from_slice(rnd_a);
90    aes128_encrypt(s_enc, &block)
91}
92
93/// `Initial R-MAC = AES_S-MAC2( AES_S-MAC1( ServerCryptogram ) )`.
94///
95/// # Spec: Annex D.4
96pub fn initial_rmac(s_mac1: &[u8; 16], s_mac2: &[u8; 16], server_cryptogram: &Block) -> Block {
97    let first = aes128_encrypt(s_mac1, server_cryptogram);
98    aes128_encrypt(s_mac2, &first)
99}
100
101/// Legacy key diversification: `SCBK = AES_MK( cUID || ~cUID )`.
102///
103/// # Spec: Annex D.7 (deprecated)
104pub fn diversify_scbk_legacy(mk: &[u8; 16], cuid: &[u8; 8]) -> [u8; 16] {
105    let mut block = [0u8; 16];
106    block[..8].copy_from_slice(cuid);
107    for (i, &b) in cuid.iter().enumerate() {
108        block[8 + i] = !b;
109    }
110    aes128_encrypt(mk, &block)
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    /// AES-128 inversion.
118    #[test]
119    fn enc_then_dec_identity() {
120        let key = [0u8; 16];
121        let plain = [0xABu8; 16];
122        let ct = aes128_encrypt(&key, &plain);
123        let pt = aes128_decrypt(&key, &ct);
124        assert_eq!(pt, plain);
125    }
126
127    /// FIPS-197 known answer — AES-128 with all-zero key + plaintext.
128    #[test]
129    fn fips_known_answer() {
130        let key = [0u8; 16];
131        let plain = [0u8; 16];
132        let ct = aes128_encrypt(&key, &plain);
133        let expected = [
134            0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A, 0x2C, 0x3B, 0x88, 0x4C, 0xFA, 0x59, 0xCA, 0x34,
135            0x2B, 0x2E,
136        ];
137        assert_eq!(ct, expected);
138    }
139
140    #[test]
141    fn keys_differ() {
142        let scbk = [0xAA; 16];
143        let rnd_a = [0x55u8; 8];
144        let k = SessionKeys::derive(&scbk, &rnd_a);
145        assert_ne!(k.s_enc, k.s_mac1);
146        assert_ne!(k.s_mac1, k.s_mac2);
147        assert_ne!(k.s_enc, k.s_mac2);
148    }
149
150    #[test]
151    fn cryptograms_distinct() {
152        let s_enc = [0; 16];
153        let rnd_a = [1u8; 8];
154        let rnd_b = [2u8; 8];
155        let c = client_cryptogram(&s_enc, &rnd_a, &rnd_b);
156        let s = server_cryptogram(&s_enc, &rnd_a, &rnd_b);
157        assert_ne!(c, s);
158    }
159}