我正在寻找与.NET的SecureString.aspx等效的Java。在2018年有这样的实施方式吗?
OWASP implementation并不完全相同,因为它只是一个纯字符数组。 .NET等效项提供了additional features,例如从非托管内存中获取实例的能力以及加密功能。
我知道常见的Java模式,它以char[]
的形式传递密码,并在使用后用零进行Arrays.fill()
。但这需要始终围绕char[]
构建一个琐碎的实用工具类。
答案 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/PKCS5Padding
。 EncryptorFactoryImpl
的构造函数将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);
}
}
}
这里还有一些改进的地方:
GuardedString
使用内部方法SecurityUtil.clear()
清除字节数组,该方法将字节清零。拥有其他可能的data erasure算法会很好。