SecureString.java
/***************************************************************************
Copyright 2014 Emily Estes
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
***************************************************************************/
package net.metanotion.util;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.text.Normalizer;
import java.util.Arrays;
/** Java Strings are immutable, which makes storing things like passwords in them undesirable, even worse, you can
print them in log files rather easily too. This class is meant to simplify working with this sort of data. It stores
the data internally in an array, and has a useless .toString() method to prevent accidental printing. And it implements
{@link java.lang.AutoCloseable} to erase the data from the array when you're done with the string. (Closeable allows
the underlying data to be erased via the try-with-resources mechanism too.) */
public final class SecureString implements AutoCloseable {
private byte[] theString;
/** Create a secure string instance from a String. The string can't be erased, but this class doesn't hold on to
the instance, and instead immediately converts it a byte array, erasing the intermediary arrays and nulling the
intermediary objects created to convert it.
@param theString The string to convert and store.
*/
public SecureString(final String theString) { this(theString.toCharArray()); }
/** Create a secure string instance backed by the character array provided. In the process it will be converted to
a byte array and the provided array will be cleared of data.
@param theString The character array
*/
public SecureString(final char[] theString) { this.theString = convert(theString); }
private static final char[] convert(final byte[] theString) {
final ByteBuffer byteBuffer = ByteBuffer.wrap(theString);
final CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
final char[] array = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
Arrays.fill(charBuffer.array(), '\u0000');
return array;
}
private static final byte[] convert(final char[] theString) {
final CharBuffer charBuffer = CharBuffer.wrap(theString);
final ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
Arrays.fill(charBuffer.array(), '\u0000');
final byte[] array = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
Arrays.fill(byteBuffer.array(), (byte) 0);
return array;
}
/** This method applies unicode normalization to this string, erasing the old representation and creating a new
one. Since this class is mutable, this is a destrctive update. Also, this is NOT thread safe. This entire class is
not thread safe.
@return this instance of secure string.
*/
public SecureString normalize() {
char[] array = convert(theString);
/* I don't want to do this, but normalizer just calls ".toString()" if you try to pass it a CharSequence. The
best I think I can do is just make a string and quickly set it to null as quickly as possible so it's
unreachable from MY code. */
String temp = new String(array);
String newString = Normalizer.normalize(temp, Normalizer.Form.NFKC);
temp = null;
Arrays.fill(array, '\u0000');
array = null;
this.close();
this.theString = convert(newString.toCharArray());
newString = null;
return this;
}
/** This returns a reference to the internal byte array stored by this object. Since this object is mutable other
operations like {@link #concat}, {@link #normalize}, {@link #close} are destructive and invalidate this
reference.
@return a byte array representation of the string currently stored by this instance.
*/
public byte[] toByteArray() { return theString; }
/** This returns the current length of the string in BYTES backing this object. Since this object is mutable, the
destructive operations could change this value.
@return the length of the string in bytes in a UTF-8 encoding.
*/
public int length() { return theString.length; }
/** This concatenates the string to the current string in this instance, destructively updating the underlying
representation and clearing any intermediate buffers where possible.
@param str The string to concatenate.
@return this instance of secure string.
*/
public SecureString concat(String str) {
char[] array1 = convert(theString);
char[] array2 = str.toCharArray();
char[] out = Arrays.copyOf(array1, array1.length + array2.length);
System.arraycopy(array2, 0, out, array1.length, array2.length);
Arrays.fill(array1, '\u0000');
Arrays.fill(array2, '\u0000');
this.close();
this.theString = convert(out);
array1 = null;
array2 = null;
out = null;
return this;
}
@Override public boolean equals(final Object o) {
if(o==this) { return true; }
if(o instanceof SecureString) { return Arrays.equals(theString, ((SecureString) o).theString); }
return false;
}
/** This class is great for feeding a password to a secure hash function. However, THIS is not a secure hash
function. Also, you shouldn't be keeping these around in a hash table. So I'm going to make life difficult for you
if you try to do that.
@return Doesn't return anything, throws an exception since you shouldn't use this method.
*/
@Override public int hashCode() { throw new UnsupportedOperationException(); }
@Override public String toString() { return "**SECURE STRING, DON'T PRINT ME**"; }
@Override public void close() {
if(theString == null) { return; }
Arrays.fill(theString, (byte) 0);
theString = null;
}
}