Java: Zeroization

Zeroization of passwords, keys and other sensitive data

Passwords and secret keys that remains in memory are a security issue. There are certainly more urgent security problems, but this is not a reason to neglect this issue.

Memory attacks:

  • The secret values may be swapped and become readable from the disk,
  • an attacker can perform a cold boot attack if she or he has access to the computer a short time after it was shut down,
  • programs may contain bugs like read overflows,
  • the operating system may contain bugs or backdoors that allow to read values of the memory.

Garbage collection:

There is no need to free memory in Java. There is an automatically garbage collection which checks if an object is no longer used, reclaims the memory and reuses it for new objects. Garbage collection reduces the risk of certain memory leaks and other bugs.
If the garbage collection sweeps an object (for example a byte array) the region of the memory sometime will be reused and so overwritten with new stuff. Reusing is as good as an explicitly zeroization, but there is no way to determine at what time the garbage collection cleans up, and certainly not at what time a memory area is overwritten.
Calling the System.gc() method is more of a suggestion to the JVM as a command.
The garbage colletion, where objects may copied from one part of the memory to another (from Eden space to Survivor space, from Survivor space to the old generation...) makes things a little unpredictable, but anyway, or perhaps because of these unpredictability immediate zeroing is a good idea.

Explecitely zeroization by Arrays.fill()

Expecitely zeroization is good practice.
The normal way of zeroization in Java is done by Arrays.fill(..., ...);
Example codes of Oracle simply uses the fill function of java.util.arrays.

However, there is an objection to this practice. Arrays.fill() ist something like dead code or more specific redundant code.
The execution of the method does not affect the execution of the program. A compiler perhaps decided that this code is actually redundant and eliminates it.
In contrast to programming languages like C in Java, there are two compilers which are eligible: the bytecode compiler and the JIT (just in time) compiler. The bytecode compiler compiles at compile time, so you can figure out by decompiling the class file, if the code was eliminated (as far as I've tested this - for javac and ejc -, this is not the case).
The JIT compiler runs at run time and performs various optimizaions like dead code elimination.
I tested the Hotspot compiler by comparing the execution time when calling Arrays.fill() and without calling it. There is a significant difference, so Hotspot performs the Arrays.fill() method and does not eliminate it.
It seems for Java the problem is less considerable like for programming languages like C or C++. But there are several JIT compilers, not only the Sun Hotspot. Do they eliminate Arrays.fill() or not? And there are also compilers like gcc which generates native machine code.

Zeroization can be done in an extra class which checks if Arrays.fill() was performed or not.


package cologne.eck.dr.op.peafactory.tools;

import java.util.Arrays;

/*
 * Class to prevent compilers' dead code elimination. 
 * 
 */

public class Zeroizer {
	
	private static byte zero8bit = 0;
	private static char zeroChar = '\0';
	private static int zero32bit = 0;
	private static long zero64bit = 0L;
	
	public final static void zero(byte[] input) {		
		Arrays.fill(input,  (byte) 0);		
		for (int i = 0; i < input.length; i++) {
			zero8bit |= input[i];
		}
	}
	public final static void zero(int[] input) {		
		Arrays.fill(input, 0);		
		for (int i = 0; i < input.length; i++) {
			zero32bit |= input[i];
		}
	}	
	public final static void zero(long[] input) {		
		Arrays.fill(input, 0);		
		for (int i = 0; i <input.length; i++) {
			zero64bit |= input[i];
		}
	}		
	public final static void zero(char[] input) {		
		Arrays.fill(input,  '\0');		
		for (int i = 0; i < input.length; i++) {
			zeroChar |= input[i];
		}
	}
	
	public final static void zero(byte zero) {
		zero = (byte) 0;
		zero8bit |= zero;
	}
	public final static void zero(int zero) {
		zero = 0;
		zero32bit |= zero;
	}
	public final static void zero(long zero) {
		zero = (byte) 0;
		zero64bit |= zero;
	}
	public final static void zero(char zero) {
		zero = '\0';
		zeroChar |= zero;
	}

	
	/**
	 * To check the execution, this function should be 
	 * called as last step before the application exits
	 */
	public final static void getZeroizationResult() {
		
		if (zeroChar != '\0') {
			System.err.println("Zeroization failed - char: " + zeroChar); 
		}

		if ( (zero8bit != 0) || (zero32bit != 0) || (zero64bit != 0)) {
			System.err.println("Zeroization failed - \nbyte: " + zero8bit 
					+ "\nint: " + zero32bit
					+ "\nlong: " + zero64bit);
		} else {
			System.out.println("Zeroization: success");
		}
	}
	
	// Test:
/*	public static void main(String[] args) {
		
		char[] testChars = "abcdefghijklmnopqrstuvwxyz".toCharArray();
		byte[] testBytes = new byte[128];
		Arrays.fill(testBytes,  (byte) 127);
		int[] testInts = new int[55];
		Arrays.fill(testInts,  55);
		long[] testLongs = new long[33];
		Arrays.fill(testLongs,  33L);
		byte b = (byte) 0xFF;
		int i = 0xFFFFFFFF;
		long l = 0xFFFFFFFFFFFFFFFFL;
		char c = 'X';
		
		zero(testChars);
		zero(testBytes);
		zero(testInts);
		zero(testLongs);
		
		zero(b);
		zero(i);
		zero(l);
		zero(c);
		
		getZeroizationResult();
	} */
}

O.k., this is probably secure but greatly overkill. And for programs using large vectors this is also a performance issue.

A more simple alternative

The following code gives an example on how to prevent dead code elimination without an extra class:


		// Clean-up:
		Arrays.fill(key, (byte) 0);
		Arrays.fill(data, (byte) 0);		
		Arrays.fill(password,  (byte) 0);
		
		// prevent dead code eliminations (compiler optimizations):
		if ((key[key.length -1] 
				| data[data.length -1] 
				| password[password.length-1]) != 0) {
			System.err.print("zeroization failed!");
		}

This should be secure too. There is an access to the arrays after they are filled, so the compiler should not treat it as redundant code.
Some C compilers only zeroize the index 0, so instead of array[0] it is probably better to take the last value array[array.length -1].

Risks of zeroization:

There is also a risk: a cryptographic key or password which was filled, but reused later performs an encryption or authentication with a zero array. You might not realize it, because an encryption with a zero key produces good random looking ciphertexts although it is completely insecure.
To avoid this risk, the array can be set to null after filling it. If you try to reuse the zeroized array, this will throw a NullPointerException. This must be done after the check.
Note: This can't be done inside a function like the first example, because Java passes objects as references (adresses) and those references are passed by value. So, you can change the values of an array, but not the reference inside a function.


		// Clean-up:
		Arrays.fill(key, (byte) 0);
		Arrays.fill(data, (byte) 0);		
		Arrays.fill(password,  (byte) 0);
		
		// prevent dead code eliminations (compiler optimizations):
		if ((key[key.length -1] 
				| data[data.length -1] 
				| password[password.length-1]) != 0) {
			System.err.print("zeroization failed!");
		}
		key = null;
		data = null;
		password = null;
		[...]
		byte[] ciphertext = encrypt(key, plaintext);// this will now throw a NullPointerException