Encryption and Decryption of Strings in Java

Encryption is the process of converting a plain text into a ciphertext. A plaintext is the readable and understandable form of data, whereas ciphertext is a random sequence of characters designed to be unintelligible without the correct decryption key or algorithm. A ciphertext is not readable until it is converted to plain text using a secret key.

Decryption is the process of converting a ciphertext into a plain text using a secret key so as to make it readable.

There are two types of encryption in Java:

  1. Asymmetric Encryption: Asymmetric encryption uses two keys: a public key and a private key. A public key is used to encrypt plain text and can be freely shared with anyone who needs to encrypt and send a message. The encrypted text can only be decrypted using the corresponding private key, which must be kept confidential or shared securely with individuals authorized to read the encrypted texts. Some examples of asymmetric key encryption algorithms include DSA, RSA, PKCS, ElGamal, ECC, Diffie-Hellman, etc.
  2. Symmetric Encryption: Symmetric encryption uses a single key for both encryption and decryption. Some of the symmetric key encryption algorithm are AES (Advanced Encryption Standard), DES (Data Encryption Standard), etc.

Asymmetric vs Symmetric Encryption

Asymmetric encryption is considered more secure than symmetric encryption because the asymmetric encryption technique is relatively newer compared to symmetric encryption, and asymmetric also uses two keys for both encryption and decryption.

Cipher Class in Java

Java provides the Cipher class with cryptographic functions for encryption and decryption. You can simply import the Cipher class from the package javax.crypto.Cipher.

A Cipher object can be created using the getInstance() method of the Cipher class by passing the name of the transformation to it. A transformation is a string in the form algorithm/mode/scheme or algorithm. It includes the name of the cryptographic algorithm and may be followed by a feedback mode and padding scheme.

Here's an example of how to create a Cipher object in Java using a valid transformation:

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

Encryption and Decryption Example using Asymmetric

In this example code, we will do the following:

  1. Generate and save public and private key pair files to a specified location.
  2. Load the keys from the files.
  3. Utilize the keys for encrypting and decrypting a string using asymmetric encryption with RSA algorithm.
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

public class CryptoManager {

    public static void main(String[] args) {
        /* path to project root folder for saving public and private keys */
        String path = System.getProperty("user.dir");

        // Generates New public and private keys
        KeyPair keyPair = generateKeys();

        /* save public and private keys to files at the given location */
        saveKeysToFiles(path, keyPair.getPublic(), keyPair.getPrivate());

        // Algorithm to convert keys
        String algorithm = "RSA";

        // Read keys from files
        PublicKey publicKey = loadPublicKey(path, algorithm);
        PrivateKey privateKey = loadPrivateKey(path, algorithm);

        // Text to encrypt
        String plainText = "This is a  secret message";
        /* Encrypting plain text to ciphertext */
        String encryptedText = encrypt(publicKey, plainText);
        System.out.println("Encrypted text = " + encryptedText);

        /* Decrypting encrypted text back to plain text */
        String decryptedText = decrypt(privateKey, encryptedText);
        System.out.println("Decrypted text = " + decryptedText);
    }

    public static String encrypt(PublicKey publicKey, String plainText) {
        if (plainText == null || plainText.isEmpty()) {
            System.out.println("No data to encrypt!");
            return plainText;
        }
        Cipher cipher = null;
        String encryptedString = "";
        try {
            // Creating a Cipher object
            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

            // Initializing a Cipher object with public key
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            // Encrypting the plain text string
            byte[] encryptedText = cipher.doFinal(plainText.getBytes());

            // Encoding the encrypted text to Base64
            encryptedString = Base64.getEncoder().encodeToString(encryptedText);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                | IllegalBlockSizeException | BadPaddingException ex) {
            System.out.println("Exception caught while encrypting : " + ex);
        }

        return encryptedString;
    }

    public static String decrypt(PrivateKey privateKey, String cipherText) {
        if (cipherText == null || cipherText.isEmpty()) {
            System.out.println("No data to decrypt!");
            return cipherText;
        }
        String decryptedString = "";
        Cipher cipher = null;
        try {
            // Creating a Cipher object
            cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

            // Initializing a Cipher object with private key
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            // Decoding from Base64
            byte[] encryptedText = Base64.getDecoder().decode(cipherText.getBytes());

            // Decrypting to plain text
            decryptedString = new String(cipher.doFinal(encryptedText));

        } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
                | IllegalBlockSizeException | BadPaddingException ex) {
            System.out.println("Exception caught while decrypting : " + ex);
        }
        return decryptedString;
    }


    public static KeyPair generateKeys() {
        KeyPair keyPair = null;
        try {
            KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");

            SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");

            // Initializing KeyPairGenerator
            keyGen.initialize(2048, random);

            // Generate keys
            keyPair = keyGen.generateKeyPair();

        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            e.printStackTrace();
        }
        return keyPair;
    }

    public static void saveKeysToFiles(String path, PublicKey publicKey, PrivateKey privateKey) {

        FileOutputStream fos = null;

        try {
            File file = new File(path + "/public.key");
            if (file.createNewFile()) {
                fos = new FileOutputStream(file);
                X509EncodedKeySpec x509EncodedKeySpec =
                        new X509EncodedKeySpec(publicKey.getEncoded());
                fos.write(x509EncodedKeySpec.getEncoded());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        // Private Key
        try {
            File file = new File(path + "/private.key");
            if (file.createNewFile()) {
                fos = new FileOutputStream(file);
                PKCS8EncodedKeySpec pkcs8EncodedKeySpec =
                        new PKCS8EncodedKeySpec(privateKey.getEncoded());
                fos.write(pkcs8EncodedKeySpec.getEncoded());
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    public static PublicKey loadPublicKey(String path, String algorithm) {

        FileInputStream fis = null;
        PublicKey publicKey = null;
        try {
            File file = new File(path + "/public.key");
            fis = new FileInputStream(file);
            byte[] encodedPublicKey = new byte[(int) file.length()];
            fis.read(encodedPublicKey);

            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(encodedPublicKey);
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return publicKey;
    }

    public static PrivateKey loadPrivateKey(String path, String algorithm) {

        FileInputStream fis = null;
        PrivateKey privateKey = null;

        try {
            File file = new File(path + "/private.key");
            fis = new FileInputStream(file);
            byte[] encodedPrivateKey = new byte[(int) file.length()];
            fis.read(encodedPrivateKey);

            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            privateKey = keyFactory.generatePrivate(privateKeySpec);
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return privateKey;
    }

}

The code begins by importing various classes and packages necessary for encryption, decryption, and file handling. The CryptoManager class is defined, which contains the main method and other helper methods for key generation, encryption, and decryption. In the main method, the path to the project's root folder is obtained using the System.getProperty("user.dir") method. 

The generateKeys method is called to generate a new pair of public and private keys using the RSA algorithm. 

The saveKeysToFiles method is called to save the generated public and private keys to separate files in the specified path. 

The loadPublicKey and loadPrivateKey methods are used to read the public and private keys respectively from the saved files. A plain text message is defined as "This is a secret message" which is to be encrypted. 

The encrypt method is called with the public key and the plain text message as parameters to encrypt the message using RSA encryption. The encrypted text is then printed to the console. 

The decrypt method is called with the private key and the encrypted text as parameters to decrypt the message using RSA decryption. The decrypted text is then printed to the console.

Overall, this code demonstrates how to generate RSA public and private keys, save them to files, load the keys from files, and use them to encrypt and decrypt a message.

The output of the above code is as follows:

Encrypted text = P+wlSykyT9DfNLtr39mvo9xlMkvhQ5I6BUtngrTbls34pYZfJ9pux9Lrf9bB9RryYWEvww1v6MzScXuMXbEsTRVaYxRGFnF6uLPJ/+ypZDmOTwRJ1abJuyG/h5Vc0K3pnMtZG8De6uVckXeIPmDRsZP5NexIo5Folf4N/Rin6eqXxuk6Vu7UlesJewuAROTYfKBCh/bYTpH41D4hLKi86dkWgwYfKzGOZ4ZBZLh0UNF3a0FIk9F4t8ooxd+Xf7ooTJqGppby9Kn0MiaL/iWzz1UdLudHH5CNUr6k5PVCYc9DZUglUCBnEefE/4ojv9VDai9PuT1uvSsdDLCnNg0Mwg==

Decrypted text = This is a secret message

It is important to note that, you should generate the public and private key pair only once and use the same keys every time you want to encrypt or decrypt data. It's important to understand that if you encrypt a message using a specific key pair, you can only decrypt it using the corresponding private key from the same key pair. Using a private key from a different key pair will not work. So, make sure to use the correct keys for encryption and decryption to ensure successful data security.

Encryption and Decryption Example using Symmetric

The following example code demonstrates encryption and decryption of a string data using symmetric encryption with the AES (Advanced Encryption Standard) algorithm in Java:

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class CryptoManager {
    //Key for encryption and decryption
    public static final byte[] KEY =
            {118, 106, 107, 122, 76, 99, 69, 83, 101, 103, 82, 101, 116, 75, 101, 127};

    public static void main(String args[]) {
        String salt = "HELLO";
        String encryptedString = encrypt(salt, "HelloWorld12345");
        System.out.println("Encripted string is " + encryptedString);

        String decryptedString = decrypt(salt, encryptedString);
        System.out.println("Decrypted string is " + decryptedString);

    }

    public static String encrypt(String salt, String plainText) {
        if (plainText == null || plainText.isEmpty()) {
            System.out.println("No data to encrypt!");
            return plainText;
        }
        Cipher cipher = null;
        String encryptedString = "";
        try {
            // Creating a Cipher object
            cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

            // Creating a secret key from KEY byte array
            final SecretKeySpec secretKey = new SecretKeySpec(KEY, "AES");

            // Initializing a Cipher object
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);

            // Encrypting the plain text string
            byte[] encryptedText = cipher.doFinal(salt.concat(plainText).getBytes());

            // Encoding the encrypted text to Base64
            encryptedString = Base64.getEncoder().encodeToString(encryptedText);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
                | IllegalBlockSizeException | BadPaddingException ex) {
            System.out.println("Exception caught while encrypting : " + ex);
        }
        return encryptedString;
    }


    public static String decrypt(String salt, String cipherText) {
        if (cipherText == null || cipherText.isEmpty()) {
            System.out.println("No data to decrypt!");
            return cipherText;
        }
        String decryptedString = "";
        Cipher cipher = null;
        try {
            // Creating a Cipher object
            cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");

            // Creating a secret key from KEY byte array
            final SecretKeySpec secretKey = new SecretKeySpec(KEY, "AES");

            // Initializing a Cipher object
            cipher.init(Cipher.DECRYPT_MODE, secretKey);

            // Decoding from Base64
            byte[] encryptedText = Base64.getDecoder().decode(cipherText.getBytes());

            // Decrypting to plain text
            decryptedString = new String(cipher.doFinal(encryptedText));

        } catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
                | IllegalBlockSizeException | BadPaddingException ex) {
            System.out.println("Exception caught while decrypting : " + ex);
        }
        return decryptedString.replace(salt, "");
    }

}

The above code begins with the import statements, which include necessary classes for encryption and decryption operations, such as java.security and javax.crypto.

The CryptoManager class contains a static byte array KEY, which serves as the secret key for encryption and decryption. This key should be kept secure and shared only with authorized parties.

The main method demonstrates the usage of the encrypt and decrypt methods. 

The encrypt method takes a salt value and a plain text as inputs to encrypt the combination of the salt and plain text. It initializes a Cipher object with the AES algorithm and the secret key derived from the KEY byte array. It encrypts the combined salt and plain text by converting it into bytes, performing the encryption operation, and encoding the result in Base64 format. The encrypted string is returned.

The decrypt method takes the salt and the encrypted string as inputs. It initializes a Cipher object in decryption mode using the AES algorithm and the secret key. It decodes the encrypted string from Base64, performs the decryption operation, and converts the decrypted bytes back into a string. The salt is removed from the decrypted string, and the result is returned.

When you run the above code, you will see the following output:

Encripted string is VKttEiG2vqYr4t7R7plUhmY0bLmHwvH21zKVz7n4BrM=

Decrypted string is HelloWorld12345

It's important to note that this is a simplified example, and in real-world scenarios, you would need to consider additional security measures and best practices, such as securely storing the secret key, using appropriate padding schemes, and protecting against potential vulnerabilities.