Java-эквивалент SecureString
Я ищу Java эквивалент SecureString.aspx.NET. Есть ли такая реализация в 2018 году?
Реализация OWASP не совсем то же самое, потому что это просто массив символов. В то время как.NET-эквивалент предоставляет дополнительные функции, такие как возможность получения экземпляра из / в неуправляемую память, а также шифрование.
Я знаю общий шаблон Java для передачи паролей как char[]
и делать Arrays.fill()
их с нулями после использования. Но это требует построения тривиального класса полезности вокруг char[]
все время.
4 ответа
Oracle имеет GuardedString
реализация. Это самое близкое совпадение с.NET SecureString
решение.
Реализация безопасной строки, которая решает проблемы, связанные с сохранением паролей как
java.lang.String
, То есть все, представленное в виде строки, хранится в памяти в виде обычного текстового пароля и остается в памяти по крайней мере до тех пор, пока не будет собрано мусор.
GuardedString
Класс облегчает эту проблему, сохраняя символы в памяти в зашифрованном виде. Ключ шифрования будет случайным образом сгенерированным ключом.В их серийной форме,
GuardedString
s будет зашифрован с использованием известного ключа по умолчанию. Это должно обеспечить минимальный уровень защиты независимо от транспорта. Для связи с Remote Connector Framework рекомендуется, чтобы при развертывании был включен SSL для истинного шифрования.Приложения могут также пожелать сохраняться
GuardedString
, В случае Identity Manager, он должен преобразоватьGuardedString
сEncryptedData
чтобы их можно было хранить и управлять ими с помощью функций управления шифрованием Identity Manager. Другие приложения могут пожелать сериализацииAPIConfiguration
в целом. Эти приложения отвечают за шифрованиеAPIConfiguration
BLOB-объект для дополнительного уровня безопасности (помимо базового ключа шифрования по умолчанию, предоставляемогоGuardedString
).
Я изменил версию OWASP, чтобы случайным образом заполнить массив символов в памяти, чтобы массив символов в состоянии покоя не сохранялся с фактическими символами.
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;
}
}
}
Этот ответ добавляет немного больше объяснения sanketshah здорово ответ.
Следующий код показывает использование:
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
можно получить из maven IdentityConnectors: 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";
Небезопасным аспектом реализации является то, что они используют AES/CBC/PKCS5Padding
с жестко запрограммированным IV и ключом. Конструктор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);
}
}
}
Здесь есть место для улучшений:
- Используйте AES/CTR или AES/GCM вместо AES/CBC. (См. Режим работы блочного шифра.)
- Всегда используйте случайный IV и ключ.
GuardedString
использует внутренний методSecurityUtil.clear()
для очистки байтовых массивов, что обнуляет байты. Было бы неплохо иметь другие возможные алгоритмы стирания данных.
Я сам изучаю этот вопрос и, прочитав эту статью , понял, что:
- проблема не может быть полностью искоренена
- лучшее, что мы можем сделать, это зашифровать строку как можно раньше, и
- сотрите ячейку памяти, используемую для хранения незашифрованной строки, сразу после использования - из-за этого требования лучше всего использовать
char[]
вместоString
, так как последний неизменяем (и прибегать к рефлексии для вайпа не рекомендуется )
Для достижения этого можно было бы либо использоватьorg.identityconnectors.common.security.GuardedString
, часть этой библиотеки Oracle илиjavax.crypto.SealedObject
который является частью SDK, см. примеры здесь .
ТЛ;ДР,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);
}
}
:
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)));
}
}
Одна вещь, которая мне не нравится вGuardedString
приходится тянуть целую библиотеку для одной функции, и при этом «предприятие»:
<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>
Не говоря уже о том, что библиотека последний раз обновлялась 11 лет назад, но кто знает, может она просто стабильная.