SecureString.java
package org.flasby.crypto;
import java.lang.ref.Cleaner;
import java.security.SecureRandom;
import java.util.Arrays;
/**
* This is not a string but a CharSequence that can be cleared of its memory. Important for handling
* passwords. Represents text that should be kept confidential, such as by deleting it from memory
* when no longer needed or garbaged collected.
*
* <p>Modified from an original post in stackoverflow by Melloware who modified something from
* OWASP.
*/
public final class SecureString implements CharSequence, AutoCloseable {
private static final Cleaner CLEANER = Cleaner.create();
private final Cleaner.Cleanable cleanable;
private final State state;
private static class State implements Runnable {
private final int[] chars;
private final int[] pad;
private boolean cleared = false;
/**
* Randomly pad the characters to not store the real character in memory.
*
* @param start start of the {@code CharSequence}
* @param length length of the {@code CharSequence}
* @param characters the {@code CharSequence} to scramble
*/
State(final int start, final int length, final CharSequence characters) {
pad = new int[length];
chars = new int[length];
for (int i = start; i < length; i++) {
final char charAt = characters.charAt(i);
pad[i] = random.nextInt();
chars[i] = pad[i] ^ charAt;
}
}
public char charAt(final int i) {
return (char) (pad[i] ^ chars[i]);
}
public int length() {
return chars.length;
}
public void run() {
// Native resource deallocation happens here.
Arrays.fill(chars, '0');
Arrays.fill(pad, 0);
cleared = true;
}
}
private static final SecureRandom random = new SecureRandom();
public SecureString(final CharSequence original) {
this(0, original.length(), original);
}
public SecureString(final int start, final int end, final CharSequence original) {
state = new State(start, (end - start), original);
cleanable = CLEANER.register(this, state);
}
@Override
public char charAt(final int i) {
return state.charAt(i);
}
@Override
public int length() {
return state.length();
}
@Override
public CharSequence subSequence(final int start, final int end) {
return new SecureString(start, end, this);
}
/**
* Convert array back to String but not using toString(). See toString() docs below. Calling
* should be avoided as it leaves passwords in memory which can't be cleared.
*/
public String asString() {
if (state.cleared) throw new NullPointerException("SecureString is cleared, check your code");
final char[] value = new char[length()];
for (int i = 0; i < value.length; i++) {
value[i] = charAt(i);
}
return new String(value);
}
/** Manually clear the underlying array holding the characters */
public void clear() {
state.run();
}
/**
* Protect against using this class in log statements.
*
* <p>{@inheritDoc}
*/
@Override
public String toString() {
return "Secure:XXXXX";
}
@Override
public void close() {
cleanable.clean();
}
}