Main Tutorials

Java – Hybrid Cryptography example

encrypted

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.

1. Project Directory

java-hybrid-cryptography-8f

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:

java-hybrid-cryptography-8a

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

java-hybrid-cryptography-8b

4. Alice writes her message

java-hybrid-cryptography-8c

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 {

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

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.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
java-hybrid-cryptography-8d

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 {

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

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.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
java-hybrid-cryptography-8e

Download Source Code

Download it – HybridCryptographyExample.zip (11 KB)

References

  1. Asymmetric Encryption Algorithms
  2. Comparative Study of Asymmetric Key Cryptographic Algorithms
  3. KeyPairGenerator Algorithms
  4. Import Limits on Cryptographic Algorithms
  5. Cipher Algorithms

About Author

author image
Marilena Panagiotidou is a senior at University of the Aegean, in the department of Information and Communication Systems Engineering. She is passionate about programming in a wide range of languages. You can contact her at [email protected] or through her LinkedIn.

Comments

Subscribe
Notify of
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
2 years ago

Thank you, it’s well suited for learning how hybrid encoding/decoding works.