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();
- }
- }