Java equivalent of SecureString

I'm researching on this myself, and by reading this article, I gather that that:

  • the problem cannot be completely eradicated
  • the best we can do is to encrypt the string as early as possible, and
  • wipe the memory location used to store the unencrypted string immediately after use - due to this requirement, it's best to use a char[] instead of a String, since the latter is immutable (and resorting to reflection to wipe it is not recommended)

To achieve this, one could either use org.identityconnectors.common.security.GuardedString, part of this Oracle's library, or javax.crypto.SealedObject which is part of the SDK, see examples here.

TL;DR, SealedObject:

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SealedObject;
import java.security.Key;

public class SealedMain
{
    public static void main(String[] args) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");

        Key key = keyGenerator.generateKey();
        Cipher cipher = Cipher.getInstance("DESede");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        SealedObject so = new SealedObject("foo", cipher);

        String unencryptedPassword = (String) so.getObject(key);

        System.out.println(unencryptedPassword);
    }
}

GuardedString:

import org.identityconnectors.common.security.GuardedString;

public class GuardedMain
{
    public static void main(String[] args) {
        GuardedString gs = new GuardedString("foo".toCharArray());
        gs.access(chars -> System.out.println(String.valueOf(chars)));
    }
}

One thing that I don't like about GuardedString is having to pull a whole library for a single function, and an "enteprise-looking" one at that:

<dependency>
    <groupId>org.syncope.identityconnectors</groupId>
    <artifactId>framework</artifactId>
    <version>0.4.3</version>
</dependency>
<dependency>
    <groupId>org.syncope.identityconnectors</groupId>
    <artifactId>framework-internal</artifactId>
    <version>0.4.3</version>
    <scope>runtime</scope>
</dependency>

Not to mention the fact that the library has last been updated 11 years ago, but who knows, maybe it's just stable.


This answer adds a bit more explanation to @sanketshah's great answer.

The following code shows the usage:

import org.identityconnectors.common.security.GuardedString;

import java.security.SecureRandom;

public class Main {
    public static void main(String[] args) {
        /*
         * Using:
         *   "password".toCharArray();
         * would create an immutable String "password",
         * which remains in memory until GC is called.
         */
        char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
        GuardedString guardedString = new GuardedString(password);

        /*
         * Securely wipe the char array by storing random values in it.
         * Some standards require multiple rounds of overwriting; see:
         * https://en.wikipedia.org/wiki/Data_erasure#Standards
         */
        SecureRandom sr = new SecureRandom();
        for (int i = 0; i < password.length; i++)
            password[i] = (char) sr.nextInt(Character.MAX_VALUE + 1);
        //noinspection UnusedAssignment
        password = null;

        /*
         * At some later point in the code, we might need the secret.
         * Here's how to obtain it using Java 8+ lambdas.
         */

        guardedString.access(chars -> {
            for (char c : chars) {
                System.out.print(c);
            }
        });
    }
}

GuardedString can be obtained from maven IdentityConnectors: Framework. However, for the actual implementation, one also needs IdentityConnectors: Framework Internal.

To be more precise, the former package defines the Encryptor interface:

package org.identityconnectors.common.security;

/**
 * Responsible for encrypting/decrypting bytes. Implementations
 * are intended to be thread-safe.
 */
public interface Encryptor {
    /**
     * Decrypts the given byte array
     * @param bytes The encrypted bytes
     * @return The decrypted bytes
     */
    public byte [] decrypt(byte [] bytes);
    /**
     * Encrypts the given byte array
     * @param bytes The clear bytes
     * @return The ecnrypted bytes
     */
    public byte [] encrypt(byte [] bytes);
}

which is implemented by EncryptorImpl in the second package. (The same goes for the abstract class EncryptorFactory, which is extended by EncryptorFactoryImpl).

The EncryptorFactory actually fixes its implementation:

// At some point we might make this pluggable, but for now, hard-code
private static final String IMPL_NAME = "org.identityconnectors.common.security.impl.EncryptorFactoryImpl";

An insecure aspect of the implementations is that they use AES/CBC/PKCS5Padding with hard-coded IV and key. The constructor of EncryptorFactoryImpl passes true to EncryptorImpl:

public EncryptorFactoryImpl() {
    _defaultEncryptor = new EncryptorImpl(true);
}

which causes it to use the default key. Regardless of that, the IV is always fixed:

public EncryptorImpl( boolean defaultKey ) {
    if ( defaultKey ) {
        _key = new SecretKeySpec(_defaultKeyBytes,ALGORITHM);
        _iv  = new IvParameterSpec(_defaultIvBytes);            
    }
    else {
        try {
            _key = KeyGenerator.getInstance(ALGORITHM).generateKey();
            _iv  = new IvParameterSpec(_defaultIvBytes);
        }
        catch (RuntimeException e) {
            throw e;
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

There is some space for improvements here:

  1. Use AES/CTR or AES/GCM instead of AES/CBC. (See block cipher mode of operation.)
  2. Always use random IV and key.
  3. GuardedString uses an internal method SecurityUtil.clear() to clear byte arrays, which zeros out the bytes. It would be nice to have other possible data erasure algorithms.

Oracle has a GuardedString implementation. It is the closest match to .NET's SecureString solution.

Secure string implementation that solves the problems associated with keeping passwords as java.lang.String. That is, anything represented as a String is kept in memory as a clear text password and stays in memory at least until it is garbage collected.

The GuardedString class alleviates this problem by storing the characters in memory in an encrypted form. The encryption key will be a randomly-generated key.

In their serialized form, GuardedStrings will be encrypted using a known default key. This is to provide a minimum level of protection regardless of the transport. For communications with the Remote Connector Framework it is recommended that deployments enable SSL for true encryption.

Applications may also wish to persist GuardedString. In the case of Identity Manager, it should convert GuardedStrings to EncryptedData so that they can be stored and managed using the Manage Encryption features of Identity Manager. Other applications may wish to serialize APIConfiguration as a whole. These applications are responsible for encrypting the APIConfiguration blob for an additional layer of security (beyond the basic default key encryption provided by GuardedString).


I modified the OWASP version to randomly pad the char array in memory so the char array at rest is not stored with the actual characters.

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 computer memory when no longer
* needed or garbaged collected.
*/
public class SecureString implements CharSequence {

   private final int[] chars;
   private final int[] pad;

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

   public SecureString(final int start, final int end, final CharSequence original) {
      final int length = end - start;
      pad = new int[length];
      chars = new int[length];
      scramble(start, length, original);
   }

   @Override
   public char charAt(final int i) {
      return (char) (pad[i] ^ chars[i]);
   }

   @Override
   public int length() {
      return chars.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.
    */
   public String asString() {
      final char[] value = new char[chars.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() {
      Arrays.fill(chars, '0');
      Arrays.fill(pad, 0);
   }

   /**
    * Protect against using this class in log statements.
    * <p>
    * {@inheritDoc}
    */
   @Override
   public String toString() {
      return "Secure:XXXXX";
   }

   /**
    * Called by garbage collector.
    * <p>
    * {@inheritDoc}
    */
   @Override
   public void finalize() throws Throwable {
      clear();
      super.finalize();
   }

   /**
    * 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
    */
   private void scramble(final int start, final int length, final CharSequence characters) {
      final SecureRandom random = new SecureRandom();
      for (int i = start; i < length; i++) {
         final char charAt = characters.charAt(i);
         pad[i] = random.nextInt();
         chars[i] = pad[i] ^ charAt;
      }
   }

}