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:

1. Generates a new Symmetric Key using a strong algorithm
2. Encrypts “confidential.txt” using the Key from step 1
3. Encrypts the Key from step 1 using Bob’s Public Key
4. 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:

1. Decrypts the Key he received using his own Private Key
2. Decrypts the text he received using the Key he got in step 1 after the decryption
Note
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.

## 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.

GenerateKeys.java
``````
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.

GenerateSymmetricKey.java
``````
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.spec.SecretKeySpec;

public class GenerateSymmetricKey {

private SecretKeySpec secretKey;

public GenerateSymmetricKey(int length, String algorithm)

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)

GenerateSymmetricKey genSK = new GenerateSymmetricKey(16, "AES");
genSK.writeToFile("OneKey/secretKey", genSK.getKey().getEncoded());

}
}
``````

Output:

## 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`.

StartEncryption.java
``````
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 {

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePrivate(spec);

}

public PublicKey getPublic(String filename, String algorithm) throws Exception {

X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePublic(spec);

}

public SecretKeySpec getSecretKey(String filename, String algorithm) throws IOException{

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.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.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)

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.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.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.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)

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.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`.

StartDecryption.java
``````
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 {

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePrivate(spec);

}

public PublicKey getPublic(String filename, String algorithm) throws Exception {

X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePublic(spec);

}

public SecretKeySpec getSecretKey(String filename, String algorithm) throws IOException{

return new SecretKeySpec(keyBytes, algorithm);

}

public static void main(String[] args) throws IOException, GeneralSecurityException, Exception{

StartDecryption startEnc = new StartDecryption();

File decreptedKeyFile = new File("DecryptedFiles/SecretKey");
new DecryptKey(startEnc.getPrivate("KeyPair/privateKey_Bob", "RSA"),

File decryptedFile = new File("DecryptedFiles/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.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.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);

}

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)

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.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.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.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);

}

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)

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.close();
return fbytes;

}

}
``````

Output:

``````
The file was successfully decrypted. You can view it in: DecryptedFiles\decryptedFile
``````