Raccoglierò qui per ora un po’ di informazioni su come funziona la tessera Mifare per chi volesse cimentarsi o aiutare.
Alcuni file per partire:
Modalità di trasmissione
- I dati possono essere trasmessi in tre modalità
- chiaro
- autenticati: un MAC (message authentication code) viene accodato ai dati; questo viene derivato dal cifrario attivo e quindi non può essere forgiato e certifica la correttezza dei dati.
- cifrati: un CRC (cyclic redundancy check) viene accodato ai dati e poi il tutto viene cifrato; dopo che il messaggio viene decifrato, il CRC viene controllato per verificare che effettivamente sia stato decifrato correttamente e non siano bit spazzatura.
- I dati in entrata ed uscita possono essere trasmessi con modalità diverse.
- I dati in entrata ed uscita possono essere solo parzialmente autenticati o parzialmente cifrati: è comune che il codice del comando sia trasmesso in chiaro e poi sia seguito dai dati autenticati o cifrati.
Per ogni comando bisogna sapere esattamente che modalità utilizzare e su che porzione dei dati applicare il giusto protocollo. Questo lo leggiamo da RFDoorLock e Easypay.
Selezione del cifrario
- Il cifrario da utilizzare dipende dall’applicazione correntemente selezionata. La Mifare può contenere tot applicazioni; alla creazione si specifica che cifrario utilizzare tra DES, 2K3DES, 3K3DES e AES128.
- La master key della tessera, che fa riferimento all’applicazione root, può essere cambiata in uno degli altri cifrari (ma se qualcosa va storto poi buttiamo la tessera per cui per ora cambiamo solo chiavi in applicazioni create da noi).
Note su DES
DES, 2K3DES e 3K3DES sono la stessa cosa in salse diverse dal punto di vista crittografico; si possono ottenere tutti e tre a partire da 3DES, duplicando e riordinando i blocchi delle chiavi più corte. Di fatto MbedTLS, la libreria che utilizziamo, usa 3DES anche per 2K3DES. La tessera Mifare anche “abusa” di questo fatto: le chiavi DES (8 byte) della Mifare sono rappresentate come chiavi 2K3DES (16 byte) in cui i primi 8 e i secondi 8 byte coincidono.
Potrei sbagliarmi ma credo che anche algoritmicamente, 2K3DES con 8 byte ripetuti sia identico a plain DES perché due chiamate si cancellano l’una con l’altra.
RFDoorLock e Easypay implementano da soli i cifrari (non una buona idea per un progetto nuovo). Anche se la loro implementazione di CBC mette in evidenza che a seconda della modalità, viene fatto XOR con l’IV prima o dopo aver processato i blocchi, questo altro non è che la normale cifratura/decifratura con il relativo cifrario.
Note sulla direzione di cifratura
La prima divergenza tra RFDoorLock e Easypay la abbiamo sul fatto che Easypay decritta i dati in uscita per DES e 2K3DES invece di crittarli, come qualsiasi protocollo di buon senso farebbe. Questo sembrerebbe essere dovuto al fatto che in alcune implementazioni della Mifare, hanno implementato solo l’algoritmo di cifratura (per risparmiare?) per cui i dati vanno trasmessi “de-cifrati” così che possano essere “cifrati” dalla tessera per riottenere il plaintext.
Sappiamo per certo che la nostra tessera è in grado di decifrare DES e 2K3DES perché il comando Authenticate funziona, e dentro al comando Authenticate parte della chiave di sessione viene cifrata da noi, e quindi dev’essere decifrata dalla tessera perché lo scambio chiave vada a buon fine.
Calcolo del CRC
Un unit test rispetto agli esempi forniti conferma che sappiamo calcolare il CRC correttamente (usa un valore di inizializzazione particolare per CRC16, e bisogna negare il risultato rispetto all’implementazione dell’ESP).
Note sul protocollo di trasmissione legacy vs moderno
- Nel sorgente, legacy:
cipher_scheme.cpp
, moderno cipher_scheme_impl.hpp
; i metodi sono prepare_tx
e confirm_rx
.
- Legacy usa CRC16, moderno CRC32
- Legacy calcola CRC solo sui dati da cifrare, moderno su tutto il payload
- Legacy calcola MAC solo sui dati da autenticare, moderno su tutto il payload
- (in teoria) Legacy decritta in uscita invece di crittare
- Legacy usa crittografia CBC per derivare il MAC, moderno invece deriva due chiavi intermedie e fa delle rotazioni o XOR prima di chiamare CBC per ottenere il MAC.
- Legacy usa IV=0, moderno usa un IV globale per tutte le operazioni crittografiche
- (in teoria) Moderno passa dentro al MAC anche i dati trasmessi in chiaro, aggiornando l’IV.
L’IV globale
L’IV globale significa che tutte le operazioni di MAC, cifratura e decifratura devono essere eseguite esattamente nello stesso ordine sullo stesso IV, altrimenti la tessera e l’ESP vanno fuori sync e non possono comunicare. Per fortuna sembrerebbe che l’IV viene resettato soltanto quando viene avviata una nuova sessione (a meno del processo di autenticazione che per fortuna funziona così com’è).
La
Purtroppo, ogni comando ha le sue regolette su quali dati vadano passati nel MAC, su quali IV vengono resettati, su quale CRC utilizzare. O è fatto apposta, o è fatto male. Ecco qui una bella lista di tutti i parametri da tenere a mente per trasmettere e per ricevere. Scrivere codice per controllare la Mifare è come cercare di trovare ordine in un caos che ordine non ha:
- cifrario (DES, 2K3DES, 3K3DES, AES128)
- IV zero/IV globale
- CRC32 o CRC16
- Modalità di comunicazione: chiaro, autenticata, cifrata
- MAC standard o MAC con chiave derivata
- MAC su dati in chiaro o no
- Override: ometti il MAC
- Override: ometti il CRC
- Override: ometti cifratura (mai visto per ora)
- Direzione di cifratura: critta o decritta in uscita (sempre usato ‘critta’ per ora)
- Override: offset dei dati cifrati o autenticati (solo legacy, in teoria)
Ho cercato di “codificare” queste cose in desfire::cipher::config
e desfire::tag::comm_cfg
.
Senza contare che poi ci sono i comandi che hanno il loro protocollino custom, come… Authenticate e ChangeKey.
Mescolare la : legacy mode e IV globale
Ad aggiungere divertimento, è possibile mescolare queste modalità. La Mifare prende 3 modalità di autenticazione al comando Authenticate, legacy (DES e 2K3DES), ISO (3K3DES) e AES. Tuttavia è possibile autenticarsi in modalità ISO anche con una chiave DES e 2K3DES.
Quando ci si autentica in modalità ISO con un cifrario legacy, le cose si mescolano. Uno degli effetti è che bisogna utilizzare un IV globale. Al momento la modalità che usiamo è legacy e viene selezionata da desfire::auth_command
, tuttavia ho fatto degli esperimenti ed effettivamente funziona, cambiando auth_command
e aggiungendo questo snippet in tag::authenticate
:
const auto auth_cmd = auth_command(k.type());
if (auth_cmd != command_code::authenticate_legacy) {
pcipher->set_iv_mode(cipher_iv::global);
}
Perché questo? Perché RFDoorLock usa solo autenticazione ISO per 2K3DES e 3K3DES. Il che sembra implicare che è possibile mescolare i protocolli.
Questo è supportato dal fatto che RFDoorLock usa il protocollo moderno per ChangeKey, con il CRC32 calcolato su tutti i dati invece che il CRC16 calcolato sui dati parziali, come invece fa Easypay.
Le implicazioni di questo e del fatto che RFDoorLock sembra non implementare DES, è che gli esempi non sono utili per testare se ad esempio ChangeKey è corretto per DES e 2K3DES.
Il problema attuale con ChangeKey
ChangeKey con DES e 2K3DES non funziona, la tessera dice che non riconosce il CRC (il che significa che o i dati non sono crittati correttamente, o il messaggio è malformato). Funziona con i cifrari moderni, ma non abbiamo dump di esempi e quindi non possiamo sapere cosa non va, se è la nostra tessera o il codice. Al momento, ho implementato ChangeKey come in Easypay. Per i procolli legacy e per gli esperimenti attuali, in cui la chiave da cambiare è la stessa con cui siamo autenticati, questo significa che viene calcolato un CRC16 sulla chiave soltanto, e poi il pacchetto è formato da:
0xC4 | KEY_NUM(1b) | ENCRYPT[ KEY_DATA(16b) | CRC16[KEY_DATA](2b) ]
Da confrontare con la versione “moderna” (prendiamo 3K3DES, che imposta la flag 0x40
per marcare il tipo di cifrario)
0xC4 | KEY_NUM(1b) + 0x40 | ENCRYPT[
KEY_DATA(24b) | CRC32[
0xC4 | KEY_NUM(1b) + 0x40 | KEY_DATA(24b)
](4b)
]
RFDoorLock usa il secondo approccio anche per 2K3DES. Tuttavia nessuna delle due implementazioni sembra funzionare per DES e 2K3DES. Degli unit test verificano che CRC16 sia calcolato correttamente e che la cifratura/decifratura con 2K3DES avvenga correttamente anche con chiavi non banali. Dovrei aver provato tutte le 8 combinazioni di queste varianti
- usare il “protocollo ChangeKey moderno” invece di quello legacy
- usare autenticazione ISO e IV globale come in RFDoorLock anche per DES e 2K3DES
- invertire la direzione del cifrario in uscita (decifrare invece di cifrare, come in Easypay)
E qui è dove mi sono fermato al momento