28

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.

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
  • 1
    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. – Sam Ginrich Apr 15 '22 at 16:47

4 Answers4

18

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

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
sanketshah
  • 424
  • 1
  • 5
  • 13
  • 1
    See also https://security.stackexchange.com/a/149553/207011 and https://stackoverflow.com/q/62842903/14955 – Thilo Jul 11 '20 at 08:47
10

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

}

Melloware
  • 10,435
  • 2
  • 32
  • 62
  • Hi melloware .... can you explain me how we can use this when we get user password from console and we need to match it. – Devendra Singraul Apr 15 '20 at 12:09
  • 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. – Melloware Apr 15 '20 at 16:39
4

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.
Sadeq Dousti
  • 3,346
  • 6
  • 35
  • 53
  • 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 – Maurice Jan 20 '22 at 01:22
  • 1
    That vulnerability affects `JUnit`, not `identityconnectors`. – Milad Sep 25 '22 at 15:06
2

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.

Milad
  • 836
  • 7
  • 13