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

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

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

17 ответов


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

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

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

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


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

процитирую официальный документ -Java Cryptography Architecture guide говорит Это О char[] и String пароли (о парольном шифровании, но это, как правило, о паролях, конечно):

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

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

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

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

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


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


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

обновление

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

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

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


  1. строки неизменяемы в Java если вы храните пароль как обычный текст, он будет доступен в памяти, пока сборщик мусора не очистит его, и поскольку строки используются в пуле строк для повторного использования, существует довольно высокая вероятность того, что он останется в памяти в течение длительного времени, что создает угрозу безопасности. Поскольку любой, кто имеет доступ к дампу памяти, может найти пароль в открытом тексте
  2. рекомендация Java используя getPassword() метод JPasswordField, который возвращает char[] и deprecated 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[] недостаточно, вам нужно удалить контент, чтобы быть более безопасным. Я также предлагаю работать с хэшированным или зашифрованным паролем вместо обычного текста и очищать его из памяти, как только аутентификация будет завершена.


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

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

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


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

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

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

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

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


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

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

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 [] строки был скопирован как часть цикла GC, есть шанс, что предыдущая копия находится где-то в памяти.

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


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

оригинальный ответ: насчет того, что строка.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[] массив вместо строка для пароль.

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

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

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

    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

ссылка из: http://javarevisited.blogspot.com/2012/03/why-character-array-is-better-than.html Надеюсь, это поможет.


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

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

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


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

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


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

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

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

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


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

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


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

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

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


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

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

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

private static void cleanPassword (char[] pass) {
 for (char ch: pass) {
  ch = null;
 }
}

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

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