Skip to content

Password-Based Encryption Algorithms

In the previous section, we discussed AES encryption. Some observant readers may have noticed that the key length is fixed at 128/192/256 bits, unlike what we see in tools like WinZip/WinRAR, where you can enter a password of any length.

This is because symmetric encryption algorithms require the key to be of a fixed length, and then they divide the plaintext into blocks for encryption. Due to security requirements, key lengths are usually 128 bits or more, which means at least 16 characters.

Why Short Passwords Work in Encryption Software

However, encryption software often allows us to enter passwords as short as 6 or 8 characters. Is the encryption method different?

In fact, the password input by the user cannot be directly used as an AES key for encryption (unless the length happens to be 128/192/256 bits). Additionally, user-entered passwords often have patterns and are not as secure as random passwords generated by secure random number generators. Therefore, the password entered by the user typically needs to be processed using the PBE algorithm, which hashes the user password with a random number to generate the actual encryption key.

PBE, or Password-Based Encryption, works as follows:

java
key = generate(userPassword, secureRandomPassword);

The purpose of PBE is to combine the user's password with a securely generated random password to compute the actual key through hashing. For AES encryption, for example, we allow the user to input a password, then generate a random number, use the PBE algorithm to calculate the real AES key, and then encrypt the data. Here is the code:

java
import java.security.*;
import java.util.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.*;
import javax.crypto.spec.*;

public class Main {
    public static void main(String[] args) throws Exception {
        // Add BouncyCastle as a Provider in java.security:
        Security.addProvider(new BouncyCastleProvider());
        // Original text:
        String message = "Hello, world!";
        // Password for encryption:
        String password = "hello12345";
        // 16 bytes of random Salt:
        byte[] salt = SecureRandom.getInstanceStrong().generateSeed(16);
        System.out.println(HexFormat.of().formatHex(salt));
        // Encrypt:
        byte[] data = message.getBytes("UTF-8");
        byte[] encrypted = encrypt(password, salt, data);
        System.out.println("encrypted: " + Base64.getEncoder().encodeToString(encrypted));
        // Decrypt:
        byte[] decrypted = decrypt(password, salt, encrypted);
        System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
    }

    // Encryption method:
    public static byte[] encrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
        SecretKey skey = skeyFactory.generateSecret(keySpec);
        PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
        Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
        cipher.init(Cipher.ENCRYPT_MODE, skey, pbeps);
        return cipher.doFinal(input);
    }

    // Decryption method:
    public static byte[] decrypt(String password, byte[] salt, byte[] input) throws GeneralSecurityException {
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray());
        SecretKeyFactory skeyFactory = SecretKeyFactory.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
        SecretKey skey = skeyFactory.generateSecret(keySpec);
        PBEParameterSpec pbeps = new PBEParameterSpec(salt, 1000);
        Cipher cipher = Cipher.getInstance("PBEwithSHA1and128bitAES-CBC-BC");
        cipher.init(Cipher.DECRYPT_MODE, skey, pbeps);
        return cipher.doFinal(input);
    }
}

Explanation of PBE

When using PBE, we need to include BouncyCastle and specify the algorithm as PBEwithSHA1and128bitAES-CBC-BC. In the code, the real AES key is generated when we call the Cipher's init() method, where both SecretKey and PBEParameterSpec are passed. When creating the PBEParameterSpec, we also specify the iteration count as 1000. The higher the iteration count, the more computational effort is required to brute force the encryption.

If we fix the salt and iteration count, we can create a generic "password" encryption software. If we store the randomly generated salt on a USB drive, we can create encryption software that combines a password with a USB key. The advantage of this method is that even if a weak password is used, decryption is impossible without the USB key, as it stores a highly secure random key.

Summary

  • PBE (Password-Based Encryption) calculates the encryption key by combining the user's password with a secure random salt, and then performs encryption.
  • The key is derived from both the password and a secure random salt, greatly enhancing security.
  • PBE algorithms internally still use standard symmetric encryption algorithms (such as AES).
Password-Based Encryption Algorithms has loaded