La cryptographie : qu'est-ce que c'est et comment ça marche ? Part I

La cryptographie : qu'est-ce que c'est et comment ça marche ? Part I

Un aperçu de haut niveau avec des exemples utilisant Java

Vous êtes-vous déjà demandé comment les entreprises stockent vos mots de passe en toute sécurité ? Ou comment les informations de votre carte de crédit sont-elles gardées confidentielles lorsque vous effectuez des achats en ligne ?

La réponse est la cryptographie. La grande majorité des sites internet utilisent désormais une forme de cryptographie pour garantir la confidentialité de ses utilisateurs. Même les informations telles que les e-mails de votre compte Gmail sont chiffrées lorsqu'elles circulent dans les centres de données de Google.

Terminologie cryptographique

  • La cryptographie utilise des concepts issus de nombreux domaines (Informatique, Mathématiques, Electronique).
  • Cryptologie : Il s'agit d'une science mathématique comportant deux branches : la cryptographie et la cryptanalyse.

La Cryptographie : c'est une discipline incluant les principes, les moyens et des méthodes de transformation des données, dans le but :

  • de masquer leur contenu (Confidentialité),
  • d'empêcher leur modification (Intégrité),
  • ou leur utilisation illégale (Authentification et Non-Répudiation).

    Cryptanalyse : c'est l'étude des faiblesses des outils de sécurité (algorithmes) produits par la cryptographie dans le but de les corriger ou nuire au système de communication .

  • Chiffrement : Le chiffrement consiste à transformer une donnée (texte, message, ...) afin de la rendre incompréhensible par une personne autre que celui qui a créé le message et celui qui en est le destinataire.
  • Déchiffrement: C'est la fonction permettant de retrouver le texte clair à partir du texte chiffré.
  • Clé : Il s'agit du paramètre impliqué et autorisant des opérations de chiffrement et/ou déchiffrement.
  • Dans le cas d'un algorithme symétrique, la clef est identique lors des deux opérations.
  • Dans le cas d'algorithmes asymétriques, elle diffère pour les deux opérations.

Algorithmes de chiffrement

  1. Chiffrement à clé symétrique — Dans les algorithmes à clé symétrique, il existe une clé commune, utilisée pour verrouiller et déverrouiller la « boîte » de chiffrement. L'expéditeur et le destinataire ont la même clé.

Les algorithmes à clé symétrique sont très rapides car les clés n'ont pas besoin d'être très longues ; cependant, il existe le problème du partage de la clé commune en premier lieu car elle pourrait être interceptée et ensuite l'ensemble du système est compromis.

Les algorithmes les plus répandus sont le DES, AES(Que nous allons implémenter dans cet article🙂), 3DES, ... La taille des clés est souvent de l'ordre de 128 bits. Le DES en utilise 56, mais l'AES peut aller jusque 256.

Ci dessous une illustration du canal de communication dans le système de cryptographie symétrique:

2-Chiffrement à clé asymétrique — Dans les algorithmes à clé asymétrique, seul le récepteur détient la clé.

Le destinataire peut envoyer publiquement une serrure (ou une méthode de verrouillage de notre hypothétique boîte), dont seul le destinataire détient la clé.

Le verrou s'appelle la clé publique et la clé s'appelle la clé privée. Remarque : Il n'y a qu'une seule clé publique pour chaque clé privée🤫.

L'algorithme de cryptographie asymétrique le plus connu est le RSA. La taille des clés s'étend de 1024 bits à 4096 bits en standard. Dans le cas du RSA, une clé de 1024 bits n'est plus sûre, mais est toujours utilisable de particulier à particulier.

Au niveau des performances, le chiffrement par voie asymétrique est environ 1000 fois plus lent que le chiffrement symétrique.

▶Schéma du système asymétrique (non symétrique):

Screenshot_20220121_181354.png

On en vient à la Pratique, génial 😀

Chiffrement:

  • J'ai un message M (fichier de n’importe quel type) à chiffrer.
  • J'appelle ma fonction de génération de clé qui me retourne ma clé de chffrement et/ou de déchiffrement.
  • Je chiffre M avec la clé K pour Obtenir le chiffré C.

Déchiffrement:

  • J’ai un message C à déchiffrer.
  • Je récupère ma clé K à l'endroit où je l'avais stockée.
  • Je déchiffre C avec la clé K pour Obtenir le clair M.

Java a mis en place une plateforme de cryptographie (JCA/JCE) permettant d’utiliser les standard cryptographiques du marché sans qu’on ait besoin de ré-implémenter toute la pile: une batterie de spécifications , d’interfaces, d’implémentations, …. nous permettant soit de fournir des services cryptographiques ou bien d’en utiliser.

Remarque: Ceci n'est pas un cours sur Java, je suppose que vous êtes à l'aise avec ce langage de programmation 🙂 sinon vous pouvez consultez la documentation.

Le programme:

On se propose de réaliser une Application composée essentiellement de trois fonctions:

  • Génération de clé: output clé symétrique
  • Chiffrement: input: clé symétrique, fichier source à chiffrer
  • Déchiffrement : input: clé symétrique, fichier source à déchiffrer

Pour se fixer les idées, on spécifie tout ça dans une Interface JAVA. Une fois la clé obtenue nous souhaitons utiliser AES comme schéma de chiffrement symétrique en mode CBC) avec le padding PKCS5.

package com.wone.sn;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
/**
 * @author mamadou-wone
 */
// #  ___       __   ________  ________   _______
// # |\  \     |\  \|\   __  \|\   ___  \|\  ___ \
// # \ \  \    \ \  \ \  \|\  \ \  \\ \  \ \   __/|
// #  \ \  \  __\ \  \ \  \\\  \ \  \\ \  \ \  \_|/__
// #   \ \  \|\__\_\  \ \  \\\  \ \  \\ \  \ \  \_|\ \
// #    \ \____________\ \_______\ \__\\ \__\ \_______\
// #     \|____________|\|_______|\|__| \|__|\|_______|

public interface ICipher {
    public static final IvParameterSpec iv = new IvParameterSpec("MamadouIbnHamire".getBytes());
    public static final String cipherAlgorithm = "AES/CBC/PKCS5Padding";
    public static final String keyAlgorithm = "AES";
    public static final int keySize = 128;

    /**
     * *
     * @param cipherAlgorithm L'algorithme de Chiffrement
     * @param iv Le vecteur d'initialisation
     * @param key la clé secrete
     * @param mode le mode ENCRYPT/DECRYPT
     * @return L'instance de Pour le Chiffrement ou Dechiffrement
     */
    public Cipher getCipher(String cipherAlgorithm, IvParameterSpec iv, SecretKey key, int mode);

    /**
     * *
     * @param c L'instance (Chiffrement ou Dechiffrement)
     * @param originalFilePath Le chemin du fichier à chiffrer
     * @param encryptFilePath Le chemin du fichier à enregistrer
     * @return True si l'opération s'est bien déroulée sinon false
     */
    public boolean encryptFile(Cipher c, String originalFilePath, String encryptFilePath);

    /**
     * *
     * @param c L'instance (Chiffrement ou Dechiffrement)
     * @param encryptFilePath Le chemin du fichier chiffrer
     * @param decryptedFilePath Le chemin du fichier déchiffrer
     * @return True si l'opération s'est bien déroulée sinon false
     */
    public boolean decryptFile(Cipher c, String encryptFilePath, String decryptedFilePath);

}
package com.wone.sn;

import javax.crypto.SecretKey;
/**
 * @author mamadou-wone
 */

public interface ICrypto {

    /**
     * Cette Méthode permet de générer une clé
     * @param algo : L'algorithme de chiffrement (ex:"AES","DES")
     * @param keySize : La Taille de la clé (ex: 128 ou 256)
     * @return : La clé générée
     */
    public SecretKey genKey(String algo, int keySize);

    /**
     *  Cette Méthode permet de stocké la clé dans un fichier
     * @param key: La clé générée
     * @param filePath: Le chemin choisi pour stocker le fichier contenant la clé
     * @return : Le chemin choisi
     */
    public String saveKey(SecretKey key, String filePath);

    /**
     * Cette Méthode permet de récupérer et reconstituer la clé stockée dans le fichier
     * @param filePath: Le chemin du fichier où est stocké notre clé
     * @return : La clé contenue dans le fichier
     */
    public SecretKey getKey(String filePath);

}

Ici On passe à une implémentation (des méthodes) de nos interfaces :

package com.wone.sn;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * @author mamadou-wone
 */
public class CipherImpl implements ICipher{

    @Override
    public Cipher getCipher(String cipherAlgorithm, IvParameterSpec iv, SecretKey key, int mode) {
        try {
            Cipher c = Cipher.getInstance(cipherAlgorithm);
            if (mode == 1) {
                c.init(Cipher.ENCRYPT_MODE, key, iv);
            } else {
                c.init(Cipher.DECRYPT_MODE, key, iv);
            }
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public boolean encryptFile(Cipher c, String originalFilePath, String encryptFilePath) {
        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(originalFilePath));
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(encryptFilePath));
            CipherOutputStream cos = new CipherOutputStream(bos, c);
            int r_lines = 0;
            while ((r_lines = bis.read()) != -1) {
                cos.write(r_lines);
            }
            cos.close();
            bos.close();
            bis.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean decryptFile(Cipher c, String encryptFilePath, String decryptedFilePath) {
        try {
            FileInputStream file = new FileInputStream(encryptFilePath);
            BufferedInputStream bais = new BufferedInputStream(file);
            CipherInputStream cis = new CipherInputStream(bais, c);
            BufferedOutputStream decrypt = new BufferedOutputStream(new FileOutputStream(decryptedFilePath));
            int lines = 0;
            while ((lines = cis.read()) != -1) {
                System.out.print((char) lines);
                decrypt.write((char) lines);
            }
            decrypt.close();
            cis.close();
            cis.close();
            file.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}
package com.wone.sn;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

/**
 * @author mamadou-wone
 */
public class CryptoImpl implements ICrypto{

    @Override
    public SecretKey genKey(String algo, int keySize) {
        try {
            KeyGenerator gen = KeyGenerator.getInstance(algo);
            gen.init(keySize);
            return gen.generateKey();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    @Override
    public String saveKey(SecretKey key, String filePath) {
        try {
            FileOutputStream fos = new FileOutputStream(filePath);
            ObjectOutputStream obj = new ObjectOutputStream(fos);
            obj.writeObject(key);
            obj.close();
            fos.close();
            return filePath;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    @Override
    public SecretKey getKey(String filePath) {
        try {
            FileInputStream fis = new FileInputStream(filePath);
            ObjectInputStream ois = new ObjectInputStream(fis);
            return (SecretKey) ois.readObject();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

}

Nous allons maintenant écrire la classe de Main pour pouvoir exécuter l'ensemble de notre code:

package com.wone.sn;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.swing.*;
import java.util.InputMismatchException;
import java.util.Scanner;

/**
 * @author mamadou-wone
 */
public class Main {

    public static void main(String[] args) {
        initOperation();
    }

    public static void initOperation() {
        int inputCount = 0;
        boolean start = true;
        while (start) {
            System.out.println("Hello que souhaitez vous faire ? ");
            System.out.println("*********************************** ");
            System.out.println("1 - Générer une clé ? ");
            System.out.println("2 - Chiffrer un fichier  ? ");
            System.out.println("3 - Déchiffrer un fichier ? ");
            try {
                Scanner sc = new Scanner(System.in);
                int input = sc.nextInt();
                switch (input) {
                    case 1 -> {
                        genKey();
                        start = false;
                    }
                    case 2 -> {
                        enCrypt();
                        start = false;
                    }
                    case 3 -> {
                        deCrypt();
                        start = false;
                    }
                    default -> {
                        System.out.println("Nous ne comprenons pas  votre choix :(");
                        start = false;
                    }
                }
            } catch (InputMismatchException e) {
                System.out.println("Cette action n'est pas permise :( . Suivez les insctruction SVP !");
                inputCount++;
                if (inputCount == 3) {
                    start = false;
                    System.out.println("Désolez nous ne pouvons pas traiter votre demande :(");
                }
            }
        }
    }

    public static void genKey() {
        CryptoImpl crypto = new CryptoImpl();
        SecretKey key = crypto.genKey(ICipher.keyAlgorithm, ICipher.keySize);
        JFileChooser jfc = new JFileChooser();
        jfc.setDialogTitle("Choisissez le chemin pour enregistrer la clé");
        int option = jfc.showSaveDialog(jfc);
        if (option == JFileChooser.APPROVE_OPTION) {
            String filePath = jfc.getSelectedFile().getAbsolutePath();
            crypto.saveKey(key, filePath);
            JOptionPane.showMessageDialog(null, "Votre clé a bien été générer", "InfoBox: ", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    public static void enCrypt() {
        try {
            CryptoImpl crypto = new CryptoImpl();
            SecretKey key = null;
            JFileChooser jfc = new JFileChooser();
            jfc.setDialogTitle("Choisissez la clé de chiffrement");
            int option = jfc.showOpenDialog(jfc);
            if (option == JFileChooser.APPROVE_OPTION) {
                String filePath = jfc.getSelectedFile().getAbsolutePath();
                key = crypto.getKey(filePath);
            }

            CipherImpl cipher = new CipherImpl();
            Cipher c = cipher.getCipher(ICipher.cipherAlgorithm, ICipher.iv, key, 1);
            jfc.setDialogTitle("Choisissez le fichier à chiffrer");
            option = jfc.showOpenDialog(jfc);
            String originalFilePath = "";
            String encryptedFilePath = "";

            if (option == JFileChooser.APPROVE_OPTION) {
                originalFilePath = jfc.getSelectedFile().getAbsolutePath();
            }

            jfc.setDialogTitle("Choisissez le chemin du fichier chiffrer pour l'enregistrer");
            option = jfc.showSaveDialog(jfc);
            if (option == JFileChooser.APPROVE_OPTION) {
                encryptedFilePath = jfc.getSelectedFile().getAbsolutePath();
            }
            cipher.encryptFile(c, originalFilePath, encryptedFilePath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void deCrypt() {
        try {
            CryptoImpl crypto = new CryptoImpl();
            SecretKey key = null;
            JFileChooser jfc = new JFileChooser();
            jfc.setDialogTitle("Choisissez la clé de déchiffrement");
            int option = jfc.showOpenDialog(jfc);
            if (option == JFileChooser.APPROVE_OPTION) {
                String filePath = jfc.getSelectedFile().getAbsolutePath();
                key = crypto.getKey(filePath);
            }
            CipherImpl cipher = new CipherImpl();
            Cipher c = cipher.getCipher(ICipher.cipherAlgorithm, ICipher.iv, key, 2);

            jfc.setDialogTitle("Choisissez le fichier à déchiffrer");
            option = jfc.showOpenDialog(jfc);
            String encryptedFilePath = "";
            String decryptedFilePath = "";

            if (option == JFileChooser.APPROVE_OPTION) {
                encryptedFilePath = jfc.getSelectedFile().getAbsolutePath();
            }

            jfc.setDialogTitle("Choisissez le chemin du fichier déchiffrer pour l'enregistré");
            option = jfc.showSaveDialog(jfc);
            if (option == JFileChooser.APPROVE_OPTION) {
                decryptedFilePath = jfc.getSelectedFile().getAbsolutePath();
            }

            cipher.decryptFile(c, encryptedFilePath, decryptedFilePath);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

Remarque: le code source est disponible ici 🙂.