Java - Exemple de cryptographie hybride
La cryptographie hybride est la doublure argentée entre la cryptographie sûre mais lente sur les mégadonnées (cryptographie asymétrique) et la cryptographie dangereuse mais rapide (cryptographie symétrique). La cryptographie hybride combine la vitesse de chiffrement et de déchiffrement à une clé avec la sécurité fournie par la paire de clés publique-privée et est donc considérée comme un type de chiffrement hautement sécurisé.
La méthodologie la plus courante pour la cryptographie hybride consiste à chiffrer les données à l'aide d'une clé symétrique qui sera ensuite chiffrée avec la clé privée de l'expéditeur ou la clé publique du récepteur. Pour déchiffrer, le récepteur devra d'abord déchiffrer la clé symétrique à l'aide de la clé asymétrique correspondante, puis utiliser cette clé symétrique pour déchiffrer les données qu'il a reçues. Cet exemple pourrait aider à clarifier que:
Imaginez un scénario avec deux utilisateurs, Alice et Bob, qui ont déjà échangé des clés publiques. Alice veut envoyer à Bob un fichier texte appelé "confidential.txt". Les étapes qu'elle suivra pour crypter son message sont les suivantes:
-
Génère une nouvelle clé symétrique à l'aide d'un algorithme puissant
-
Crypte «confidential.txt» à l'aide de la clé de l'étape 1
-
Chiffre la clé de l'étape 1 à l'aide de la clé publique de Bob
-
Envoie à la fois le texte crypté et la clé à Bob
Bob reçoit les deux fichiers. Les étapes qu'il suivra pour les décrypter sont les suivantes:
-
Décrypte la clé qu'il a reçue en utilisant sa propre clé privée
-
Décrypte le texte qu'il a reçu à l'aide de la clé qu'il a obtenue à l'étape 1 après le décryptage
Note
Si vous voulez en savoir plus sur le cryptage symétrique et asymétrique, reportez-vous àSymmetric-Key Cryptography example etAsymmetric-Key Cryptography example
Les processus dans les codes ci-dessous sont divisés en fichiers séparés pour aider à comprendre le processus et à faciliter l'apprenant.
1. Répertoire des projets
2. Générer des clés pour Alice et Bob
Il existe plusieurs façons de générer une paire de clés publique-privée en fonction de votre plate-forme. Dans cet exemple, nous allons créer deux paires, une pour Alice et une pour Bob en utilisant Java. L'algorithme cryptographique que nous utiliserons dans cet exemple est RSA.
GenerateKeys.java
package com.example.keypair; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; public class GenerateKeys { private KeyPairGenerator keyGen; private KeyPair pair; private PrivateKey privateKey; private PublicKey publicKey; public GenerateKeys(int keylength) throws NoSuchAlgorithmException, NoSuchProviderException { this.keyGen = KeyPairGenerator.getInstance("RSA"); this.keyGen.initialize(keylength); } public void createKeys() { this.pair = this.keyGen.generateKeyPair(); this.privateKey = pair.getPrivate(); this.publicKey = pair.getPublic(); } public PrivateKey getPrivateKey() { return this.privateKey; } public PublicKey getPublicKey() { return this.publicKey; } public void writeToFile(String path, byte[] key) throws IOException { File f = new File(path); f.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(f); fos.write(key); fos.flush(); fos.close(); } public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, IOException { GenerateKeys gk_Alice; GenerateKeys gk_Bob; gk_Alice = new GenerateKeys(1024); gk_Alice.createKeys(); gk_Alice.writeToFile("KeyPair/publicKey_Alice", gk_Alice.getPublicKey().getEncoded()); gk_Alice.writeToFile("KeyPair/privateKey_Alice", gk_Alice.getPrivateKey().getEncoded()); gk_Bob = new GenerateKeys(1024); gk_Bob.createKeys(); gk_Bob.writeToFile("KeyPair/publicKey_Bob", gk_Bob.getPublicKey().getEncoded()); gk_Bob.writeToFile("KeyPair/privateKey_Bob", gk_Bob.getPrivateKey().getEncoded()); } }
Sortie:
3. Alice génère une clé symétrique
Avec le code ci-dessous, vous pouvez générer une clé symétrique. Le code utiliseSecureRandom
pour ajouter du caractère aléatoire à la clé. Pour plus d'informations surSecureRandom
, reportez-vous àJava – How to create strong random numbers.
Le constructeur est initialisé avec le chemin d'accès au répertoire où la clé sera enregistrée, la longueur de la clé et l'algorithme qui sera utilisé pour la créer. Pour en savoir plus sur les longueurs de clé de chaque algorithme, reportez-vous àImport Limits on Cryptographic Algorithms.
Dans cet exemple, nous utilisons AES car il est considéré comme la doublure argentée entre vitesse et sécurité. Si vous souhaitez en savoir plus sur les algorithmes de chiffrement, reportez-vous àPerformance Analysis of Data Encryption Algorithms: 2.5 Compared Algorithms.
GenerateSymmetricKey.java
package com.example.onekey; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; public class GenerateSymmetricKey { private SecretKeySpec secretKey; public GenerateSymmetricKey(int length, String algorithm) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException { SecureRandom rnd = new SecureRandom(); byte [] key = new byte [length]; rnd.nextBytes(key); this.secretKey = new SecretKeySpec(key, algorithm); } public SecretKeySpec getKey(){ return this.secretKey; } public void writeToFile(String path, byte[] key) throws IOException { File f = new File(path); f.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(f); fos.write(key); fos.flush(); fos.close(); } public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException { GenerateSymmetricKey genSK = new GenerateSymmetricKey(16, "AES"); genSK.writeToFile("OneKey/secretKey", genSK.getKey().getEncoded()); } }
Sortie:
4. Alice écrit son message
5. Alice crypte les données
5.1 The StartEncryption
class contains the three methods getPrivate()
, getPublic()
, getSecretKey()
that are used to build the corresponding keys from files. Dans la méthode principale, nous construisons lesFile objects
que nous devons utiliser pour les deux classes que nous appellerons pour le chiffrement; EncryptData
etEncryptKey
.
StartEncryption.java
package com.example.hybrid.encrypt; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.spec.SecretKeySpec; public class StartEncryption { public PrivateKey getPrivate(String filename, String algorithm) throws Exception { byte[] keyBytes = Files.readAllBytes(new File(filename).toPath()); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance(algorithm); return kf.generatePrivate(spec); } public PublicKey getPublic(String filename, String algorithm) throws Exception { byte[] keyBytes = Files.readAllBytes(new File(filename).toPath()); X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance(algorithm); return kf.generatePublic(spec); } public SecretKeySpec getSecretKey(String filename, String algorithm) throws IOException{ byte[] keyBytes = Files.readAllBytes(new File(filename).toPath()); return new SecretKeySpec(keyBytes, algorithm); } public static void main(String[] args) throws IOException, GeneralSecurityException, Exception{ StartEncryption startEnc = new StartEncryption(); File originalKeyFile = new File("OneKey/secretKey"); File encryptedKeyFile = new File("EncryptedFiles/encryptedSecretKey"); new EncryptKey(startEnc.getPublic("KeyPair/publicKey_Bob", "RSA"), originalKeyFile, encryptedKeyFile, "RSA"); File originalFile = new File("confidential.txt"); File encryptedFile = new File("EncryptedFiles/encryptedFile"); new EncryptData(originalFile, encryptedFile, startEnc.getSecretKey("OneKey/secretKey", "AES"), "AES"); } }
5.2 EncryptData
constructor receives as parameters two File
Objects (the first is the file we want to encrypt and the second is the output file), a SecretKeySpec
Object which is the Symmetric Key we want to use for the encryption and a String
with the name of the algorithm that we will use for the Cipher
.
EncryptData.java
package com.example.hybrid.encrypt; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.SecretKeySpec; public class EncryptData { private Cipher cipher; public EncryptData(File originalFile, File encrypted, SecretKeySpec secretKey, String cipherAlgorithm) throws IOException, GeneralSecurityException{ this.cipher = Cipher.getInstance(cipherAlgorithm); encryptFile(getFileInBytes(originalFile), encrypted, secretKey); } public void encryptFile(byte[] input, File output, SecretKeySpec key) throws IOException, GeneralSecurityException { this.cipher.init(Cipher.ENCRYPT_MODE, key); writeToFile(output, this.cipher.doFinal(input)); } private void writeToFile(File output, byte[] toWrite) throws IllegalBlockSizeException, BadPaddingException, IOException{ output.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(output); fos.write(toWrite); fos.flush(); fos.close(); System.out.println("The file was successfully encrypted and stored in: " + output.getPath()); } public byte[] getFileInBytes(File f) throws IOException{ FileInputStream fis = new FileInputStream(f); byte[] fbytes = new byte[(int) f.length()]; fis.read(fbytes); fis.close(); return fbytes; } }
5.3 EncryptKey
constructor receives as parameters the PublicKey
that will be used for the encryption, two File
Objects (the first is the file that contains the Symmetric Key and the second is the output file), and a String
with the name of the algorithm that we will use for the Cipher
.
EncryptKey.java
package com.example.hybrid.encrypt; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PublicKey; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; public class EncryptKey { private Cipher cipher; public EncryptKey(PublicKey key, File originalKeyFile, File encryptedKeyFile, String cipherAlgorithm) throws IOException, GeneralSecurityException{ this.cipher = Cipher.getInstance(cipherAlgorithm); encryptFile(getFileInBytes(originalKeyFile), encryptedKeyFile, key); } public void encryptFile(byte[] input, File output, PublicKey key) throws IOException, GeneralSecurityException { this.cipher.init(Cipher.ENCRYPT_MODE, key); writeToFile(output, this.cipher.doFinal(input)); } private void writeToFile(File output, byte[] toWrite) throws IllegalBlockSizeException, BadPaddingException, IOException{ output.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(output); fos.write(toWrite); fos.flush(); fos.close(); System.out.println("The key was successfully encrypted and stored in: " + output.getPath()); } public byte[] getFileInBytes(File f) throws IOException{ FileInputStream fis = new FileInputStream(f); byte[] fbytes = new byte[(int) f.length()]; fis.read(fbytes); fis.close(); return fbytes; } }
Sortie:
The key was successfully encrypted and stored in: EncryptedFiles\encryptedSecretKey The file was successfully encrypted and stored in: EncryptedFiles\encryptedFile
6. Bob reçoit les données et les déchiffre
6.1 The StartDecryption
class contains the three methods getPrivate()
, getPublic()
, getSecretKey()
that are used to build the corresponding keys from files. Dans la méthode principale, nous construisons les objetsFile
que nous devons utiliser pour les deux classes que nous appellerons pour le déchiffrement; DecryptData
etDecryptKey
.
StartDecryption.java
package com.example.hybrid.decrypt; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.spec.SecretKeySpec; public class StartDecryption { public PrivateKey getPrivate(String filename, String algorithm) throws Exception { byte[] keyBytes = Files.readAllBytes(new File(filename).toPath()); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance(algorithm); return kf.generatePrivate(spec); } public PublicKey getPublic(String filename, String algorithm) throws Exception { byte[] keyBytes = Files.readAllBytes(new File(filename).toPath()); X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance(algorithm); return kf.generatePublic(spec); } public SecretKeySpec getSecretKey(String filename, String algorithm) throws IOException{ byte[] keyBytes = Files.readAllBytes(new File(filename).toPath()); return new SecretKeySpec(keyBytes, algorithm); } public static void main(String[] args) throws IOException, GeneralSecurityException, Exception{ StartDecryption startEnc = new StartDecryption(); File encryptedKeyReceived = new File("EncryptedFiles/encryptedSecretKey"); File decreptedKeyFile = new File("DecryptedFiles/SecretKey"); new DecryptKey(startEnc.getPrivate("KeyPair/privateKey_Bob", "RSA"), encryptedKeyReceived, decreptedKeyFile, "RSA"); File encryptedFileReceived = new File("EncryptedFiles/encryptedFile"); File decryptedFile = new File("DecryptedFiles/decryptedFile"); new DecryptData(encryptedFileReceived, decryptedFile, startEnc.getSecretKey("DecryptedFiles/SecretKey", "AES"), "AES"); } }
6.2 DecryptKey
constructor receives as parameters the PrivateKey
that will be used for the decryption, two File
Objects (the first is the encrypted key and the second is the output file) and a String
with the name of the algorithm that we will use for the Cipher
.
DecryptKey.java
package com.example.hybrid.decrypt; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PrivateKey; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; public class DecryptKey { private Cipher cipher; public DecryptKey(PrivateKey privateKey, File encryptedKeyReceived, File decreptedKeyFile, String algorithm) throws IOException, GeneralSecurityException { this.cipher = Cipher.getInstance(algorithm); decryptFile(getFileInBytes(encryptedKeyReceived), decreptedKeyFile, privateKey); } public void decryptFile(byte[] input, File output, PrivateKey key) throws IOException, GeneralSecurityException { this.cipher.init(Cipher.DECRYPT_MODE, key); writeToFile(output, this.cipher.doFinal(input)); } private void writeToFile(File output, byte[] toWrite) throws IllegalBlockSizeException, BadPaddingException, IOException{ output.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(output); fos.write(toWrite); fos.flush(); fos.close(); } public byte[] getFileInBytes(File f) throws IOException{ FileInputStream fis = new FileInputStream(f); byte[] fbytes = new byte[(int) f.length()]; fis.read(fbytes); fis.close(); return fbytes; } }
6.3 DecryptData
constructor receives as parameters two File
Objects (the first is the encrypted file and the second is the output file), a SecretKeySpec
Object which is the Symmetric Key we want to use for the decryption and a String
with the name of the algorithm that we will use for the Cipher.
DecryptData.java
package com.example.hybrid.decrypt; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.spec.SecretKeySpec; public class DecryptData { private Cipher cipher; public DecryptData(File encryptedFileReceived, File decryptedFile, SecretKeySpec secretKey, String algorithm) throws IOException, GeneralSecurityException { this.cipher = Cipher.getInstance(algorithm); decryptFile(getFileInBytes(encryptedFileReceived), decryptedFile, secretKey); } public void decryptFile(byte[] input, File output, SecretKeySpec key) throws IOException, GeneralSecurityException { this.cipher.init(Cipher.DECRYPT_MODE, key); writeToFile(output, this.cipher.doFinal(input)); } private void writeToFile(File output, byte[] toWrite) throws IllegalBlockSizeException, BadPaddingException, IOException{ output.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(output); fos.write(toWrite); fos.flush(); fos.close(); System.out.println("The file was successfully decrypted. You can view it in: " + output.getPath()); } public byte[] getFileInBytes(File f) throws IOException{ FileInputStream fis = new FileInputStream(f); byte[] fbytes = new byte[(int) f.length()]; fis.read(fbytes); fis.close(); return fbytes; } }
Sortie:
The file was successfully decrypted. You can view it in: DecryptedFiles\decryptedFile
Télécharger le code source
Téléchargez-le -HybridCryptographyExample.zip (11 Ko)