SecureString.java

  1. package org.flasby.crypto;

  2. import java.lang.ref.Cleaner;
  3. import java.security.SecureRandom;
  4. import java.util.Arrays;

  5. /**
  6.  * This is not a string but a CharSequence that can be cleared of its memory. Important for handling
  7.  * passwords. Represents text that should be kept confidential, such as by deleting it from memory
  8.  * when no longer needed or garbaged collected.
  9.  *
  10.  * <p>Modified from an original post in stackoverflow by Melloware who modified something from
  11.  * OWASP.
  12.  */
  13. public final class SecureString implements CharSequence, AutoCloseable {

  14.   private static final Cleaner CLEANER = Cleaner.create();
  15.   private final Cleaner.Cleanable cleanable;
  16.   private final State state;

  17.   private static class State implements Runnable {
  18.     private final int[] chars;
  19.     private final int[] pad;
  20.     private boolean cleared = false;

  21.     /**
  22.      * Randomly pad the characters to not store the real character in memory.
  23.      *
  24.      * @param start start of the {@code CharSequence}
  25.      * @param length length of the {@code CharSequence}
  26.      * @param characters the {@code CharSequence} to scramble
  27.      */
  28.     State(final int start, final int length, final CharSequence characters) {
  29.       pad = new int[length];
  30.       chars = new int[length];
  31.       for (int i = start; i < length; i++) {
  32.         final char charAt = characters.charAt(i);
  33.         pad[i] = random.nextInt();
  34.         chars[i] = pad[i] ^ charAt;
  35.       }
  36.     }

  37.     public char charAt(final int i) {
  38.       return (char) (pad[i] ^ chars[i]);
  39.     }

  40.     public int length() {
  41.       return chars.length;
  42.     }

  43.     public void run() {
  44.       // Native resource deallocation happens here.
  45.       Arrays.fill(chars, '0');
  46.       Arrays.fill(pad, 0);
  47.       cleared = true;
  48.     }
  49.   }

  50.   private static final SecureRandom random = new SecureRandom();

  51.   public SecureString(final CharSequence original) {
  52.     this(0, original.length(), original);
  53.   }

  54.   public SecureString(final int start, final int end, final CharSequence original) {
  55.     state = new State(start, (end - start), original);
  56.     cleanable = CLEANER.register(this, state);
  57.   }

  58.   @Override
  59.   public char charAt(final int i) {
  60.     return state.charAt(i);
  61.   }

  62.   @Override
  63.   public int length() {
  64.     return state.length();
  65.   }

  66.   @Override
  67.   public CharSequence subSequence(final int start, final int end) {
  68.     return new SecureString(start, end, this);
  69.   }

  70.   /**
  71.    * Convert array back to String but not using toString(). See toString() docs below. Calling
  72.    * should be avoided as it leaves passwords in memory which can't be cleared.
  73.    */
  74.   public String asString() {
  75.     if (state.cleared) throw new NullPointerException("SecureString is cleared, check your code");
  76.     final char[] value = new char[length()];
  77.     for (int i = 0; i < value.length; i++) {
  78.       value[i] = charAt(i);
  79.     }
  80.     return new String(value);
  81.   }

  82.   /** Manually clear the underlying array holding the characters */
  83.   public void clear() {
  84.     state.run();
  85.   }

  86.   /**
  87.    * Protect against using this class in log statements.
  88.    *
  89.    * <p>{@inheritDoc}
  90.    */
  91.   @Override
  92.   public String toString() {
  93.     return "Secure:XXXXX";
  94.   }

  95.   @Override
  96.   public void close() {
  97.     cleanable.clean();
  98.   }
  99. }