Почему char [] предпочтительнее, чем String для паролей? - PullRequest
3198 голосов
/ 16 января 2012

В Swing поле пароля имеет метод getPassword() (возвращает char[]) вместо обычного метода getText() (возвращает String). Точно так же я натолкнулся на предложение не использовать String для обработки паролей.

Почему String представляет угрозу безопасности, когда речь идет о паролях? Это неудобно для использования char[].

Ответы [ 18 ]

4064 голосов
/ 16 января 2012

Строки неизменны .Это означает, что после того, как вы создали String, если другой процесс может создавать дамп памяти, вы не сможете (кроме отражение ) избавиться от данных до того, как сборщик мусора ударитin.

С массивом вы можете явно стереть данные после того, как с ним покончено.Вы можете перезаписать массив всем, что захотите, и пароль не будет присутствовать нигде в системе, даже перед сборкой мусора.

Так что да, этот является проблемой безопасности - нодаже использование char[] только уменьшает окно возможностей для злоумышленника, и это только для этого конкретного типа атаки.

Как отмечалось в комментариях, возможно, что массивы, перемещаемые сборщиком мусора, оставят заблудшиекопии данных в памяти.Я полагаю, что это зависит от реализации - сборщик мусора может очищать всю память по мере необходимости, чтобы избежать подобных вещей.Даже если это так, есть время, в течение которого char[] содержит действительные символы в качестве окна атаки.

1166 голосов
/ 16 января 2012

В то время как другие предложения кажутся обоснованными, есть еще одна веская причина. С обычным String у вас гораздо больше шансов случайно распечатать пароль для журналов , мониторов или других небезопасных мест. char[] менее уязвим.

Учтите это:

public static void main(String[] args) {
    Object pw = "Password";
    System.out.println("String: " + pw);

    pw = "Password".toCharArray();
    System.out.println("Array: " + pw);
}

Печать:

String: Password
Array: [C@5829428e
654 голосов
/ 17 января 2012

Чтобы процитировать официальный документ, в руководстве по архитектуре Java Cryptography говорится о char[] против String паролей (о шифровании на основе паролей, но, конечно, в целом о паролях):

Казалось бы логично собирать и хранить пароль в объекте типа java.lang.String. Тем не менее, вот предостережение: Object с Тип String является неизменным, т. е. не определены методы, которые позволяют изменить (перезаписать) или обнулить содержимое String после использования. Эта функция делает String объекты непригодными для хранение конфиденциальной информации, такой как пароли пользователей. Вы всегда следует собирать и хранить конфиденциальную информацию о безопасности в char вместо массива.

В Руководстве 2-2 Руководства по безопасному кодированию для языка программирования Java, версия 4.0 также говорится что-то похожее (хотя изначально это было в контексте ведения журнала):

Рекомендация 2-2: не регистрировать особо конфиденциальную информацию

Некоторая информация, такая как номера социального страхования (SSN) и пароли, очень чувствительны. Эта информация не должна храниться дольше, чем это необходимо, ни там, где это можно увидеть, даже администраторы. Например, его не следует отправлять в файлы журналов и его присутствие не должно быть обнаружено при поиске. Некоторые переходные данные могут храниться в изменчивых структурах данных, таких как массивы символов, и очищается сразу после использования. Очистка структур данных уменьшила эффективность в типичных системах времени выполнения Java при перемещении объектов в память прозрачно для программиста.

Это руководство также имеет значение для реализации и использования библиотеки более низкого уровня, которые не имеют семантического знания данных они имеют дело с. Как пример, низкоуровневый анализ строки библиотека может регистрировать текст, над которым она работает. Приложение может анализировать SSN с библиотекой. Это создает ситуацию, когда SSN доступно администраторам с доступом к файлам журнала.

335 голосов
/ 16 января 2012

Массивы символов (char[]) можно очистить после использования, установив для каждого символа ноль, а для строк - нет. Если кто-то каким-то образом видит образ памяти, он может видеть пароль в виде простого текста, если используются строки, но если используется char[], после очистки данных с нулями пароль является безопасным.

207 голосов
/ 16 января 2012

Некоторые люди считают, что вам нужно перезаписать память, использованную для хранения пароля, когда он вам больше не нужен. Это сокращает интервал времени, в течение которого злоумышленнику приходится считывать пароль из вашей системы, и полностью игнорирует тот факт, что злоумышленнику уже необходим достаточный доступ для захвата памяти JVM, чтобы сделать это. Злоумышленник с таким большим доступом может перехватить ваши ключевые события, что делает его совершенно бесполезным (AFAIK, поэтому, пожалуйста, исправьте меня, если я ошибаюсь).

Обновление

Благодаря комментариям я должен обновить свой ответ. Очевидно, есть два случая, когда это может добавить (очень) незначительное улучшение безопасности, поскольку это уменьшает время, когда пароль может попасть на жесткий диск. Тем не менее, я думаю, что это излишне для большинства случаев использования.

  • Ваша целевая система может быть плохо настроена, или вы должны предположить, что это так, и вы должны быть параноиком по поводу дампов ядра (может быть допустимо, если системы не управляются администратором).
  • Ваше программное обеспечение должно быть чрезмерно параноидальным, чтобы предотвратить утечку данных, когда злоумышленник получит доступ к оборудованию - используя такие вещи, как TrueCrypt (прекращено), VeraCrypt или CipherShed .

Если возможно, отключение дампов ядра и файла подкачки решит обе проблемы. Тем не менее, они потребуют прав администратора и могут снизить функциональность (меньше памяти для использования), а извлечение ОЗУ из работающей системы все равно будет иметь значение.

135 голосов
/ 28 декабря 2012
  1. Строки являются неизменяемыми в Java, если вы храните пароль в виде обычного текста, он будет доступен в памяти, пока сборщик мусора не очистит его, и поскольку строки используются в пуле строк для повторного использования, существует довольно высокий шанс что он останется в памяти на долгое время, что создает угрозу безопасности. Поскольку любой, кто имеет доступ к дампу памяти, может найти пароль в открытом тексте
  2. Рекомендация Java с использованием getPassword() метода JPasswordField, который возвращает символ char [], и устаревшего getText() метода, который возвращает пароль в виде открытого текста с указанием причины безопасности.
  3. toString () всегда существует риск печати простого текста в файле журнала или консоли, но если использовать массив, вы не будете печатать содержимое массива, вместо этого будет напечатана его ячейка памяти.

    String strPwd = "passwd";
    char[] charPwd = new char[]{'p','a','s','s','w','d'};
    System.out.println("String password: " + strPwd );
    System.out.println("Character password: " + charPwd );
    

    Строка пароля: passwd

    Символьный пароль: [C @ 110b2345

Заключительные мысли: Хотя использование char [] недостаточно, вам нужно стереть контент, чтобы быть более безопасным. Я также предлагаю работать с хешированным или зашифрованным паролем вместо простого текста и удалять его из памяти, как только аутентификация будет завершена.

80 голосов
/ 16 января 2012

Я не думаю, что это правильное предложение, но, по крайней мере, я могу предположить причину.

Я думаю, что мотивация состоит в том, чтобы убедиться, что вы можете стереть все следы пароля впамять оперативно и с уверенностью после ее использования.С char[] вы можете наверняка перезаписать каждый элемент массива пустым или чем-то еще.Вы не можете редактировать внутреннее значение String таким образом.

Но это само по себе не является хорошим ответом;почему бы просто не убедиться, что ссылка на char[] или String не исчезла?Тогда нет проблем с безопасностью.Но дело в том, что String объекты можно теоретически редактировать и поддерживать в постоянном пуле.Я полагаю, использование char[] запрещает эту возможность.

62 голосов
/ 19 января 2012

Ответ уже дан, но я хотел бы поделиться проблемой, которую я обнаружил в последнее время, со стандартными библиотеками Java.Несмотря на то, что они очень внимательно следят за заменой строк пароля на char[] везде (что, конечно, хорошо), другие важные для безопасности данные, по-видимому, упускаются из виду при очистке их из памяти.

IЯ думаю о, например, PrivateKey класс.Рассмотрим сценарий, в котором вы бы загружали закрытый ключ RSA из файла PKCS # 12, используя его для выполнения какой-либо операции.Теперь, в этом случае, прослушивание пароля само по себе не очень поможет, если физический доступ к файлу ключа должным образом ограничен.Как злоумышленнику, вам будет гораздо лучше, если вы получите ключ вместо пароля.Желаемая информация может быть утечка многообразия, дампы ядра, сеанс отладчика или файлы подкачки - это только некоторые примеры.

И, как выясняется, нет ничего, что позволяло бы очистить личную информацию PrivateKey отпамяти, потому что нет API, который позволял бы вам стирать байты, формирующие соответствующую информацию.

Это плохая ситуация, поскольку в этой статье описывается, как это обстоятельство может быть потенциально использовано.

Библиотека OpenSSL, например, перезаписывает критические разделы памяти перед освобождением закрытых ключей.Поскольку Java является сборщиком мусора, нам потребуются явные методы для очистки и аннулирования частной информации для ключей Java, которые должны применяться сразу после использования ключа.

47 голосов
/ 08 июля 2015

Как утверждает Джон Скит, нет другого выхода, кроме как с помощью отражения.

Однако, если для вас есть вариант отражения, вы можете сделать это.

public static void main(String[] args) {
    System.out.println("please enter a password");
    // don't actually do this, this is an example only.
    Scanner in = new Scanner(System.in);
    String password = in.nextLine();
    usePassword(password);

    clearString(password);

    System.out.println("password: '" + password + "'");
}

private static void usePassword(String password) {

}

private static void clearString(String password) {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] chars = (char[]) value.get(password);
        Arrays.fill(chars, '*');
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

при запуске

please enter a password
hello world
password: '***********'

Примечание: если символ String [] был скопирован как часть цикла GC, есть вероятность, что предыдущая копия находится где-то в памяти.

Эта старая копия не будет отображаться в дампе кучи, но если у вас есть прямой доступ к необработанной памяти процесса, вы можете ее увидеть. В общем, вы должны избегать любого, имеющего такой доступ.

40 голосов
/ 08 мая 2014

Это все причины, поэтому нужно выбрать массив char [] вместо String для пароля.

1. Поскольку строки являются неизменяемыми в Java, если вы сохраните пароль в виде простого текста, он будет доступен в памяти, пока сборщик мусора не очистит его, и поскольку String используется в пуле строк для повторного использования существует довольно высокая вероятность того, что он останется в памяти на длительный срок, что создает угрозу безопасности.

Поскольку любой, кто имеет доступ к дампу памяти, может найти пароль в виде открытого текста, это еще одна причина, по которой вы всегда должны использовать зашифрованный пароль, а не простой текст. Поскольку строки являются неизменяемыми, содержимое строк не может быть изменено, потому что любое изменение приведет к созданию новой строки, в то время как если вы используете char [], вы все равно можете установить все элементы как пустые или нулевые. Таким образом, хранение пароля в массиве символов явно снижает риск кражи пароля.

2. Сама Java рекомендует использовать метод getPassword () из JPasswordField, который возвращает char [], вместо устаревшего метода getText (), который возвращает пароли в виде открытого текста с указанием соображений безопасности. Хорошо следовать советам команды Java и придерживаться стандартов, а не идти против них.

3. При использовании String всегда существует риск печати простого текста в файле журнала или консоли, но если вы используете массив, вы не будете печатать содержимое массива, а вместо этого получит место в памяти распечатаны. Хотя это и не реальная причина, это все же имеет смысл.

String strPassword="Unknown";
char[] charPassword= new char[]{'U','n','k','w','o','n'};
System.out.println("String password: " + strPassword);
System.out.println("Character password: " + charPassword);

String password: Unknown
Character password: [C@110b053

Ссылка от этого блога . Надеюсь, это поможет.

...