Java等同于SecureString

时间:2018-07-09 09:24:19

标签: java java-security securestring

我正在寻找与.NET的SecureString.aspx等效的Java。在2018年有这样的实施方式吗?

OWASP implementation并不完全相同,因为它只是一个纯字符数组。 .NET等效项提供了additional features,例如从非托管内存中获取实例的能力以及加密功能。

我知道常见的Java模式,它以char[]的形式传递密码,并在使用后用零进行Arrays.fill()。但这需要始终围绕char[]构建一个琐碎的实用工具类。

3 个答案:

答案 0 :(得分:8)

Oracle具有GuardedString实现。这是最接近.NET SecureString解决方案的匹配项。

  

安全的字符串实现,可以解决与以下问题相关的问题   保持密码为java.lang.String。也就是说,任何代表   因为字符串以明文密码形式保存在内存中并保持在   内存,直到被垃圾回收为止。

     

GuardedString类通过存储以下内容来缓解此问题:   加密形式的内存中的字符。加密密钥为   随机生成的密钥。

     

GuardedString将以序列号的形式使用   已知的默认密钥。这是为了提供最低级别的保护   不管运输。与遥控器通讯   连接器框架,建议部署为以下应用程序启用SSL   真正的加密。

     

应用程序也可能希望保留GuardedString。如果是   Identity Manager,它将GuardedString转换为   EncryptedData,以便可以使用   管理Identity Manager的加密功能。其他应用可能   希望将APIConfiguration整体序列化。这些应用   负责加密APIConfiguration Blob   额外的安全层(除了基本的默认密钥加密之外)   由GuardedString提供。

答案 1 :(得分:2)

我修改了OWASP版本,以将char数组随机填充到内存中,以便静止的char数组不与实际字符一起存储。

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

}

答案 2 :(得分:1)

此答案为@sanketshah出色的answer添加了更多解释。

以下代码显示了用法:

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);
            }
        });
    }
}
可以从行家IdentityConnectors: Framework获得

GuardedString。但是,对于实际的实现,还需要IdentityConnectors: Framework Internal

更准确地说,前一个包定义了Encryptor接口:

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

由第二个软件包中的EncryptorImpl实现。 (对于抽象类EncryptorFactory也是如此,它由EncryptorFactoryImpl扩展了。)

EncryptorFactory实际上修复了其实现:

// 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";

实现的一个不安全方面是它们使用带有硬编码的IV和密钥的AES/CBC/PKCS5PaddingEncryptorFactoryImpl的构造函数将true传递给EncryptorImpl

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

使其使用默认密钥。无论如何,IV始终是固定的:

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

这里还有一些改进的地方:

  1. 使用AES / CTR或AES / GCM代替AES / CBC。 (请参阅block cipher mode of operation。)
  2. 始终使用随机IV和密钥。
  3. GuardedString使用内部方法SecurityUtil.clear()清除字节数组,该方法将字节清零。拥有其他可能的data erasure算法会很好。