Java – Hybrid Cryptography example
Hybrid Cryptography is the silver lining between safe, but slow cryptography over big data (Asymmetric Cryptography) and unsafe but fast cryptography (Symmetric Cryptography). Hybrid Cryptography combines the speed of One-Key encryption and decryption along with the security that the Public-Private Key pair provides and thus considered a highly secure type of encryption.
The most common methodology for Hybrid Cryptography is to encrypt the data using a Symmetric Key which will be then encrypted with the Private Key of the sender or the Public Key of the receiver. To decrypt, the receiver, will have to first decrypt the Symmetric key using the corresponding Asymmetric Key and then use that Symmetric Key to decrypt the data they received. This example might help clarify that:
Imagine a scenario with two users, Alice and Bob, who have already exchanged Public Keys. Alice wants to send Bob a text file called “confidential.txt”. The steps she will follow to encrypt her message are the following:
- Generates a new Symmetric Key using a strong algorithm
- Encrypts “confidential.txt” using the Key from step 1
- Encrypts the Key from step 1 using Bob’s Public Key
- Sends both the encrypted text and the key to Bob
Bob receives the two files. The steps he will follow to decrypt them are the following:
- Decrypts the Key he received using his own Private Key
- Decrypts the text he received using the Key he got in step 1 after the decryption
If you want to read more about Symmetric and Asymmetric Encryption, refer to Symmetric-Key Cryptography example and Asymmetric-Key Cryptography example
The processes in the codes below are split into separate files to help understand the process and ease the learner.
1. Project Directory
2. Generate Keys for Alice and Bob
There are several ways to generate a Public-Private Key Pair depending on your platform. In this example, we will create two pairs, one for Alice and one for Bob using java. The Cryptographic Algorithm we will use in this example is RSA.
package com.mkyong.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());
}
}
Output:
3. Alice generates a Symmetric Key
With the code below you can generate a Symmetric Key. The code uses SecureRandom
to add randomness to the Key. For more information about SecureRandom
refer to Java – How to create strong random numbers.
The constructor is initialized with the path to the directory where the key will be saved, the length of the key and the algorithm that will be used to create it. To learn more about the key lengths for each algorithm refer to Import Limits on Cryptographic Algorithms.
In this example we use AES as it is considered the silver lining between speed and security. If you want to read more about Encryption Algorithms, refer to Performance Analysis of Data Encryption Algorithms: 2.5 Compared Algorithms.
package com.mkyong.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());
}
}
Output:
4. Alice writes her message
5. Alice encrypts the data
5.1 The StartEncryption
class contains the three methods getPrivate()
, getPublic()
, getSecretKey()
that are used to build the corresponding keys from files. In the main method we construct the File objects
we need to use for the two classes that we will call for the encryption; EncryptData
and EncryptKey
.
package com.mkyong.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
.
package com.mkyong.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
.
package com.mkyong.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;
}
}
Output:
The key was successfully encrypted and stored in: EncryptedFiles\encryptedSecretKey
The file was successfully encrypted and stored in: EncryptedFiles\encryptedFile
6. Bob receives the data and decrypts them
6.1 The StartDecryption
class contains the three methods getPrivate()
, getPublic()
, getSecretKey()
that are used to build the corresponding keys from files. In the main method we construct the File
objects we need to use for the two classes that we will call for the decryption; DecryptData
and DecryptKey
.
package com.mkyong.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
.
package com.mkyong.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.
package com.mkyong.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;
}
}
Output:
The file was successfully decrypted. You can view it in: DecryptedFiles\decryptedFile
Thank you, it’s well suited for learning how hybrid encoding/decoding works.