Java equivalent of SecureString

15,499

Solution 1

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).

Solution 2

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

}

Solution 3

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.
Share:
15,499

Related videos on Youtube

Mikhail Kholodkov
Author by

Mikhail Kholodkov

Updated on September 15, 2022

Comments

  • Mikhail Kholodkov
    Mikhail Kholodkov almost 2 years

    I'm looking for Java's equivalent of .NET's SecureString.aspx. Is there such implementation available in 2018?

    OWASP implementation is not exactly the same because it's just a plain char array. While .NET equivalent provides additional features such as the ability to get an instance from/to unmanaged memory and also encryption.

    I'm aware of common Java pattern to pass around passwords as char[] and do Arrays.fill() them with zeros after use. But it requires building a trivial utility class around char[] all the time.

    • Sam Ginrich
      Sam Ginrich about 2 years
      java design basically does not provide treatment for Destroyable-s, not even a JPasswordField is cleaned up, same with security-private keys. My solution would be using reflection to access critical members of String, BigInteger or the document attached to a JPasswordField.
  • Devendra  Singraul
    Devendra Singraul about 4 years
    Hi melloware .... can you explain me how we can use this when we get user password from console and we need to match it.
  • Melloware
    Melloware about 4 years
    Yep you store the password in SecureString and then you can do secureString.asString() to convert it back to plain text to compare it with an passed in password. The point of SecureString is to store it scrambled at rest so if someone dumps your JVM params the String is not right in there.
  • hansvb
    hansvb almost 4 years
  • Maurice
    Maurice over 2 years
    According to maven repository the identityconnectors framework has the vulnerability CVE-2020-15250. I wouldn't want to use this dependency until that vulnerability is resolved