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

Здесь есть место для улучшений:

  1. Используйте AES/CTR или AES/GCM вместо AES/CBC. (См. Режим работы блочного шифра.)
  2. Всегда используйте случайный IV и ключ.
  3. 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 лет назад, но кто знает, может она просто стабильная.

Другие вопросы по тегам