From: Ummon Date: Wed, 5 Nov 2014 23:34:09 +0000 (+0100) Subject: Update the report. X-Git-Url: http://git.euphorik.ch/?p=crypto_lab1.git;a=commitdiff_plain;h=d3d8743586e533af4f32c1edfcff6377161d7cf4 Update the report. --- diff --git a/lab1_rust/run_attack.sh b/lab1_rust/run_attack.sh index 2850e68..5cba4e2 100755 --- a/lab1_rust/run_attack.sh +++ b/lab1_rust/run_attack.sh @@ -1 +1,3 @@ -time cargo run --release -- oracle-weak +#!/usr/bin/env bash +cargo build --release +time target/release/lab1_rust oracle-weak diff --git a/lab1_rust/src/crypto.rs b/lab1_rust/src/crypto.rs index 31b224f..2e8e046 100644 --- a/lab1_rust/src/crypto.rs +++ b/lab1_rust/src/crypto.rs @@ -9,7 +9,7 @@ use openssl::crypto::symm; static KEY_A: &'static [u8] = [125, 31, 131, 118, 143, 180, 252, 53, 211, 217, 79, 240, 128, 91, 252, 87, 104, 236, 145, 198, 163, 203, 161, 12, 53, 56, 218, 40, 221, 95, 171, 140]; static KEY_C: &'static [u8] = [75, 226, 88, 31, 223, 216, 182, 216, 178, 58, 59, 193, 245, 80, 254, 128, 125, 246, 246, 224, 194, 190, 123, 123, 10, 131, 217, 183, 112, 157, 166, 102]; -/// Only returns the first ten bytes. +/// Only returns the first ten bytes from HMAC-SHA256. pub fn compute_mac(data: &[u8]) -> [u8, ..10] { let mut hmac = HMAC(SHA256, KEY_A); hmac.update(data); @@ -18,7 +18,7 @@ pub fn compute_mac(data: &[u8]) -> [u8, ..10] { result } -/// Encrypt may fail if the provided data size isn't a multiple of 16. +/// Encrypt may fail if the provided data size isn't a multiple of 16, no padding will be automatically added. pub fn encrypt(plaindata: &[u8], iv: &[u8]) -> Option> { let c = symm::Crypter::new(symm::AES_256_CBC); c.init(symm::Encrypt, KEY_C, iv.to_vec()); @@ -32,7 +32,7 @@ pub fn encrypt(plaindata: &[u8], iv: &[u8]) -> Option> { } } -/// Decrypt may fail if the provided data size isn't a multiple of 16. +/// Decrypt may fail if the provided data size isn't a multiple of 16, no padding will be automatically added. pub fn decrypt(cipherdata: &[u8], iv: &[u8]) -> Option> { let c = symm::Crypter::new(symm::AES_256_CBC); c.init(symm::Decrypt, KEY_C, iv.to_vec()); diff --git a/lab1_rust/src/end_point.rs b/lab1_rust/src/end_point.rs index 9492d95..51b55b8 100644 --- a/lab1_rust/src/end_point.rs +++ b/lab1_rust/src/end_point.rs @@ -8,7 +8,7 @@ use packet::{ Packet, Command, Answer, Error, ReadingResult, PacketType }; static DEFAULT_TIMEOUT: Option = Some(500); // [ms]. pub struct Server { - acceptor: TcpAcceptor + acceptor: TcpAcceptor, } pub struct Client { diff --git a/lab1_rust/src/main.rs b/lab1_rust/src/main.rs index 967e0c7..de93150 100644 --- a/lab1_rust/src/main.rs +++ b/lab1_rust/src/main.rs @@ -15,17 +15,6 @@ mod oracle_machine; const PORT: u16 = 4221; -fn print_usage() { - println!( - r"{} [genkey | tests | oracle-weak | oracle-fixed] - genkey: Generate a 256 bits key - tests: launch some tests between a client and a weak server - oracle-weak: launch a padding oracle attack against a weak server - oracle-fixed: launch a padding oracle attack against a fixed server", - os::args()[0] - ); -} - fn do_oracle_attack(address: &str, variant: packet::Variant) { // 16 bytes encrypted data from 'Packet::random_packet_data([4])'. let cipher_block: [u8, ..16] = [254, 9, 228, 149, 60, 42, 165, 34, 233, 75, 112, 57, 37, 9, 116, 103]; // Known by the attacker. @@ -75,6 +64,17 @@ fn mode() -> Mode { } } +fn print_usage() { + println!( + r"{} [genkey | tests | oracle-weak | oracle-fixed] + genkey: Generate a 256 bits key + tests: launch some tests between a client and a weak server + oracle-weak: launch a padding oracle attack against a weak server + oracle-fixed: launch a padding oracle attack against a fixed server", + os::args()[0] + ); +} + fn main() { let mode = mode(); diff --git a/lab1_rust/src/oracle_machine.rs b/lab1_rust/src/oracle_machine.rs index a5be2d7..3d72ddf 100644 --- a/lab1_rust/src/oracle_machine.rs +++ b/lab1_rust/src/oracle_machine.rs @@ -1,13 +1,13 @@ use std::io; -use std::io::{ TcpStream }; -use std::iter::{ range_inclusive }; +use std::io::TcpStream; +use std::iter::range_inclusive; use std::slice::bytes::copy_memory; use packet; use packet::{ Packet, Error }; use end_point::EndPoint; -/// Try to decipher a ciphered data block by using the previous xor operand and an oracle on the provided address and port. -/// May prints some message on the stdout. +/// Try to decipher a ciphered data block by using the previous XOR operand and an oracle on the provided address and port. +/// May print some message on stdout. pub fn decipher(address: &str, port: u16, original_xor_operand: &[u8, ..16], cipherblock: &[u8, ..16], variant: packet::Variant) -> Option> { let mut end_point = EndPoint::new( match TcpStream::connect(address, port) { @@ -20,8 +20,9 @@ pub fn decipher(address: &str, port: u16, original_xor_operand: &[u8, ..16], cip variant, ); + // See 'packet::Packet' documentation for a complete description about the binary packet structure. let mut final_packet = [0u8, ..2 + 1 + 8 + 32 + 10]; - final_packet[1] = 1 + 8 + 32 + 10; // Data length. + final_packet[1] = (final_packet.len() as u8) - 2; // Data length. copy_memory(final_packet.slice_mut(2 + 1 + 8 + 16, 2 + 1 + 8 + 32), cipherblock); let mut decipher_block = [0u8, ..16]; // The result. @@ -64,12 +65,13 @@ pub fn decipher(address: &str, port: u16, original_xor_operand: &[u8, ..16], cip x_prime_block[byte] = v ^ (padding_value as u8); decipher_block[byte] = x_prime_block[byte] ^ original_xor_operand[byte]; - // We set the processed bytes of the forged XOR operand to have the next padding value. + // We set the processed bytes of the forged XOR operand to have the next padding value (2, 3, 4, etc..). for i in range(16 - padding_value, 16) { forged_xor_operand(&mut final_packet)[i] = x_prime_block[i] ^ ((padding_value as u8) + 1); } - // Special case for the first byte: we have to test all the values. + // Special case for the first byte: we have to test all the values to avoid a valid padding + // which is not [.., 0x01], for instance [.., 0x02, 0x02]. It's a very rare case but not impossible. if byte == 15 { first_byte = forged_xor_operand(&mut final_packet)[15]; } else { diff --git a/rapport/main.tex b/rapport/main.tex index e83451d..16b8e6c 100644 --- a/rapport/main.tex +++ b/rapport/main.tex @@ -5,6 +5,7 @@ \usepackage[T1]{fontenc} \usepackage{lmodern} +\usepackage{graphicx} \usepackage{listings} \usepackage{url} @@ -28,11 +29,13 @@ Nous utiliseront \emph{AES-256} en mode \emph{CBC} pour chiffrer les données ai %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Protocol simulator} +\section{Simulation du protocole} + +\subsection{Utilisation du code} \subsection{Structure du code} -\subsection{Quelle est la stratégie recommendée en pratique parmi les trois listées ci après ?} +\subsection{Quelle est la stratégie recommandée en pratique parmi les trois listées ci après ?} \begin{itemize} \item \emph{MAC-and-Encrypt} : $Enc(M)|MAC(M)$ ; @@ -65,21 +68,52 @@ Via une \emph{replay attack} en modifiant le \emph{timestamp} pour qu'il soit va \subsection{À la place d'utiliser un \emph{IV} aléatoire, le mode \emph{CBC} implémente une approche basée sur un \emph{nonce}. Que peut-on dire de sa sécurité ?} +TODO \subsection{Remarques concernant la sécurité de notre protocole} +TODO + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -\section{Utilisation du serveur comme d'un Oracle de déchiffrement} +\section{Utilisation du serveur comme d'un oracle de déchiffrement} \subsection{Historique de l'attaque par oracle à l'aide du remplissage} +TODO \subsection{Explication de l'attaque pour notre cas} -\subsection{Calcul de la complexité moyenne de l'attaque} +Le but est de faire décoder tout ou une partie d'un message chiffré intercepté par un oracle. Le décryptage se fait par bloc de 16 octets et nécessite le bloc chiffré le précédant ou l'\emph{IV} dans le cas du premier bloc. Pour notre test nous partons du principe que l'attaquant a intercepté un paquet chiffré, qu'il en a compris la structure et qu'il a deviné que l'\emph{IV} correspondait au \emph{timestamp}. + +Nous utilisons une attaque basé sur l'information renvoyé par l'oracle concernant la présence d'un bourrage valide. D'après le protocole un \emph{MAC} est calculé à partir des données non-bourrées puis le bourrage est ajouté pour obtenir une taille multiple de 16 et finalement les données et le bourrage sont chiffrés. Lors du déchiffrement par l'oracle, les données sont d'abord déchiffrées puis le bourrage est contrôlé, s'il n'est pas valide un paquet d'erreur et renvoyé au client (\emph{CryptError}). Si le bourrage est correct alors celui ci est retiré et les données restantes sont authentifiée à l'aide de la \emph{MAC}, si l'authentification échoue alors un paquet d'erreur et renvoyé au client (\emph{AuthError}). + +La valeur des octets du bourrage correspond à sa taille, par exemple un bourrage de longueur trois est représenté par \emph{[0x03, 0x03, 0x03]}. Si les données avant bourrage sont déjà multiple de 16 alors un bourrage de longueur 16 est ajouté de sorte qu'un bourrage soit toujours présent. + +\begin{figure} +\begin{center} +\includegraphics[scale=0.6]{diagramme_AES-CBC.eps} +\caption{\label{diagramme_AES-CBC} \textit{Décryptage par un oracle, AES-CBC.}} + +\end{center} +\end{figure} + +La figure \ref{diagramme_AES-CBC} illustre la structure de l'attaque. \emph{IV} et \emph{D} n'ont pas d'importance dans notre cas. \emph{X} est le bloc à décrypter, \emph{X'} est le bloc à décrypter après avoir été décodé par \emph{AES} mais avant d'avoir été \flqq xoré \frqq par \emph{F}. \emph{F} correspond à un bloc qui sera forgé par nos soins durant le décryptage de \emph{X}. De plus \emph{C}, qui n'est pas illustré sur le schéma, correspond au bloc précédent \emph{X} ou à l'\emph{IV} si \emph{X} est le premier bloc et \emph{R} correspond au message décrypté. + +dans un premier temps nous allons chercher le premier octet $b$ de $F$ noté $F_{1}$ en itérant celui ci de 0 à 255. Pour chaque itération un paquet de commande est envoyé à l'oracle comprenant en guise de données chiffrée $F + X$, le paquet d'erreur renvoyé va nous indiquer si le bourrage est correct (\emph{AuthError}) ou s'il ne l'est pas (\emph{CryptError}). Pour le premier octet nous allons chercher le bourrage \emph{[0x01]}, pour le deuxième le bourrage \emph{[0x02, 0x02]} et ainsi de suite jusqu'à l'octet 16. + +Dès qu'un paquet d'erreur \emph{AuthError} est reçu alors nous pouvons calculer $X'_{1} = F_{1} \oplus b$ puis le premier octet de notre message décrypté $R_{1} = X'_{1} \oplus C_{1}$. Avant de passer à l'octet suivant $b' = b + 1$ il faut s'assurer que les $b$ premiers octets de \emph{E} vaudront bien $b'$ lors du décryptage par l'oracle, pour ce faire on met à jour \emph{F} comme ceci : $\forall i \in [1, \ldots, b], F_{i} = b' \oplus X_{i}$. + +Une subtilité existe pour la recherche du premier octet, il est possible que le paquet d'erreur \emph{AuthError} correspond, avec une faible probabilité, à un autre bourrage que \emph{[0x01]}. Pour prévenir ce cas il faut, pour ce premier octet, envoyer un paquet de commande pour toutes les valeurs de $F_{1}$ et compter le nombre de paquet d'erreur \emph{AuthError} reçu. Si ce nombre est égal à 1 alors on peut passer à $b'$, sinon il faut recommencer en modifiant $F_{2} = (F_{2} + 1) mod 256$. + + +\subsection{Calcul de la complexité moyenne de l'attaque en terme de nombre de requête effectué auprès de l'oracle} + +Sans prendre en compte la particularité du premier octet illustré à la section précédente, la complexité moyenne pour le décryptage d'un bloc de 16 octets est de $16 * 256 / 2 = 2048$ requêtes. + +Dans le cas présenté dans le code, le nombre de requête est de 1795. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%