Quote of the Day

more Quotes

Categories

Buy me a coffee

Encryption in Java with JCA and Bouncy Castle API.

Published September 17, 2018 in Java , security - 3 Comments

In this post, I cover the basics, what I have learned about encryption while building a module to protect Personal Identifiable Information (PII) data using the Java Cryptography API (JCA) and Bouncy Castle API.

You may find this post helpful if:

  • You are new to encryption or not sure how to use the JCA/Bouncy Castle to do encryption in Java.
  • You face some issues with key length using the JCA.
  • You are not sure which types of encoding to use for encryption.

Overview

You use the JCE library with Bouncy Castle as the security provider to encrypt a plaintext into a byte array. You then take the byte array and encode into cipher text for storing into the database. Below is the high level of the process.

Initialization

  1. Register Bouncy Castle as a security provider.
  2. Bypass the key length restriction if you plan to use a key longer than 128 bits (applicable for Java 8 or below). More details on this later.
  3. Generate a master key for using in both encryption and decryption.
  4. Instantiate Cipher objects, one for encryption and one for decryption.

Encryption:

  1. Call the method on the Cipher object to encrypt the data into cipher binary data.
  2. Encode the cipher binary data for storing into the database.
  3. Add metadata into the cipher text to flag the text as cipher.

Decryption:

  1. Remove the metadata that flag the text as cipher.
  2. Decode the cipher text back to cipher binary data.
  3. Decrypt the cipher binary data and get back the plain string.

Limitation on key length on Java 8 and below

If you use Java 8 or below, and you want to use a strong key which is longer than 128 bits, you may get this exception.

Caused by: java.security.InvalidKeyException: Illegal key size or default parameters

Because of import restrictions in various countries, the JDK limits the key length by default. To get around this, do one of the following:

  • Just bypass the JCA and use another library such as Bouncy Castle. Bouncy Castle is good as a provider, but I personally find the JCA easier to use.
  • Download the local_policy.jar and US_export_policy.jar and place them in the $JAVA_HOME/jre/lib/security directory. This approach is probably the most straightforward. However, if may not be convenient for devops. For instance, you may need to include additional steps in your scripts for preparing server environments to include the JCE files.
  • Use reflection to remove the restriction at runtime. This requires embedding some codes in the application. The codes below I get from this post. I just include it here in case the post becomes unavailable in the future.
/** * Use reflection to disable the cryptography strength restrictions */
JCEUnlimitedPolicyUtil.removeCryptographyRestrictions();
public static class JCEUnlimitedPolicyUtil {
 private static void removeCryptographyRestrictions() {
  if (!isRestrictedCryptography()) {
   logger.fine("Cryptography restrictions removal not needed");
   return;
  }
  try {
   /* * Do the following, but with reflection to bypass access checks: * * JceSecurity.isRestricted = false; * JceSecurity.defaultPolicy.perms.clear(); * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE); */
   final Class << ? > jceSecurity = Class.forName("javax.crypto.JceSecurity");
   final Class << ? > cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
   final Class << ? > cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");
   final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
   isRestrictedField.setAccessible(true);
   final Field modifiersField = Field.class.getDeclaredField("modifiers");
   modifiersField.setAccessible(true);
   modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
   isRestrictedField.set(null, false);
   final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
   defaultPolicyField.setAccessible(true);
   final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);
   final Field perms = cryptoPermissions.getDeclaredField("perms");
   perms.setAccessible(true);
   ((Map << ? , ? > ) perms.get(defaultPolicy)).clear();
   final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
   instance.setAccessible(true);
   defaultPolicy.add((Permission) instance.get(null));
   logger.fine("Successfully removed cryptography restrictions");
  } catch (final Exception e) {
   logger.log(Level.WARNING, "Failed to remove cryptography restrictions", e);
  }
 }
 private static boolean isRestrictedCryptography() {
  // This matches Oracle Java 7 and 8, but not Java 9 or OpenJDK. 

final String name = System.getProperty("java.runtime.name"); final String ver = System.getProperty("java.version"); return name != null && name.equals("Java(TM) SE Runtime Environment") && ver != null && (ver.startsWith("1.7") || ver.startsWith("1.8")); } }
  • For Java 9, it appears you can get around the key limitation with just the code below. I haven’t tested this out though. Security.setProperty(“crypto.policy”, “unlimited”);

Using Bouncy Castle as a security provider

To use Bouncy Castle as a security provider, you need to have the Jar on your class path. Then, you can either update the java.security file following this post or add the code below to register the provider at runtime.

/**
         * Security Providers initialization. The first call of the init method
         * will have the class loader do the job. This technique ensures proper
         * initialization without the need of maintaining the
         * <i>${java_home}/lib/security/java.security</i> file, that would otherwise
         * need the addition of the following line:
         * <code>security.provider.<i>n</i>=org.bouncycastle.jce.provider.BouncyCastleProvider</code>.
         */
        Security.insertProviderAt( new BouncyCastleProvider(), 1 );

Key

For encrypting data at rest, we use symmetric encryption.

In case you are not familiar with symmetric encryption, it essentially means using a same key for both encryption and decryption. The other type of encryption is asymmetric encryption, which operates on two keys, one public and one private.

The simplest approach is to use a shared key for all the data you want to protect. However, the downside is if an attacker gains access to the key, the attacker may be able to decrypt all the data with only that one key.

private static final SecureRandom SECURE_RANDOM = new SecureRandom(  );
  public static byte[] generateKey( int keyLength )
          throws IOException, NoSuchProviderException, NoSuchAlgorithmException
  {
      CipherKeyGenerator cipherKeyGenerator = new CipherKeyGenerator();
      cipherKeyGenerator.init( new KeyGenerationParameters( SECURE_RANDOM, keyLength ) );
      return cipherKeyGenerator.generateKey();
  }

If your data pertain to individual users, a more secure approach is deriving individual keys for each user based on the user’s given passphrase/password (password-based encryption (PBE)). That way, at worse, an attacker who knows the key for one user can only decrypt the data for that user.

A key is vulnerable to several types of attack. For instance, in a brute-force attack, the attacker utilizes computing resources to try all the possible passphrase to crack the key. In a dictionary attack, the attacker tries to crack the key by attempting to use common words from a list  as passphrase.

A key is probably most of what is necessary to decrypt the data. So it’s important to use a strong key that make it impractical to crack. Some of the characteristics of a strong key include:

  • The input is the user’s password + a salt. The salt is essentially a sequence of bytes generated by a secure random generator. You generate a salt for each password and store it unencrypted alongside with the encrypted data. Using a salt for each password ensures the keys for two users are unique even when the users use a same passphrase. In the worse case scenario, the attacker can only decrypt the data for that user and not of any other user. Using a salt does not protect against dictionary attacks though.
  • The key derivation function is deliberately slow. This is to make attacks on the key using brute force techniques impractical. To make the function slow, we repeatedly apply the hashing algorithms multiple times.
  • The key size is large enough. This is also to make brute force attack against the key impractical. The key length of 128 bits should be enough. However, a 256 bits key may provide more protection in case of a flaw in the encryption algorithm.

The sample code below shows how to derive a PBE key using the JCA

public SecretKey generateKey(string passphrase) {
 SecureRandom secureRandom = SecureRandom.getInstance(); 
// use a large number of iterations to prevent brute force attacks. 
int iterations = 2000;
 int keyLength = 256;
 string algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC";
 try {
  byte[] salt = secureRandom.nextBytes(new byte[20]);
  PBEUTFKeySpec keySpec = new PBEKeySpec(passphrase, salt, iterations, 256);
  SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm);
  SecretKey key = keyFactory.generateSecret(keySpec);
 } catch (Exception e) {
  logThrowException("Key factory initialization failed! ", e);
 }
 return key;
}

In the above snippet, we specify the algorithm to generate the key as “PBEWITHSHA256AND256BITAES-CBC-BC”. Here is the explanation from this post on StackExchange:

PBE = password-based encryption. Isn’t this what you need?

WITHSHA256 = SHA-256 is a modern hash algorithm, and is used to prove that the data can’t have been modified by anyone with having the password at hand. 256-bit hashes are the modern standard, with 512-bit hashes for those of us who want just a little bit more reassurance. The SHA-2 family of hash functions is the most recent NIST standard digests.

AND256BITAES = AES-256 is a modern encryption algorithm. The 256-bit key size is for those of us who want just a little bit more reassurance, even though a 128-bit key size can be sufficient for many uses. The AES family of functions are the most recent NIST standard block ciphers.

CBC = The CBC mode for encryption is the default choice for turning a block cipher into a stream cipher. Stream ciphers are needed when one has a stream of data of arbitrarily length to be enciphered. AES in CBC mode is a more modern choice than RC4.

BC = The choice of crypto provider library. Choose a reliable, performance crypto library. BouncyCastle is implemented in Java, so the JIT may be able to optimize it. OpenSSL is implemented in C and the crypto provider might have bindings to the C functions, but the JIT is unable to optimize the C functions.

Cipher

You use a Cipher object of the javax.crypto package to perform encryption or decryption depending on the mode.

private Cipher encryptCipher;
private Cipher decryptCipher; 
try {
 SecretKeySpec spec = new SecretKeySpec(key.getEncoded(), "AES");
 encryptCipher = Cipher.getInstance("AES/CTR/NOPADDING");
 encryptCipher.init(Cipher.ENCRYPT_MODE, spec, ivParameterSpec, secureRandom);
 decryptCipher = Cipher.getInstance("AES/CTR/NOPADDING");
 decryptCipher.init(Cipher.DECRYPT_MODE, spec, ivParameterSpec, secureRandom);
} catch (Exception e) {
 logThrowException("Cipher initialization failed! ", e);
}

AES – Advanced Encryption Standards is a block cipher. From Wikipedia,

In cryptography, a block cipher is a deterministic algorithm operating on fixed-length groups of bits, called a block, with an unvarying transformation that is specified by a symmetric key.

CTR – Counter is one of the five modes of block ciphers. The other four modes are: Electronic Code Book (ECB), Cipher Block Chaining (CBC), Cipher FeedBack (CFB), and Output FeedBack (OFB). From the article on The Internet Engineering Task Force,

AES-CTR uses the AES block cipher to create a stream cipher. Data is encrypted and decrypted by XORing with the key stream produced by AES encrypting sequential counter block values.

CTR requires an initialization vector (IV). In a block cipher, the result of encrypting a block is the input for encrypting the next block. Since the first block has no other block before it, a randomized data gets used as input. This randomized data is called the initialization vector. Using an initialization vector make the patterns in a cipher text unique such that an attacker is not able to detect patterns which may help to decrypt all or part of the original text. For more details, see this post.

Encryption

The code snippet below demonstrates how to use the Cipher object to encrypt a plain text.

/**
    * Encrypt a string input.
    *
    * @param valueToEncrypt the plain text value to encrypt.
    * @return the encrypted value.
    */
   public synchronized String encrypt( String valueToEncrypt )
   {
       // avoid double encryption.
       if ( !isEncrypted( valueToEncrypt ) )
       {
           try
           {
               byte[] encryptedBytes = encryptCipher.doFinal( valueToEncrypt.getBytes() );
               String cipherText = new String( Hex.encode( encryptedBytes ), "UTF-8" );
               return ENCRYPTED_TOKEN_START + cipherText;
           }
           catch ( Exception e )
           {
               logThrowException( "Failed to encrypt '" + valueToEncrypt + "'. ", e );
           }
       }

       return valueToEncrypt;
   }

Decryption

To decrypt a cipher text, we just reverse the steps we apply on encryption.

/**
     * reverse the steps done by encryption.
     *
     * @param valueToDecrypt the value to decrypt
     * @return the original value before encryption.
     * @see #encrypt(String)
     */
    public synchronized String decrypt( String valueToDecrypt )
    {
        LOGGER.info( "Using key to decrypt: " + Hex.toHexString( key.getEncoded() ) );
        if ( isEncrypted( valueToDecrypt ) )
        {
            try
            {
                // remove the encryption header
                String cipher = removeEncryptionMetadata( valueToDecrypt );
                // get back the cipher bytes that were Hex encoded on encryption and decrypt
                byte[] cipherBytes = Hex.decode( cipher );
                return new String ( decryptCipher.doFinal( cipherBytes ), "UTF-8" );
            }
            catch ( Exception e )
            {
                LOGGER.info( "Decryption failed value: " + valueToDecrypt + "  cipher: " + removeEncryptionMetadata( valueToDecrypt ));
                logThrowException( "Decryption failed! ", e );
            }
        } return valueToDecrypt;
    }

Encoding

When encrypting a plaintext using the JCA, you get back a byte array.  As such, you need to encode the byte array into characters for storing into the database. I know of two encoding schemes for storing binary data are Base64 and Hexadecimal encodings.
In terms of space efficiency, Base64 uses less space than Hex to represent data. Base64 typically contains a-z, A-Z, 0-9,  “/”, “+”, and “=”.  It uses four characters for every three bytes. Compared to Base64, Hex (Base16) contains 0-9 and a-f and uses two characters for every byte.

base64-encoded data is only 33% larger than the raw data, whereas the hexadecimal representation is 100% larger.

In terms of simplicity and compatibility, Hex is better as it only uses 16 characters to represent data; all of those characters probably don’t need escaping. With Base64, you may need to worry about escaping since Base64 use special characters (“/”, “+”, “=”). I personally run into issues when storing Base64 encoded cipher texts as DN values in an Ldap database.

Conclusion

I hope you find the post helpful, especially if you are new to encryption or how to do it in Java.

Additional resources

Some of the links below I have also referenced in the above sections. These are the ones I find helpful while learning more about the topic and writing this post.

3 comments