Чому для паролів char [] віддається перевагу над String?


3422

У Swing поле пароля має getPassword()(повертає char[]метод) замість звичайного getText()(повертає Stringметод). Так само я натрапив на пропозицію не використовувати Stringдля обробки паролів.

Чому це Stringстворює загрозу безпеці, якщо мова йде про паролі? Це відчуває незручність у використанні char[].

Відповіді:


4288

Струни незмінні . Це означає, що коли ви створили String, якщо інший процес може скинути пам'ять, немає ніякого способу (окрім рефлексії ), ви можете позбутися даних, перш ніж починається збирання сміття .

За допомогою масиву ви можете явно витерти дані після того, як ви закінчите їх. Ви можете перезаписати масив усім, що вам подобається, і пароль не буде присутній ніде в системі, навіть перед збиранням сміття.

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

Як зазначається в коментарях, цілком можливо, що масиви, що переміщуються сміттєзбірником, залишать заблоковані копії даних у пам'яті. Я вважаю, що це стосується конкретної реалізації - сміттєзбірник може очистити всю пам'ять, щоб уникнути подібних речей. Навіть якщо це так, все ще залишається час, протягом якого char[]містяться фактичні символи як вікно атаки.


3
Якщо процес має доступ до пам'яті вашої програми, то це вже порушення безпеки, правда?
Йєті

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

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

Якщо ОС не очищає пам'ять, перш ніж передати її іншому процесу, ОС має основні проблеми безпеки! Однак технічно очищення часто виконується із захищеними режимами захищеного режиму, і якщо ЦП порушений (наприклад, Intel Meldown), все-таки можна прочитати старий вміст пам'яті.
Мікко Ранталайнен

1221

Хоча інші пропозиції тут здаються дійсними, є ще одна вагома причина. З простою у 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 для масивів.
mauhiz

37
Я б написав Passwordдля цього тип класу. Це менш незрозуміло, і важче випадково кудись проїхати.
праворуч

8
Чому хтось вважає, що масив char буде переданий як Об'єкт? Я не впевнений, що отримую, чому всім подобається ця відповідь. Припустимо, ви зробили це: System.out.println ("Пароль" .toCharArray ());
GC_

680

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

Здавалося б, логічно збирати і зберігати пароль в об'єкті типу java.lang.String. Однак ось застереження: Objects типу 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
ще раз, хоча пароль передається як рядок від браузера до запиту як "string", а не char? тож незалежно від того, чим ти займаєшся, це рядок, в який момент слід діяти і відкидати, ніколи не зберігати в пам'яті?
Dawesi

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

354

Масиви символів ( char[]) можна очистити після використання, встановивши кожен символ на нуль, а рядки - ні. Якщо хтось може якимось чином бачити зображення пам'яті, він може бачити пароль у простому тексті, якщо використовуються рядки, але якщо char[]вони використовуються, після очищення даних з 0-х, пароль захищено.


13
За замовчуванням не захищено. Якщо ми говоримо про веб-додаток, більшість веб-контейнерів передадуть пароль HttpServletRequestоб'єкту в простому тексті. Якщо версія JVM дорівнює 1,6 або менше, вона буде знаходитися в пермгенському просторі. Якщо він знаходиться в 1.7, він все ще буде читабельним, поки він не буде зібраний. (Коли це так.)
avgvstvs

4
@avgvstvs: рядки не переміщуються автоматично в пермгенський простір, що стосується лише інтернованих рядків. Крім того, пермгенський простір також підлягає збору сміття, лише за меншою швидкістю. Справжня проблема з пермгенським простором - це його фіксований розмір, і саме це є причиною, чому ніхто не повинен бездумно закликати intern()до довільних рядків. Але ви маєте рацію в тому, що Stringекземпляри існують в першу чергу (поки зібрані), і перетворення їх у char[]масиви після цього не змінює цього.
Холгер

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

3
@Holger, ти прав, я зв'язав пул постійних пулів і рядків, але також помилково, що пермгенський простір застосовується лише до інтернованих рядків. До 1.7, і константа_pool, і string_pool проживали в пермгенському просторі. Це означає, що єдиним класом рядків, які були виділені до купи, були, як ви сказали, new String()або StringBuilder.toString() я керував програмами з великою кількістю струнних констант, і в нас було багато повзучості пермгену. До 1.7.
авгвствс

5
@avgvstvs: ну, константи рядків, як мандати JLS, завжди інтерновані, отже, твердження про те, що інтерновані рядки потрапили в пермгенський простір, застосовуються до рядкових констант неявно. Єдина відмінність полягає в тому, що константи рядків були створені в пермгенському просторі, тоді як виклик intern()довільної рядки може призвести до виділення еквівалентної рядки в пермгенському просторі. Останній міг би отримати GC'ed, якби не було буквальної рядки того самого вмісту, що поділяє цей об’єкт…
Holger

220

Деякі люди вважають, що вам доведеться перезаписати пам'ять, що використовується для зберігання пароля, як тільки він вам більше не потрібен. Це скорочує часове вікно, коли зловмисник повинен прочитати пароль з вашої системи і повністю ігнорує той факт, що зловмиснику вже потрібен достатній доступ для викрадення пам'яті JVM для цього. Зловмисник з таким великим доступом може зловити ваші ключові події, роблячи це абсолютно марним (AFAIK, тому, будь ласка, виправте мене, якщо я помиляюся).

Оновлення

Завдяки коментарям я маю оновити свою відповідь. Мабуть, є два випадки, коли це може додати (дуже) незначне поліпшення безпеки, оскільки це скорочує час, коли пароль може потрапити на жорсткий диск. І все-таки я думаю, що це надмірність для більшості випадків використання.

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

Якщо можливо, вимкнення основних скидів і файлу swap допоможе вирішити обидві проблеми. Однак вони вимагатимуть прав адміністратора і можуть знизити функціональність (менше використовувати пам'ять), а витяг оперативної пам’яті з працюючої системи все одно буде поважною проблемою.


33
Я б замінив "абсолютно марно" на "лише незначне поліпшення безпеки". Наприклад, ви можете отримати доступ до дампа пам'яті, якщо у вас трапляється доступ до читання до каталогу tmp, погано налаштована машина та збій у вашій програмі. У такому випадку ви не зможете встановити кейлоггер, але ви можете проаналізувати дамп ядра.
Йоахім Зауер

44
Витирання незашифрованих даних із пам'яті, як тільки ви закінчите з цим, вважається найкращою практикою не тому, що це надійно (це не так); а тому, що це знижує рівень викриття. Це не запобігає атакам у реальному часі; але тому, що він служить засобом для зменшення пошкоджень, значно зменшує кількість даних, які піддаються зворотній атаці на знімок пам'яті (наприклад, копію пам’яті ваших додатків, яка була записана у файл swap або вичитана з пам'яті з запущеного сервера і перейшов на інший до того, як його стан не вдався).
День вигадує Firelight

9
Я схильний погоджуватися з позицією цієї відповіді. Я б ризикну запропонувати більшість порушень безпеки наслідків, які відбуваються на набагато більшому рівні абстракції, ніж біти в пам'яті. Зрозуміло, є ймовірні сценарії в захищених захисних системах, де це може викликати серйозне занепокоєння, але серйозне роздуми на цьому рівні є надмірним для 99% програм, де .NET або Java використовуються (як це пов'язано зі збиранням сміття).
kingdango

10
Після проникнення Heartbleed в серверну пам'ять, виявлення паролів, я замінив би рядок "просто незначне поліпшення безпеки" на "абсолютно необхідне, щоб не використовувати String для паролів, а використовувати char [] замість цього".
Пітер vdL

9
@PetervdL з сердечним дозволом дозволяють читати певну повторно використану колекцію буферів (використовується як для критичних даних щодо безпеки, так і для мережевого вводу / виводу без очищення між ними - з міркувань продуктивності), ви не можете комбінувати це з рядком Java, оскільки вони за задумом не використовуються повторно . Не можна також читати у випадковій пам'яті за допомогою Java, щоб отримати вміст String. Проблеми з мовою та дизайном, які призводять до безпроблемного сервісу, просто не можливі з Java Strings.
josefx

86

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

Я думаю, що мотивація хоче переконатися, що ви зможете оперативно і з певністю видалити всі сліди пароля в пам'яті після його використання. З допомогою char[]ви можете перезаписати кожен елемент масиву порожнім або напевно. Ви не можете редагувати внутрішнє значення таким Stringчином.

Але це одне не є гарною відповіддю; чому б просто не переконатися, що посилання на char[]або Stringне уникне? Тоді проблем із безпекою немає. Але річ у тому, що Stringоб’єкти можна intern()редагувати теоретично і зберігати живими всередині постійного пулу. Я припускаю, що використовувати char[]таку можливість забороняє.


4
Я б не сказав, що проблема полягає в тому, що ваші посилання будуть або не "уникнуть". Просто рядки залишаться незмінними в пам'яті протягом деякого додаткового часу, тоді як вони char[]можуть бути змінені, і тоді не має значення, збираються вони чи ні. А оскільки строкове інтернування потрібно робити явно для нелітералів, це те саме, що говорити про те, що на char[]посилання може посилатися статичне поле.
Гроо

1
Чи не пароль в пам'яті як рядок із публікації форми?
Dawesi

66

Відповідь вже дана, але я хотів би поділитися проблемою, яку я нещодавно виявив зі стандартними бібліотеками Java. У той час як вони дуже піклуються про заміну рядків паролів на char[]всюди (що, звичайно, це добре), але інші критично важливі для безпеки дані, здається, не помічаються, коли мова йде про очищення їх з пам'яті.

Я думаю, наприклад, про клас PrivateKey . Розглянемо сценарій, коли ви завантажите приватний ключ RSA з файлу PKCS # 12, використовуючи його для виконання певної операції. Тепер у цьому випадку нюхання пароля не допоможе вам сильно, якщо фізичний доступ до ключового файлу обмежений належним чином. Як зловмисник, вам було б набагато краще, якби ви отримали ключ безпосередньо замість пароля. Бажана інформація може бути просоченою колектором, основними дампами, сеансом налагодження або файлами swap - лише деякі приклади.

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

Це погана ситуація, оскільки ця стаття описує, як ця обставина може бути потенційно використана.

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


Одним із способів цього є використання реалізації PrivateKey, яка фактично не завантажує його приватний вміст у пам'ять: наприклад, через апаратний маркер PKCS # 11. Можливо, реалізація програмного забезпечення PKCS №11 могла б подбати про очищення пам'яті вручну. Можливо, використовувати щось на кшталт магазину NSS (який поділяє більшу частину його реалізації з PKCS11типом магазину на Java). KeychainStore(OSX сховища ключів) навантаження на повне утримання секретного ключа в його PrivateKeyекземпляри, але це не потрібно. (Не впевнений, що WINDOWS-MYробить KeyStore у Windows.)
Бруно

@Bruno Звичайно, жетони на основі апаратних засобів від цього не страждають, але як бути з ситуаціями, коли вас більш-менш змушують використовувати програмний ключ? Далеко не кожне розгортання має бюджет, щоб дозволити отримати HSM. Зберігання програмних ключів у якийсь момент доведеться завантажити ключ у пам'ять, тому IMO нам, принаймні, слід дати можливість очистити пам'ять за бажанням.
тиснення

Абсолютно мені було цікаво, чи можуть деякі програми програмного забезпечення, еквівалентні HSM, вести себе краще в плані очищення пам'яті. Наприклад, коли використовується клієнт-авт із 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's char]] було скопійовано як частину циклу GC, є ймовірність, що попередня копія знаходиться десь у пам'яті.

Ця стара копія не відображатиметься на купі, але якщо у вас є прямий доступ до необмеженої пам'яті процесу, ви можете її бачити. Взагалі слід уникати того, щоб хтось мав такий доступ.


2
Краще також зробити щось, щоб запобігти друку довжини пароля, з якого ми отримуємо '***********'.
chux

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

5
Через дедуплікацію рядків Java 8, я думаю, що робити щось подібне може бути досить руйнівним ... ви можете в кінцевому рахунку очистити інші рядки у вашій програмі, які випадково мали те саме значення, що і пароль String. Навряд чи, але можливо ...
кокетка

1
@PeterLawrey Потрібно ввімкнути аргумент JVM, але він є. Про це можна прочитати тут: blog.codecentric.de/en/2014/08/…
jamp

1
Існує велика ймовірність, що пароль все ще знаходиться у Scannerвнутрішньому буфері і, оскільки ви його не використовували System.console().readPassword(), у читаному вигляді у вікні консолі. Але для більшості випадків практичного використання usePasswordактуальною є проблема тривалості виконання. Наприклад, підключення до іншої машини потребує значного часу і повідомляє зловмиснику, що саме час шукати пароль у купі. Єдине рішення - не допустити зловмисників до читання купи пам'яті…
Холгер

42

Це все причини. Для пароля слід вибрати масив char [] замість String .

1. Оскільки Strings незмінні в Java, якщо ви зберігаєте пароль як звичайний текст, він буде доступний у пам'яті, поки збирач сміття не очистить його, а оскільки String використовується в пулі String для повторного використання, є досить великий шанс, що він буде залишаються в пам’яті на тривалий час, що становить загрозу безпеці.

Оскільки кожен, хто має доступ до дампа пам’яті, може знайти пароль у чистому тексті, це ще одна причина, що ви завжди повинні використовувати зашифрований пароль, а не звичайний текст. Оскільки рядки незмінні, то зміст рядків не може бути змінено, оскільки будь-яка зміна призведе до появи нового 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/… . Ця відповідь просто вирізала та вставила з того самого блогу і нічого не додала, тому це повинен був бути просто коментарем, що посилається на першоджерело.
skomisa

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 [] краще, ніж рядок?
Мохіт Канвар

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

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

Відповідне програмне забезпечення для безпеки повинно робити щось на кшталт sleep(secureRandom.nextInt())перед відхиленням спроби входу, це не лише усуває можливість синхронізованих атак, але й протидіє грубим спробам.
Холгер

36

Масив char не дає вам проти String, якщо ви не очищаєте його вручну після використання, і я не бачив, щоб хтось це робив. Тож для мене перевага char [] проти String трохи перебільшена.

Погляньте на широко розповсюджену тут бібліотеку Spring Security і запитайте себе - невмілі хлопці Spring Security або паролі char [] просто не мають великого сенсу. Коли якийсь неприємний хакер схопить пам'ять вашої оперативної пам’яті, будьте впевнені, що він / він отримає всі паролі, навіть якщо ви використовуєте складні способи їх приховання.

Однак Java постійно змінюється, і деякі страшні функції, такі як функція дедуплікації рядків Java 8, можуть інтернувати рядкові об'єкти без вашого відома. Але це вже інша розмова.


Чому рядок-дедуплікація страшно? Він застосовується лише тоді, коли є щонайменше дві струни з однаковим вмістом, тож яка небезпека виникла б у тому випадку, якщо ці дві вже однакові рядки мають спільний масив? Або давайте запитаємо навпаки: якщо немає дедупликації рядків, яка перевага випливає з того, що обидва рядки мають чіткий масив (однакового вмісту)? В будь-якому випадку, масив цього вмісту буде живий принаймні до тих пір, поки живий найдовший рядок цього вмісту…
Холгер

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

Якщо у вас є доступ до пам’яті купи і обох екземплярів рядків, не має значення, чи вказують рядки на один і той же масив або на два масиви з однаковим вмістом, кожен легко знайти. Тим більше, як це все одно не має значення. Якщо ви в цей момент, ви захопите обидва паролі, незалежні чи ні. Фактична помилка полягає у використанні паролів простого тексту замість солених хешів.
Холгер

@Holger щоб перевірити пароль, він повинен бути чистим текстом у пам'яті протягом деякого часу, 10 мс, навіть якщо це просто створити з нього солоний хеш. Тоді, якщо трапляється, що в пам’яті зберігаються два однакових пароля навіть протягом 10 мс, дедуплікація може прийти в дію. Якщо це дійсно інтерни-струни, вони зберігаються в пам'яті набагато довший час. Система, яка не перезавантажується місяцями, збирає багато з них. Просто теоретизуючи.
Олег Міхеєв

Здається, у вас є принципове непорозуміння щодо присвоєння рядків. Це не "рядки інтернів", все, що він робить, дозволяє рядкам з однаковим вмістом вказувати на той самий масив, що фактично зменшує кількість екземплярів масиву, що містять пароль простого тексту, оскільки всі, крім одного екземпляра масиву, можуть бути відтворені та перезаписані іншими об'єктами негайно. Ці рядки все ще збираються, як і будь-який інший рядок. Можливо, це допоможе, якщо ви розумієте, що дедублювання насправді здійснюється сміттєзбірником для рядків, які пережили лише кілька циклів GC.
Хольгер

30

Рядки незмінні і не можуть бути змінені після їх створення. Якщо створити пароль у вигляді рядка, це залишатиме бродячі посилання на пароль на купі або в пулі String. Тепер, якщо хтось виконує куповий процес Java і ретельно перевіряє, він може вгадати паролі. Звичайно, ці рядки, що не використовуються, збиратимуть сміття, але це залежить від того, коли запуститься GC.

З іншого боку char [] змінюються, як тільки аутентифікація виконана, ви можете перезаписати їх будь-яким символом, як усі M або зворотні косої риски. Тепер, навіть якщо хтось бере звалище, він не зможе отримати паролі, які наразі не використовуються. Це дає вам більше контролю в тому сенсі, як очищення вмісту Об'єкта самостійно проти очікування, коли GC зробить це.


Вони будуть GC'd лише в тому випадку, якщо спірний JVM є> 1.6. До 1.7 всі рядки зберігалися в пермґену.
avgvstvs

@avgvstvs: "всі рядки були збережені в permgen" - це просто невірно. Там зберігалися лише інтерновані рядки, і якщо вони не походять із рядкових літералів, на які посилається код, вони все одно збирали сміття. Просто подумайте. Якщо рядки, як правило, ніколи не GCed в JVM до 1.7, як би будь-яке додаток Java витримало більше декількох хвилин?
Холгер

@Holger Це помилково. Інтернований stringsAND Stringпул (пул раніше використовуваних рядків) були ДОДАТО зберігатися в Пермгені до 1,7. Також дивіться розділ 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… JVM завжди перевіряв, Stringsчи є вони однаковим контрольним значенням, і закликає String.intern()ДЛЯ ВАС. Результат полягав у тому, що кожного разу, коли JVM constant_poolвиявляв однакові рядки в купі, він переміщав їх у пермген. І я працював над кількома додатками з «повзучим пермгеном» до 1,7. Це була справжня проблема.
avgvstvs

Отже, до резюмування: До 1.7, струни починалися в купі, і коли вони використовувались, їх клали в constant_poolпермген, який знаходився IN, а потім, якщо рядок використовувався не один раз, він буде інтернований.
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 = null; ви не можете цього зробити
Югертен

Але ви вже не request.getPassword()створюєте рядок і не додаєте його до пулу?
Tvde1

1
ch = '0'змінює локальну змінну ch; це не впливає на масив. І ваш приклад в будь-якому разі є безглуздим, ви починаєте з рядкового екземпляра, на який ви звертаєтесь toCharArray(), створюючи новий масив, і навіть коли ви правильно перезаписуєте новий масив, він не змінює екземпляр рядка, тому він не має переваги перед просто використанням рядка екземпляр.
Хольгер

@Holger спасибі Виправлено код очищення масиву char.
ACV

3
Можна просто скористатисяArrays.fill(pass, '0');
Holger

18

Короткий і прямий відповідь був би тому, що char[]він міняється, а Stringоб'єкти - ні.

Stringsна Яві - незмінні об'єкти. Ось чому вони не можуть бути змінені після створення, і тому єдиний спосіб видалення їх вмісту з пам'яті - це збирання сміття. Це буде лише тоді, коли пам'ять, звільнена об'єктом, може бути перезаписана, а дані не зникнуть.

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

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


@fallenidol Зовсім не. Прочитайте уважно, і ви знайдете відмінності.
Прітам Банерджі

12

Рядок у яві непорушний. Тож щоразу, коли створюється рядок, вона залишатиметься в пам’яті, поки не буде зібрана сміття. Тож кожен, хто має доступ до пам'яті, може прочитати значення рядка.
Якщо значення рядка буде змінено, воно в кінцевому підсумку створить новий рядок. Так і вихідне значення, і змінене значення залишаються в пам'яті, поки воно не зібране сміття.

За допомогою символьного масиву вміст масиву може бути змінено або стерто, як тільки призначено призначення пароля. Оригінальний вміст масиву не знайдеться в пам'яті після його модифікації та ще до того, як починає збиратися сміття.

Через проблеми безпеки краще зберігати пароль як масив символів.


2

Дискусійним є питання про те, чи слід використовувати String або використовувати Char [] для цієї мети, оскільки обидва мають свої переваги та недоліки. Це залежить від того, що потрібно користувачеві.

Оскільки рядки в Java незмінні, кожен раз, коли хтось намагається маніпулювати вашим рядком, він створює новий Об'єкт, і існуючий рядок залишається незмінним. Це може розглядатися як перевага для зберігання пароля як String, але об'єкт залишається в пам'яті навіть після використання. Тож якщо хтось якось отримав місце пам'яті об'єкта, ця людина може легко простежити ваш пароль, що зберігається в цьому місці.

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

Справа 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.