Почему char [] предпочтительнее, чем String для паролей?


3423

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

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

Ответы:


4290

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

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

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

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


3
Если процесс имеет доступ к памяти вашего приложения, то это уже брешь в безопасности, верно?
Йети

5
@Yeti: Да, но это не так, как черно-белое. Если они могут получить только снимок памяти, вы хотите уменьшить ущерб, который может нанести этот снимок, или уменьшить окно, в течение которого можно сделать действительно серьезный снимок.
Джон Скит

11
Распространенным методом атаки является запуск процесса, который выделяет много памяти, а затем сканирует ее на предмет оставшихся полезных данных, таких как пароли. Процесс не нуждается в волшебном доступе к пространству памяти другого процесса; он просто полагается на то, что другие процессы умирают без предварительной очистки конфиденциальных данных, а ОС также не очищает память (или буферы страниц) перед тем, как сделать ее доступной для нового процесса. Удаление паролей, хранящихся в char[]местах, отключает эту линию атаки, что невозможно при использовании String.
Тед Хопп

Если ОС не очищает память перед передачей ее другому процессу, она имеет серьезные проблемы с безопасностью! Тем не менее, технически очистка часто выполняется с помощью трюков защищенного режима, и если процессор не работает (например, Intel Meldown), все еще возможно прочитать старое содержимое памяти.
Микко Ранталайнен

1222

В то время как другие предложения здесь кажутся действительными, есть еще одна веская причина. С обычным у 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

40
@ Voo, но я сомневаюсь, что вы войдете через прямую запись в поток и объединение. каркас ведения журнала преобразует char [] в хороший вывод
bestsss

41
@ Thr4wn Реализация по умолчанию toString- classname@hashcode. [Cпредставляет char[], остальное шестнадцатеричный хэш-код.
Конрад Гарус

15
Интересная идея. Я хотел бы отметить, что это не транспонирует в Scala, которая имеет значимую строку toString для массивов.
Маухиз

37
Я бы написал Passwordтип класса для этого. Это менее непонятно и сложнее случайно пройти где-нибудь.
правостороннее

8
Почему кто-то предположил, что массив char будет приведен как Object? Я не уверен, что понимаю, почему всем нравится этот ответ. Предположим, вы сделали это: System.out.println ("Password" .toCharArray ());
GC_

680

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

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

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

Рекомендация 2-2. Не регистрируйте конфиденциальную информацию

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

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


4
это именно ошибочная / фиктивная ссылка, о которой я говорю ниже в ответе Джона, это хорошо известный источник с большим количеством критики.
bestsss

37
@bestass не могли бы вы также привести ссылку?
user961954

10
@bestass извините, но Stringон достаточно хорошо понят и как он себя ведет в JVM ... есть веские причины, чтобы использовать его char[]вместо безопасного Stringобращения с паролями.
SnakeDoc

3
еще раз, хотя пароль передается в виде строки из браузера на запрос как «строка», а не символ? поэтому независимо от того, что вы делаете, это строка, в какой момент она должна обрабатываться и отбрасываться, а не храниться в памяти?
Давези

3
@Dawesi - At which pointэто зависит от приложения, но общее правило - делать это, как только вы получаете что-то, что должно быть паролем (открытым текстом или иным образом). Например, вы получаете его из браузера как часть HTTP-запроса. Вы не можете контролировать доставку, но вы можете контролировать свое собственное хранилище, поэтому, как только вы его получите, поместите его в символ [], сделайте то, что вам нужно с ним, затем установите все в «0» и позвольте gc вернуть его.
luis.espinal

354

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


13
Не защищен по умолчанию. Если мы говорим о веб-приложении, большинство веб-контейнеров передают пароль HttpServletRequestв виде открытого текста. Если версия JVM - 1.6 или ниже, она будет в постоянном пространстве. Если он в 1.7, он все еще будет читаемым, пока не будет собран. (Всякий раз, когда это так.)
avgvstvs

4
@avgvstvs: строки не перемещаются автоматически в область permgen, это относится только к интернированным строкам. Кроме того, пространство permgen также подлежит сборке мусора, но по более низкой ставке. Реальная проблема с пространством permgen - это его фиксированный размер, и именно поэтому никто не должен бездумно вызывать intern()произвольные строки. Но вы правы в том, что Stringэкземпляры существуют в первую очередь (до тех пор, пока они не собраны), и превращение их в char[]массивы впоследствии не изменит их.
Хольгер

3
@Holger, см. Docs.oracle.com/javase/specs/jvms/se6/html/… "В противном случае создается новый экземпляр класса String, содержащий последовательность символов Unicode, заданную структурой CONSTANT_String_info; этот экземпляр класса является результатом строковый литерал. Наконец, вызывается метод intern нового экземпляра String. " В 1.6 JVM будет вызывать для вас intern, когда обнаружит идентичные последовательности.
avgvstvs

3
@Holger, вы правы, я объединил пул констант и пул строк, но также неверно, что пространство permgen применяется только к внутренним строкам. До версии 1.7 constant_pool и string_pool находились в пространстве permgen. Это означает, что единственным классом Strings, которые были выделены для кучи, были, как вы сказали, new String()или StringBuilder.toString() я управлял приложениями с большим количеством строковых констант, и в результате у нас было много пермгена. До 1.7.
avgvstvs

5
@avgvstvs: ну, строковые константы, как и JLS-мандаты, всегда интернированы, поэтому утверждение, что интернированные строки оказались в пространстве permgen, неявно применяется к строковым константам. Единственное отличие состоит в том, что строковые константы были созданы в пространстве permgen в первую очередь, тогда как вызов intern()произвольной строки может вызвать выделение эквивалентной строки в пространстве permgen. Последний мог получить GC'ed, если бы не было буквальной строки с тем же содержимым, разделяющим этот объект ...
Хольгер

220

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

Обновить

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

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

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


33
Я бы заменил «совершенно бесполезный» на «просто незначительное улучшение безопасности». Например, вы можете получить доступ к дампу памяти, если у вас есть доступ на чтение к каталогу tmp, плохо настроенная машина и сбой в вашем приложении. В этом случае вы не сможете установить кейлоггер, но вы можете проанализировать дамп ядра.
Йоахим Зауэр

44
Стирание незашифрованных данных из памяти, как только вы закончите, считается лучшей практикой, а не потому, что она надежна (это не так); но потому что это снижает уровень вашей угрозы. Атаки в реальном времени не предотвращаются этим; но поскольку он служит инструментом уменьшения ущерба, значительно сокращает объем данных, которые подвергаются обратной атаке на снимок памяти (например, копия памяти ваших приложений, записанная в файл подкачки или считанная из памяти, выдернута) с работающего сервера и перешел на другой, прежде чем его состояние не удалось).
Дэн возится с огнем

9
Я склонен согласиться с позицией этого ответа. Рискну предположить, что большинство нарушений безопасности имеют место на гораздо более высоком уровне абстракции, чем биты в памяти. Несомненно, в системах сверхзащищенной защиты, вероятно, существуют сценарии, в которых это может вызывать серьезную озабоченность, но серьезно думать на этом уровне излишне для 99% приложений, в которых используется .NET или Java (поскольку это связано со сборкой мусора).
кингданго

10
После проникновения Heartbleed в серверную память и выявления паролей я заменил бы строку «просто незначительное улучшение безопасности» на «абсолютно необходимо не использовать String для паролей, а вместо этого использовать char []».
Питер vdL

9
@PetervdL heartbleed позволял только чтение определенной повторно используемой коллекции буферов (используемой как для критичных для безопасности данных, так и для сетевого ввода-вывода без очистки между ними - по соображениям производительности), вы не можете комбинировать это со строкой Java, так как они по своей конструкции не предназначены для повторного использования , Также вы не можете читать в случайную память, используя Java, чтобы получить содержимое строки. Проблемы с языком и дизайном, которые приводят к сердцебиению, просто невозможны с Java Strings.
josefx

86

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

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

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


4
Я бы не сказал, что проблема в том, что ваши ссылки будут или не будут "убегать". Просто строки будут оставаться неизменными в памяти в течение некоторого дополнительного времени, пока их char[]можно будет изменить, и тогда не имеет значения, собраны они или нет. И поскольку интернирование строк должно выполняться явно для не-литералов, это то же самое, что сказать, что на a char[]может ссылаться статическое поле.
Groo

1
пароль в памяти не является строкой из сообщения формы?
Давеси

66

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

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

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

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

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


Одним из способов решения этой проблемы является использование реализации PrivateKey, которая фактически не загружает свой частный контент в память: например, через аппаратный токен PKCS # 11. Возможно, программная реализация PKCS # 11 могла бы позаботиться об очистке памяти вручную. Возможно, лучше использовать что-то вроде хранилища NSS (которое разделяет большую часть своей реализации с PKCS11типом хранилища в Java). KeychainStore(OSX хранилища ключей) нагрузки на полное содержание секретного ключа в его PrivateKeyэкземпляры, но это не нужно. (Не уверен, что WINDOWS-MYKeyStore делает в Windows.)
Bruno

@ Bruno Конечно, аппаратные токены не страдают от этого, но как насчет ситуаций, когда вы более или менее вынуждены использовать программный ключ? Не каждое развертывание имеет бюджет, чтобы позволить себе HSM. Программные хранилища ключей в какой-то момент должны будут загрузить ключ в память, поэтому, по крайней мере, нам следует, по крайней мере, дать возможность очистить память снова.
выбить

Безусловно, мне было просто интересно, могут ли некоторые программные реализации, эквивалентные HSM, вести себя лучше с точки зрения очистки памяти. Например, при использовании client-auth с Safari / OSX процесс Safari фактически никогда не видит закрытый ключ, базовая библиотека SSL, предоставляемая ОС, напрямую связывается с демоном безопасности, который предлагает пользователю использовать ключ из цепочки для ключей. Хотя все это делается в программном обеспечении, подобное разделение, подобное этому, может помочь, если подпись делегирована другому объекту (даже программному), который лучше разгрузит или очистит память.
Бруно

@Bruno: Интересная идея, дополнительный слой косвенности, который заботится об очистке памяти, действительно решил бы это прозрачно. Написание оболочки PKCS # 11 для хранилища ключей программного обеспечения уже может помочь?
выбить

51

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

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

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: '***********'

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

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


2
Лучше также сделать что-нибудь, чтобы предотвратить печать длины пароля, который мы получаем '***********'.
chux - Восстановить Монику

@chux вы можете использовать символ нулевой ширины, хотя это может быть более запутанным, чем полезным. Невозможно изменить длину массива char без использования Unsafe. ;)
Питер Лоури

5
Из-за дедупликации строк в Java 8, я думаю, что-то подобное может быть довольно разрушительным ... вы можете в конечном итоге очистить другие строки в вашей программе, которые случайно имеют то же значение пароля String. Вряд ли, но возможно ...
Jamp

1
@PeterLawrey Он должен быть включен с аргументом JVM, но он есть. Можно прочитать об этом здесь: blog.codecentric.de/en/2014/08/...
JAMP

1
Существует высокая вероятность того, что пароль все еще находится во Scannerвнутреннем буфере и, поскольку вы не использовали System.console().readPassword(), в читаемой форме в окне консоли. Но для большинства практических случаев длительность usePasswordвыполнения является актуальной проблемой. Например, при установлении соединения с другим компьютером это занимает значительное время и сообщает злоумышленнику, что настало время для поиска пароля в куче. Единственное решение - не дать злоумышленникам прочитать кучу памяти ...
Хольгер

42

Это все причины, поэтому следует выбирать массив 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

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


10
Это избыточно. Этот ответ является точной версией ответа, написанного @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Пожалуйста, не копируйте и не дублируйте один и тот же ответ дважды.
Счастливое

В чем разница между 1.) System.out.println («Пароль символа:» + charPassword); и 2.) System.out.println (charPassword); Потому что он дает то же «неизвестное», что и вывод.
Vaibhav_Sharma

3
@Lucky К сожалению, старый ответ, на который вы ссылались, был плагиат из того же блога, что и этот ответ, и теперь удален. См. Meta.stackoverflow.com/questions/389144/… . Этот ответ просто вырезан и вставлен из того же блога и ничего не добавлен, так что это должен был быть просто комментарий, ссылающийся на первоисточник.
скомиса

41

Изменить: Возвращаясь к этому ответу после года исследований в области безопасности, я понимаю, что это весьма печально, что вы когда-либо сравнивать незашифрованные пароли. Пожалуйста, не надо. Используйте безопасный односторонний хеш с солью и разумным количеством итераций . Подумайте об использовании библиотеки: этот материал трудно понять правильно!

Оригинальный ответ: А как насчет того факта, что String.equals () использует оценку короткого замыкания и поэтому уязвим для временной атаки? Это может быть маловероятно, но теоретически вы можете рассчитать время сравнения пароля, чтобы определить правильную последовательность символов.

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        // Quits here if Strings are different lengths.
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // Quits here at first different character.
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

Еще несколько ресурсов о временных атаках:


Но это может быть и в сравнении char [], где-то мы будем делать то же самое и при проверке пароля. так чем же char [] лучше string?
Мохит Канвар

3
Вы абсолютно правы, ошибку можно совершить в любом случае. Знание этой проблемы является наиболее важным здесь, учитывая, что в Java не существует явного метода сравнения паролей для паролей на основе строк или char []. Я бы сказал, что искушение использовать compare () для Strings является хорошей причиной, чтобы пойти с char []. Таким образом, вы, по крайней мере, можете контролировать, как выполняется сравнение (без расширения String, что очень болезненно).
Теория графов

Помимо сравнения открытого текста пароли не правильно в любом случае, соблазн использовать Arrays.equalsдля char[]столь же высока , как и для String.equals. Если кому-то было интересно, был выделенный класс ключей, инкапсулирующий действительный пароль и заботящийся о проблемах - о, подождите, у реальных пакетов безопасности есть выделенные классы ключей, эти вопросы и ответы касаются только их привычки , скажем, например JPasswordField, использовать char[]вместо String(где фактические алгоритмы используют в byte[]любом случае).
Хольгер

Программное обеспечение, относящееся к безопасности, должно делать что-то вроде, sleep(secureRandom.nextInt())прежде чем отклонять попытку входа в систему, это не только исключает возможность синхронизации атак, но и противодействует грубым попыткам.
Хольгер

36

Массив char не даст вам ничего против String, если вы не очистите его вручную после использования, и я не видел, чтобы кто-нибудь на самом деле делал это. Поэтому для меня предпочтение char [] vs String немного преувеличено.

Посмотрите на широко используемой библиотеке Spring Security здесь и спросить себя - это Spring Security ребята недееспособными или символ [] пароли просто не имеет смысла. Когда какой-нибудь злобный хакер захватит дамп памяти вашей оперативной памяти, убедитесь, что он / она получит все пароли, даже если вы используете сложные способы их скрытия.

Тем не менее, Java постоянно меняется, и некоторые страшные функции, такие как функция дедупликации String в Java 8, могут работать с объектами String без вашего ведома. Но это другой разговор.


Почему страшна дедупликация строк? Это применимо только в том случае, если по крайней мере две строки имеют одинаковое содержимое, поэтому какая опасность может возникнуть, если эти две уже идентичные строки совместно используют один и тот же массив? Или давайте зададим другой вопрос: если нет дедупликации строк, какое преимущество имеет тот факт, что обе строки имеют отдельный массив (с одинаковым содержимым)? В любом случае, массив этого содержимого будет живым, по крайней мере, до тех пор, пока живая самая длинная строка этого содержимого…
Хольгер

@Holger все, что находится вне вашего контроля, представляет потенциальный риск ... например, если два пользователя имеют один и тот же пароль, эта замечательная функция будет хранить их обоих в одном символе [], давая понять, что они одинаковы, не уверен, что это так. огромный риск, но все же
Олег Михеев

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

@Holger, чтобы подтвердить пароль, он должен быть в виде открытого текста в памяти в течение некоторого времени, 10 мс, даже если он просто создает соленый хеш из него. Затем, если случится так, что в памяти останутся два идентичных пароля, даже на 10 мс, может возникнуть дедупликация. Если это действительно интерны строк, они хранятся в памяти гораздо дольше. Система, которая не перезагружается месяцами, соберет много таких. Просто теоретизирую.
Олег Михеев

Кажется, у вас есть фундаментальное недопонимание в отношении дедупликации строк. Он не «интернирует строки», все, что он делает, это позволяет строкам с одинаковым содержимым указывать на один и тот же массив, что фактически уменьшает количество экземпляров массива, содержащих пароль в виде открытого текста, поскольку все, кроме одного экземпляра массива, могут быть восстановлены и перезаписаны другими объектами немедленно. Эти строки все еще собираются как любая другая строка. Возможно, это поможет, если вы понимаете, что дедупликация фактически выполняется сборщиком мусора, только для строк, которые пережили несколько циклов GC.
Хольгер

30

Строки являются неизменяемыми и не могут быть изменены после того, как они были созданы. Создание пароля в виде строки оставит случайные ссылки на пароль в куче или в пуле строк. Теперь, если кто-то берет кучу дампов процесса Java и тщательно просматривает, он сможет угадать пароли. Конечно, эти неиспользуемые строки будут собираться мусором, но это зависит от того, когда включится GC.

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


Они будут только GC'd, если рассматриваемая JVM> 1.6. До 1.7 все строки хранились в permgen.
avgvstvs

@avgvstvs: «все строки были сохранены в permgen» просто неправильно. Там хранились только интернированные строки, и если они не были получены из строковых литералов, на которые ссылается код, они все равно были собраны сборщиком мусора. Просто подумай об этом. Если в JVM до 1.7 строки обычно никогда не собирались GCed, как могло бы выжить какое-либо Java-приложение дольше нескольких минут?
Хольгер

@ Холгер Это ложно. Интернированный stringsИ Stringпул (пул ранее использованных строк) ОБА хранился в Пермгене до 1.7. Также см. Раздел 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… JVM всегда проверяла, имеют Stringsли они одинаковое ссылочное значение, и будет ли вызывать String.intern()FOR YOU. В результате каждый раз, когда JVM обнаруживает идентичные строки в constant_poolкуче или в куче, она перемещает их в permgen. И я работал над несколькими приложениями с "creeping permgen" до 1.7. Это была настоящая проблема.
avgvstvs

Итак, резюмируем: до 1.7 строки начинались в куче, когда они использовались, их помещали в constant_poolкаталог IN permgen, а затем, если строка использовалась более одного раза, она была интернирована.
avgvstvs

@avgvstvs: нет «пула ранее использованных строк». Вы бросаете совершенно разные вещи вместе. Существует один пул строк времени выполнения, содержащий строковые литералы и явно интернированную строку, но другого нет. И у каждого класса есть свой постоянный пул, содержащий константы времени компиляции . Эти строки автоматически добавляются в пул времени выполнения, но только эти , а не все строки.
Хольгер

21

Строка является неизменной и идет в пул строк. После написания его нельзя перезаписать.

char[] это массив, который вы должны перезаписать после того, как вы использовали пароль, и вот как это должно быть сделано:

char[] passw = request.getPassword().toCharArray()
if (comparePasswords(dbPassword, passw) {
 allowUser = true;
 cleanPassword(passw);
 cleanPassword(dbPassword);
 passw=null;
}

private static void cleanPassword (char[] pass) {

Arrays.fill(pass, '0');
}

Один из сценариев, в котором злоумышленник может использовать это, - это аварийная остановка - когда JVM падает и генерирует дамп памяти - вы сможете увидеть пароль.

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


2
ch = ноль; Вы не можете сделать это
Yugerten

Но уже не request.getPassword()создает строку и не добавляет ее в пул?
Tvde1

1
ch = '0'изменяет локальную переменную ch; это не влияет на массив. И ваш пример в любом случае бессмысленен, вы начинаете с экземпляра строки, которую вы вызываете toCharArray(), создавая новый массив, и даже когда вы правильно перезаписываете новый массив, он не меняет экземпляр строки, поэтому он не имеет преимущества перед простым использованием строки пример.
Хольгер

@ Хольгер спасибо. Исправлен код очистки массива char.
ACV

3
Вы можете просто использоватьArrays.fill(pass, '0');
Хольгер

18

Короткий и прямой ответ будет потому, что char[]изменчив, а Stringобъекты нет.

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

Теперь сборка мусора в Java не происходит с гарантированным интервалом. Таким Stringобразом, он может сохраняться в памяти в течение длительного времени, и если в течение этого времени произойдет сбой процесса, содержимое строки может оказаться в дампе памяти или в каком-либо журнале.

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


@fallenidol Совсем нет. Читайте внимательно, и вы найдете различия.
Притам Банерджи

12

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

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

Из соображений безопасности лучше хранить пароль в виде массива символов.


2

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

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

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

Исходя из вышеизложенных обстоятельств, можно получить представление о том, использовать ли String или Char [] для их требований.


0

Строка дела:

    String password = "ill stay in StringPool after Death !!!";
    // some long code goes
    // ...Now I want to remove traces of password
    password = null;
    password = "";
    // above attempts wil change value of password
    // but the actual password can be traced from String pool through memory dump, if not garbage collected

Case CHAR ARRAY:

    char[] passArray = {'p','a','s','s','w','o','r','d'};
    // some long code goes
    // ...Now I want to remove traces of password
    for (int i=0; i<passArray.length;i++){
        passArray[i] = 'x';
    }
    // Now you ACTUALLY DESTROYED traces of password form memory
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.