java - Почему char[] предпочитает использовать String для паролей?




security passwords (12)

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

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


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

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


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

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

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

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


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

Обновить

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

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

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


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

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

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


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

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

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

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

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

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

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


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

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

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


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

Исходный ответ: Как насчет того факта, что 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;
}

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


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

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


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

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

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

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


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

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 падает и генерирует дамп памяти - вы сможете увидеть пароль.

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


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

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





char