Initiation à la cryptographie

Introduction

La cryptographie est la science qui utilise des constructions mathématiques (codes) pour sécuriser la communication. Le domaine de la cryptographie est un sous-ensemble du domaine de la sécurité de l’information.

Il existe de nombreuses opérations cryptographiques possibles ; quelques exemples les plus connus sont:

  • Chiffrement : transformer un message en clair en un message chiffré afin que le message reste confidentiel
  • Déchiffrement : retransformer un message chiffré en message clair
  • Hachage sécurisé : effectuant une compression irréversible (unidirectionnelle) pour créer une représentation de taille statique et de calcul distincte pour un message spécifique.

La cryptographie est basée sur les mathématiques et l’arithmétique est fréquemment utilisée dans les algorithmes liés à la cryptographie. Il existe un petit sous-ensemble de primitives, schémas et protocoles utilisés par les développeurs. Les développeurs n’implémentent généralement pas les algorithmes eux-mêmes, mais utilisent les schémas et protocoles fournis par les API cryptographiques et les runtimes.

Un primitif pourrait être un chiffrement par bloc tel que AES. Une primitive est un algorithme utilisé comme bloc de construction pour un schéma cryptographique. Un schéma est par exemple un chiffrement par bloc mode de fonctionnement tel que CBC ou GCM. Un ou plusieurs schémas cryptographiques peuvent constituer un protocole cryptographique. Un protocole tel que TLS utilise de nombreux schémas cryptographiques, mais également des techniques d’encodage/décodage des messages, l’ordre des messages, les conditions d’utilisation, etc. Les API cryptographiques de bas niveau fournissent simplement un accès direct aux primitives tandis que les API de haut niveau peuvent offrir un accès au protocole complet implémentations.

Les messages sont chiffrés et déchiffrés à la main depuis l’invention de l’écrit. Les dispositifs mécaniques ont été utilisés au moins depuis la société grecque antique. Ce type de cryptographie est appelé cryptographie classique. De nombreuses introductions à la cryptographie commencent par la cryptographie classique car elle est relativement facile à analyser. Les algorithmes classiques ne respectent cependant pas la sécurité requise par les constructions modernes et sont souvent faciles à casser. Des exemples de schémas classiques sont le César et le Vigenère. Le dispositif mécanique le plus connu est sans aucun doute la machine à coder Enigma.

La cryptographie moderne est basée sur la science - principalement les mathématiques et la théorie des nombres/groupes. Cela implique des algorithmes et des tailles de clé beaucoup plus complexes. Ceux-ci ne peuvent être gérés efficacement que par des appareils informatiques. Pour cette raison, la cryptographie moderne utilise principalement des entrées et des sorties orientées octets. Cela signifie que les messages doivent être convertis en binaire et inversement avant de pouvoir être transformés par toute implémentation d’un algorithme cryptographique. Cela signifie que les messages (textuels) doivent être transformés à l’aide d’un codage de caractères avant d’être chiffrés.

Les formes de * codage de caractères * des messages textuels sont UTF-8. Les messages structurés peuvent être codés à l’aide de représentations XML ASN.1 / DER ou canoniques - ou d’un certain nombre de techniques propriétaires. Parfois, la sortie binaire doit également être retransformée en texte. Dans ce cas, un schéma de codage tel que la base 64 ou les hexadécimaux peut être utilisé pour représenter les données binaires dans le texte.

La cryptographie est notoirement difficile à maîtriser. Les développeurs ne doivent utiliser que des constructions qu’ils comprennent parfaitement. Si possible, un développeur doit utiliser un protocole de niveau supérieur tel que TLS pour créer la sécurité du transport. Il n’y a aucune chance pratique de créer un algorithme, un schéma ou un protocole sécurisé sans éducation formelle ou expérience approfondie. Copier/coller des exemples provenant d’Internet ne mènera probablement pas à des solutions sécurisées et peut même entraîner une perte de données.

Intégrité validée - Clé symétrique - Exemple de chiffrement et de déchiffrement à l’aide de Java

Le cryptage est utilisé pour transformer les données dans leur format d'origine (par exemple : le contenu d'une lettre, les informations d'identification faisant partie de l'autorisation d'une transaction financière) en quelque chose qui ne peut pas être facilement reconstruit par quiconque n'est pas destiné à faire partie de la conversation.

Fondamentalement, le chiffrement est utilisé pour empêcher l'écoute clandestine entre deux entités (individus ou groupe).

En cas de chiffrement symétrique, l'expéditeur et le destinataire (par exemple : Alice, Bob) doivent utiliser le même algorithme de chiffrement (généralement standardisé) et la même clé de chiffrement (connue uniquement d'eux deux).

http://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#Examples

Liens associés

  • https://en.wikipedia.org/wiki/History_of_cryptography
  • https://en.wikipedia.org/wiki/Cryptography
package com.example.so.documentation.cryptography;

import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.StringTokenizer;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;


/**
 * 
 * <p> Encryption is used to transform data in its orignal format (Eg: The contents of a letter, Credentials part of authorizing a financial transaction) to something that 
 * cannot be easily reconstructed by anyone who is not intended to be part of the conversation.  </p>
 * <p> Basically encryption is used to prevent eavesdropping between any two entities 
 * (individuals or a group). </p> 
 * 
 * <p> In case of symmetric encryption, both the sender and receiver (Eg: Alice, Bob) must use the same encryption algorithm (generally a standardised one) 
 * and the same encryption key (known only to the two of them). </p>
 * 
 * <p> http://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#Examples </p>
 * 
 * <p> Related Links </p>
 * <ul>
 *     <li>https://en.wikipedia.org/wiki/History_of_cryptography</li>
 *     <li>https://en.wikipedia.org/wiki/Cryptography</li>
 * </ul>
 * 
 * <pre>
 *         ChangeLog : 2016-09-24
 *         1. The modified encrypted text is now reflected correctly in the log and also updated same in javadoc comment.
 * </pre>
 * @author Ravindra HV (with inputs w.r.t integrity check from ArtjomB[http://stackoverflow.com/users/1816580/artjom-b])
 * @since (30 July 2016)
 * @version 0.3
 *
 */
public class IntegrityValidatedSymmetricCipherExample {
    
    /**
     * <p>https://en.wikipedia.org/wiki/Advanced_Encryption_Standard</p>
     */
    private static final String SYMMETRIC_ENCRYPTION_ALGORITHM_NAME = "AES"; // The current standard encryption algorithm (as of writing)
    
    /**
     * <p>Higher the number, the better</p>
     * <p>Encryption is performed on chunks of data defined by the key size</p>
     * <p>Higher key sizes may require modification to the JDK (Unlimited Strength Cryptography)</p>
     *         
     */
    private static final int SYMMETRIC_ENCRYPTION_KEY_SIZE = 128; // lengths can be 128, 192 and 256
    
    /**
     * <p> 
     *         A transformation defines in what manner the encryption should be performed.
     * </p>
     * <p>
            Eg: Whether there is any link between two chunks of encrypted data (CBC) or what should happen 
     *         if there is a mismatch between the key-size and the data length.       * 
     * </p>
     * 
     * <p> https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation </p>
     */
    private static final String SYMMETRIC_ENCRYPTION_TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final Charset CHARSET_INSTANCE_UTF8 = Charset.forName("UTF-8");
    
    
    private static final int AES_IV_KEY_SIZE = 128; // for AES, iv key size is fixed at 128 independent of key-size
    
    private static final String MAC_ALGORITHM_NAME__HMAC_SHA256 = "HmacSHA256";
    private static final String HASH_FIELDS_SEPARATOR = "|" ;
    
    
    

    /**
     * @param args
     * <p>Sample output.</p>
     * <pre>
Encrypted, Base64 encoded text :W1DePjeYMlI6xmyq9jr+cw==|55F80F4C2987CC143C69563025FACE22|GLR3T8GdcocpsTM1qSXp5jLsNx6QRK880BtgnV1jFg0=
Decrypted text :helloworld
Encrypted, Base64 encoded text - v2:1XX/A9BO1Cp8mK+SHh9iHA==|B8294AC9967BB57D714ACCB3EE5710BD|TnjdaWbvp+H6yCbAAQFMkWNixeW8VwmW48YlKA/AAyw=
Decrypted text  - v2:helloworld
Encrypted, Base64 encoded text - v3 (original):EU4+rAZ2vOKtoSDiDPcO+A==|AEEB8DD341D8D9CD2EDFA05A4595EBD2|7anESSSJf1dHobS5tDdQ1mCNkFcIgCvtNC/p79xJi5U=
Encrypted, Base64 encoded text - v3 (modified):FU4+rAZ2vOKtoSDiDPcO+A==|AEEB8DD341D8D9CD2EDFA05A4595EBD2|7anESSSJf1dHobS5tDdQ1mCNkFcIgCvtNC/p79xJi5U=
Error : Integrity check failed
Exception in thread "main" java.lang.RuntimeException: Error : Integrity check failed
    at com.example.so.documentation.cryptography.IntegrityValidatedSymmetricCipherExampleThree.decrypt(IntegrityValidatedSymmetricCipherExampleThree.java:165)
    at com.example.so.documentation.cryptography.IntegrityValidatedSymmetricCipherExampleThree.main(IntegrityValidatedSymmetricCipherExampleThree.java:126)
     * </pre>
     */
    public static void main(String[] args) {
        
        /*
         * EncryptionKey : Shared secret between receiver and sender (who generates the password and how its shared depends on the purpose)
         * This program generates a new one every time its run ! 
         * Normally it would be generated once and then be stored somewhere (Eg: In a JCEKS keystore file).
         */
        byte[] generatedSharedSecret = secretKeyGeneratorUtility();
        byte[] generatedSharedHMACKey = secretKeyGeneratorUtility();
        String plainText = "helloworld";
        
        String encryptedText = encrypt(plainText, generatedSharedSecret, generatedSharedHMACKey);
        System.out.println("Encrypted, Base64 encoded text :"+encryptedText);
        String decryptedText = decrypt(encryptedText, generatedSharedSecret, generatedSharedHMACKey);
        System.out.println("Decrypted text :"+decryptedText);
        
        String encryptedTextTwo = encrypt(plainText, generatedSharedSecret, generatedSharedHMACKey);
        System.out.println("Encrypted, Base64 encoded text - v2:"+encryptedTextTwo);
        String decryptedTextTwo = decrypt(encryptedTextTwo, generatedSharedSecret, generatedSharedHMACKey);
        System.out.println("Decrypted text  - v2:"+decryptedTextTwo);
        
        String encryptedTextThree = encrypt(plainText, generatedSharedSecret, generatedSharedHMACKey);
        System.out.println("Encrypted, Base64 encoded text - v3 (original):"+encryptedTextThree);
        char[] encryptedTextThreeChars = encryptedTextThree.toCharArray();
        encryptedTextThreeChars[0] = (char) ((encryptedTextThreeChars[0])+1);
        String encryptedTextThreeModified = new String(encryptedTextThreeChars);
        System.out.println("Encrypted, Base64 encoded text - v3 (modified):"+encryptedTextThreeModified);
        
        String decryptedTextThree = decrypt(encryptedTextThreeModified, generatedSharedSecret, generatedSharedHMACKey);
        System.out.println("Decrypted text  - v3:"+decryptedTextThree);

    }
    
    
    public static String encrypt(String plainText, byte[] key, byte[] hmacKey) {
        
        byte[] plainDataBytes = plainText.getBytes(CHARSET_INSTANCE_UTF8);
        byte[] iv = initializationVectorGeneratorUtility();
        byte[] encryptedDataBytes = encrypt(plainDataBytes, key, iv);
        
        String initializationVectorHex = DatatypeConverter.printHexBinary(iv);
        String encryptedBase64EncodedString = DatatypeConverter.printBase64Binary(encryptedDataBytes); // Generally the encrypted data is encoded in Base64 or hexadecimal encoding for ease of handling.
        String hashInputString = encryptedBase64EncodedString + HASH_FIELDS_SEPARATOR + initializationVectorHex + HASH_FIELDS_SEPARATOR;
        String hashedOutputString =  DatatypeConverter.printBase64Binary(messageHashWithKey(hmacKey, hashInputString.getBytes(CHARSET_INSTANCE_UTF8)));
        String encryptionResult = hashInputString + hashedOutputString;  
        return encryptionResult;
    }
    
    public static byte[] encrypt(byte[] plainDataBytes, byte[] key, byte[] iv) {
        byte[] encryptedDataBytes = encryptOrDecrypt(plainDataBytes, key, iv, true);
        return encryptedDataBytes;
    }

    
    public static String decrypt(String cipherInput, byte[] key, byte[] hmacKey) {
        StringTokenizer stringTokenizer = new StringTokenizer(cipherInput, HASH_FIELDS_SEPARATOR);
        
        String encryptedString = stringTokenizer.nextToken();
        String initializationVectorHex = stringTokenizer.nextToken();
        String hashedString = stringTokenizer.nextToken();

        String hashInputString = encryptedString + HASH_FIELDS_SEPARATOR + initializationVectorHex + HASH_FIELDS_SEPARATOR;
        String hashedOutputString =  DatatypeConverter.printBase64Binary(messageHashWithKey(hmacKey, hashInputString.getBytes(CHARSET_INSTANCE_UTF8)));

        if( hashedString.equals(hashedOutputString) == false ) {
            String message = "Error : Integrity check failed";
            System.out.println(message);
            throw new RuntimeException(message);
        }
        
        byte[] encryptedDataBytes = DatatypeConverter.parseBase64Binary(encryptedString); // The Base64 encoding must be reversed so as to reconstruct the raw bytes.
        byte[] iv = DatatypeConverter.parseHexBinary(initializationVectorHex);
        byte[] plainDataBytes = decrypt(encryptedDataBytes, key, iv);
        String plainText = new String(plainDataBytes, CHARSET_INSTANCE_UTF8);
        return plainText;
    }

    public static byte[] decrypt(byte[] encryptedDataBytes, byte[] key, byte[] iv) {
        byte[] decryptedDataBytes = encryptOrDecrypt(encryptedDataBytes, key, iv, false);
        return decryptedDataBytes;
    }
    

    public static byte[] encryptOrDecrypt(byte[] inputDataBytes, byte[] key, byte[] iv, boolean encrypt) {
        byte[] resultDataBytes = null;
        
        // Exceptions, if any, are just logged to console for this example.
        try {
            Cipher cipher = Cipher.getInstance(SYMMETRIC_ENCRYPTION_TRANSFORMATION);
            SecretKey secretKey = new SecretKeySpec(key, SYMMETRIC_ENCRYPTION_ALGORITHM_NAME);
            AlgorithmParameterSpec algorithmParameterSpec = new IvParameterSpec(iv);
            if(encrypt) {
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, algorithmParameterSpec);    
            }
            else {
                cipher.init(Cipher.DECRYPT_MODE, secretKey, algorithmParameterSpec);
            }
            
            resultDataBytes = cipher.doFinal(inputDataBytes); // In relative terms, invoking do-final in one go is fine as long as the input size is small.
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        
        return resultDataBytes;
    }

    
    private static byte[] secretKeyGeneratorUtility() {
        byte[] keyBytes = null;
        
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(SYMMETRIC_ENCRYPTION_ALGORITHM_NAME);
            keyGenerator.init(SYMMETRIC_ENCRYPTION_KEY_SIZE);
            SecretKey secretKey = keyGenerator.generateKey();
            keyBytes = secretKey.getEncoded();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        
        return keyBytes;
        
    }
    
    
    /**
     * <p> InitialVector : Helps in avoiding generating the same encrypted result, even when the same encryption - algorithm and key are used. </p>
     * <p> Since this is also required to be known to both sender and receiver, its either based on some convention or is part of the cipher-text transmitted.</p>  
     * <p> https://en.wikipedia.org/wiki/Initialization_vector </p>
     * @return
     */
    private static byte[] initializationVectorGeneratorUtility() {
        byte[] initialVectorResult = null;
        
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(SYMMETRIC_ENCRYPTION_ALGORITHM_NAME);
            keyGenerator.init(AES_IV_KEY_SIZE);
            SecretKey secretKey = keyGenerator.generateKey();
            initialVectorResult = secretKey.getEncoded();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        
        return initialVectorResult;
    }
    
    
    private static byte[] messageHashWithKey(byte[] key, byte[] data) { // byte[] iv, 
        byte[] hmac = null;
        
        try {
            Mac mac = Mac.getInstance(MAC_ALGORITHM_NAME__HMAC_SHA256);
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, MAC_ALGORITHM_NAME__HMAC_SHA256);
            //AlgorithmParameterSpec algorithmParameterSpec = new IvParameterSpec(iv);
            mac.init(secretKeySpec); // algorithmParameterSpec
            hmac = mac.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } /*catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }*/
        
        return hmac;
        
    }

}