Java 等价于 SecureString

Posted

技术标签:

【中文标题】Java 等价于 SecureString【英文标题】:Java equivalent of SecureString 【发布时间】:2018-12-16 22:29:33 【问题描述】:

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

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

我知道常见的 Java 模式将密码作为char[] 传递,并在使用后将Arrays.fill() 用零做。但它需要始终围绕char[] 构建一个简单的实用程序类。

【问题讨论】:

【参考方案1】:

Oracle 有一个GuardedString 实现。它与 .NET 的 SecureString 解决方案最接近。

解决与相关问题的安全字符串实现 将密码保存为java.lang.String。也就是说,任何代表 因为字符串作为明文密码保存在内存中并保留在 内存至少在它被垃圾回收之前。

GuardedString 类通过存储 以加密形式存储在内存中的字符。加密密钥将是 随机生成的密钥。

在其序列化形式中,GuardedStrings 将使用 已知的默认密钥。这是为了提供最低级别的保护 无论运输。用于与遥控器通信 连接器框架建议部署启用 SSL 真正的加密。

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

【讨论】:

另见security.stackexchange.com/a/149553/207011和***.com/q/62842903/14955【参考方案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;
      
   



【讨论】:

嗨,melloware .... 当我们从控制台获取用户密码并且我们需要匹配它时,你能解释一下我们如何使用它吗? 是的,您将密码存储在 SecureString 中,然后您可以执行 secureString.asString() 将其转换回纯文本以将其与传入的密码进行比较。 SecureString 的重点是将其存储在静止状态,因此如果有人转储您的 JVM 参数,则字符串不正确。【参考方案3】:

这个答案为@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);
            
        );
    

GuardedString可以从mavenIdentityConnectors: Framework获取。不过实际实现还需要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);
        
    

这里有一些改进的空间:

    使用 AES/CTR 或 AES/GCM 代替 AES/CBC。 (见block cipher mode of operation。) 始终使用随机 IV 和密钥。 GuardedString 使用内部方法 SecurityUtil.clear() 清除字节数组,这会将字节清零。如果有其他可能的data erasure 算法,那就太好了。

【讨论】:

根据 maven 存储库,identityconnectors 框架存在漏洞CVE-2020-15250。在解决该漏洞之前,我不想使用此依赖项

以上是关于Java 等价于 SecureString的主要内容,如果未能解决你的问题,请参考以下文章

Java 等价于 Python repr()?

Java 等价于 Python 字典

JS 等价于 Java 的 Collections.unmodifiableCollection

等价于 C++ 中 Java 的 IllegalArgumentException

C++ 等价于 Java 的 BlockingQueue

Python 等价于 Java StringBuffer?