Mengapa char [] lebih disukai daripada String untuk kata sandi?

3422

Dalam Swing, bidang kata sandi memiliki metode getPassword()(pengembalian char[]) alih-alih metode getText()(pengembalian String) biasa . Demikian pula, saya menemukan saran untuk tidak menggunakan Stringkata sandi.

Mengapa Stringmenimbulkan ancaman bagi keamanan ketika menyangkut kata sandi? Rasanya tidak nyaman untuk digunakan char[].

Malu
sumber

Jawaban:

4290

String tidak berubah . Itu berarti setelah Anda membuat String, jika proses lain dapat membuang memori, tidak ada cara (selain dari refleksi ) Anda dapat membuang data sebelum pengumpulan sampah dimulai.

Dengan sebuah array, Anda dapat menghapus data secara eksplisit setelah selesai. Anda dapat menimpa array dengan apa pun yang Anda suka, dan kata sandi tidak akan ada di mana pun dalam sistem, bahkan sebelum pengumpulan sampah.

Jadi ya, ini adalah masalah keamanan - tetapi bahkan menggunakan char[]hanya mengurangi jendela peluang bagi penyerang, dan itu hanya untuk jenis serangan khusus ini.

Seperti disebutkan dalam komentar, ada kemungkinan bahwa array yang dipindahkan oleh pengumpul sampah akan meninggalkan salinan data yang tersesat dalam memori. Saya percaya ini khusus implementasi - pengumpul sampah dapat menghapus semua memori saat berjalan, untuk menghindari hal semacam ini. Bahkan jika itu terjadi, masih ada waktu di mana char[]berisi karakter yang sebenarnya sebagai jendela serangan.

Jon Skeet
sumber
3
Jika suatu proses memiliki akses ke memori aplikasi Anda, maka itu sudah merupakan pelanggaran keamanan, bukan?
Yeti
5
@ Yeti: Ya, tapi tidak hitam dan putih. Jika mereka hanya bisa mendapatkan snapshot dari memori maka Anda ingin mengurangi seberapa banyak kerusakan yang dapat snapshot lakukan, atau mengurangi jendela di mana snapshot yang sangat serius dapat diambil.
Jon Skeet
11
Metode serangan yang umum adalah menjalankan proses yang mengalokasikan banyak memori dan kemudian memindai untuk sisa, data yang berguna seperti kata sandi. Proses tidak memerlukan akses ajaib ke ruang memori proses lain; itu hanya bergantung pada proses lain yang sekarat tanpa terlebih dahulu membersihkan data sensitif dan OS juga tidak membersihkan memori (atau buffer halaman) sebelum membuatnya tersedia untuk proses baru. Menghapus kata sandi yang disimpan di char[]lokasi memotong garis serangan itu, sesuatu yang tidak mungkin saat digunakan String.
Ted Hopp
Jika OS tidak menghapus memori sebelum memberikannya ke proses lain, OS memiliki masalah keamanan utama! Namun, secara teknis pembersihan sering dilakukan dengan trik mode terlindungi dan jika CPU rusak (misalnya Intel Meldown) masih mungkin untuk membaca konten memori yang lama.
Mikko Rantalainen
1222

Sementara saran lain di sini tampaknya valid, ada satu alasan bagus lainnya. Dengan polos StringAnda memiliki peluang lebih tinggi untuk secara tidak sengaja mencetak kata sandi ke log , monitor, atau tempat tidak aman lainnya. char[]kurang rentan.

Pertimbangkan ini:

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

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

Cetakan:

String: Password
Array: [C@5829428e
Konrad Garus
sumber
40
@ voo, tapi saya ragu Anda akan login melalui tulisan langsung untuk streaming dan penggabungan. kerangka kerja logging akan mengubah char [] menjadi output yang baik
bestsss
41
@ Thr4wn Implement default dari toStringis classname@hashcode. [Cmewakili char[], sisanya adalah kode hash heksadesimal.
Konrad Garus
15
Ide yang menarik. Saya ingin menunjukkan bahwa ini tidak beralih ke Scala yang memiliki toString bermakna untuk array.
mauhiz
37
Saya akan menulis Passwordjenis kelas untuk ini. Ini kurang jelas, dan sulit untuk secara tidak sengaja melewati suatu tempat.
rightfold
8
Mengapa seseorang menganggap array char akan dilemparkan sebagai Object? Saya tidak yakin mengapa saya menyukai jawaban ini. Misalkan Anda melakukan ini: System.out.println ("Kata Sandi" .toCharArray ());
GC_
680

Mengutip dokumen resmi, panduan Arsitektur Kriptografi Java mengatakan ini tentang kata sandi char[]vs. String(tentang enkripsi berbasis kata sandi, tetapi ini lebih umum tentang kata sandi tentu saja):

Tampaknya logis untuk mengumpulkan dan menyimpan kata sandi dalam objek bertipe java.lang.String. Namun, inilah peringatannya: Objecttipe Stringtidak dapat diubah, yaitu, tidak ada metode yang didefinisikan yang memungkinkan Anda untuk mengubah (menimpa) atau nol konten dari String penggunaan setelah. Fitur ini membuat Stringobjek tidak cocok untuk menyimpan informasi sensitif keamanan seperti kata sandi pengguna. Anda harus selalu mengumpulkan dan menyimpan informasi sensitif keamanan dalam chararray.

Panduan 2-2 dari Panduan Pengkodean Aman untuk Bahasa Pemrograman Java, Versi 4.0 juga mengatakan sesuatu yang serupa (meskipun aslinya dalam konteks logging):

Panduan 2-2: Jangan mencatat informasi yang sangat sensitif

Beberapa informasi, seperti nomor Jaminan Sosial (SSN) dan kata sandi, sangat sensitif. Informasi ini tidak boleh disimpan lebih lama dari yang diperlukan atau di mana pun dapat dilihat, bahkan oleh administrator. Misalnya, itu tidak boleh dikirim ke file log dan keberadaannya tidak dapat dideteksi melalui pencarian. Beberapa data sementara dapat disimpan dalam struktur data yang dapat berubah, seperti array arang, dan dihapus segera setelah digunakan. Membersihkan struktur data telah mengurangi keefektifan sistem runtime Java karena objek dipindahkan dalam memori secara transparan ke pemrogram.

Pedoman ini juga memiliki implikasi untuk implementasi dan penggunaan perpustakaan tingkat rendah yang tidak memiliki pengetahuan semantik tentang data yang mereka hadapi. Sebagai contoh, parsing string string tingkat rendah dapat mencatat teks yang berfungsi. Aplikasi dapat menguraikan SSN dengan perpustakaan. Ini menciptakan situasi di mana SSN tersedia untuk administrator dengan akses ke file log.

Bruno
sumber
4
ini persis referensi cacat / palsu yang saya bicarakan di bawah jawaban Jon, ini adalah sumber yang terkenal dengan banyak kritik.
bestsss
37
@bestass bisakah Anda juga mengutip referensi?
user961954
10
@bestass maaf, tapi Stringini cukup dipahami dengan baik dan bagaimana berperilaku di JVM ... ada alasan yang baik untuk menggunakan char[]di tempat Stringketika berhadapan dengan password dengan cara yang aman.
SnakeDoc
3
sekali lagi meskipun kata sandi dilewatkan sebagai string dari browser ke permintaan sebagai 'string' bukan char? jadi, apa pun yang Anda lakukan itu adalah string, pada titik mana ia harus ditindaklanjuti dan dibuang, tidak pernah disimpan dalam memori?
Dawesi
3
@Dawesi - At which point- itu adalah spesifik aplikasi, tetapi aturan umum adalah melakukannya segera setelah Anda mendapatkan sesuatu yang seharusnya menjadi kata sandi (plaintext atau sebaliknya). Anda mendapatkannya dari browser sebagai bagian dari permintaan HTTP, misalnya. Anda tidak dapat mengontrol pengiriman, tetapi Anda dapat mengontrol penyimpanan Anda sendiri, jadi segera setelah Anda mendapatkannya, masukkan ke dalam char [], lakukan apa yang perlu Anda lakukan dengannya, kemudian atur semua ke '0' dan biarkan gc ambil kembali.
luis.espinal
354

Array karakter ( char[]) dapat dihapus setelah digunakan dengan mengatur setiap karakter ke nol dan Strings tidak. Jika seseorang entah bagaimana dapat melihat gambar memori, mereka dapat melihat kata sandi dalam teks biasa jika Strings digunakan, tetapi jika char[]digunakan, setelah membersihkan data dengan 0, kata sandi aman.

alephx
sumber
13
Secara default tidak aman. Jika kita berbicara tentang aplikasi web, sebagian besar wadah web akan mengirimkan kata sandi ke HttpServletRequestobjek dalam plaintext. Jika versi JVM 1.6 atau lebih rendah, itu akan berada di ruang permgen. Jika dalam 1,7, itu masih dapat dibaca sampai dikumpulkan. (Kapan pun itu.)
avgvstvs
4
@avgvstvs: string tidak secara otomatis dipindahkan ke ruang permgen, yang hanya berlaku untuk string intern'ed. Selain itu, ruang permgen juga tunduk pada pengumpulan sampah, hanya pada tingkat yang lebih rendah. Masalah sebenarnya dengan ruang permgen adalah ukurannya tetap, yang merupakan alasan mengapa tidak ada yang bisa memanggil intern()string sembarangan. Tetapi Anda benar bahwa Stringinstans ada di tempat pertama (sampai dikumpulkan) dan mengubahnya menjadi char[]array setelah itu tidak mengubahnya.
Holger
3
@ Holger lihat docs.oracle.com/javase/specs/jvms/se6/html/… "Jika tidak, instance baru dari String kelas dibuat berisi urutan karakter Unicode yang diberikan oleh struktur CONSTANT_String_info; instance kelas itu adalah hasil dari derivasi literal string. Akhirnya, metode intern instance String baru dipanggil. " Dalam 1.6, JVM akan memanggil intern untuk Anda ketika mendeteksi urutan identik.
avgvstvs
3
@ Holger, Anda benar saya menggabungkan kolam konstan dan kolam string, tetapi juga salah bahwa ruang permgen hanya diterapkan pada string diinternir. Sebelum 1,7, baik constant_pool dan string_pool berada di ruang permgen. Itu berarti satu-satunya kelas String yang dialokasikan ke heap adalah seperti yang Anda katakan, new String()atau StringBuilder.toString() saya mengelola aplikasi dengan banyak konstanta string, dan kami memiliki banyak permgen creep sebagai hasilnya. Sampai 1.7.
avgvstvs
5
@avgvstvs: well, konstanta string, seperti mandat JLS, selalu diinternir, karenanya pernyataan bahwa string yang diinternir berakhir di ruang permgen, diterapkan pada konstanta string secara implisit. Satu-satunya perbedaan adalah bahwa konstanta string diciptakan di ruang permgen di tempat pertama, sedangkan memanggil intern()string sewenang-wenang dapat menyebabkan alokasi string yang setara di ruang permgen. Yang terakhir bisa mendapatkan GC'ed, jika tidak ada string literal dari konten yang sama berbagi objek itu ...
Holger
220

Beberapa orang percaya bahwa Anda harus menimpa memori yang digunakan untuk menyimpan kata sandi setelah Anda tidak lagi membutuhkannya. Ini mengurangi waktu jendela seorang penyerang harus membaca kata sandi dari sistem Anda dan sepenuhnya mengabaikan fakta bahwa penyerang sudah membutuhkan akses yang cukup untuk membajak memori JVM untuk melakukan ini. Seorang penyerang dengan akses sebanyak itu dapat menangkap peristiwa utama Anda yang membuat ini sama sekali tidak berguna (AFAIK, jadi tolong perbaiki saya jika saya salah).

Memperbarui

Berkat komentar saya harus memperbarui jawaban saya. Rupanya ada dua kasus di mana ini dapat menambahkan (sangat) peningkatan keamanan kecil karena mengurangi waktu kata sandi bisa mendarat di hard drive. Masih saya pikir itu berlebihan untuk kebanyakan kasus penggunaan.

  • Sistem target Anda mungkin tidak dikonfigurasi dengan benar atau Anda harus menganggap itu dan Anda harus paranoid tentang dump inti (dapat valid jika sistem tidak dikelola oleh administrator).
  • Perangkat lunak Anda harus terlalu paranoid untuk mencegah kebocoran data dengan penyerang mendapatkan akses ke perangkat keras - menggunakan hal-hal seperti TrueCrypt (dihentikan), VeraCrypt , atau CipherShed .

Jika memungkinkan, menonaktifkan dump inti dan file swap akan menangani kedua masalah. Namun, mereka akan memerlukan hak administrator dan dapat mengurangi fungsionalitas (lebih sedikit memori untuk digunakan) dan menarik RAM dari sistem yang sedang berjalan masih akan menjadi masalah yang valid.

Josefx
sumber
33
Saya akan mengganti "sama sekali tidak berguna" dengan "hanya peningkatan keamanan kecil". Misalnya Anda bisa mendapatkan akses ke dump memori jika Anda kebetulan telah membaca akses ke direktori tmp, mesin yang tidak dikonfigurasi dengan benar, dan kerusakan pada aplikasi Anda. Dalam hal ini Anda tidak akan dapat menginstal keylogger, tetapi Anda bisa menganalisis dump inti.
Joachim Sauer
44
Menghapus data yang tidak terenkripsi dari memori segera setelah Anda selesai dengan itu dianggap praktik terbaik bukan karena sangat mudah (itu tidak); tetapi karena itu mengurangi tingkat paparan ancaman Anda. Serangan waktu nyata tidak dicegah dengan melakukan ini; tetapi karena ini berfungsi sebagai alat mitigasi kerusakan dengan secara signifikan mengurangi jumlah data yang terpapar dalam serangan retroaktif pada snapshot memori (misalnya salinan memori aplikasi Anda yang ditulis ke file swap, atau yang dibacakan dari memori ditarik dari server yang sedang berjalan dan pindah ke server lain sebelum kondisinya gagal).
Dan Is Fiddling By Firelight
9
Saya cenderung setuju dengan sikap respons ini. Saya berani mengusulkan sebagian besar pelanggaran keamanan akibat terjadi pada tingkat abstraksi yang jauh lebih tinggi daripada bit dalam memori. Tentu, mungkin ada skenario dalam sistem pertahanan yang sangat aman di mana ini mungkin menjadi perhatian yang cukup besar tetapi berpikir serius pada tingkat ini adalah terlalu banyak untuk 99% dari aplikasi di mana .NET atau Java sedang dimanfaatkan (karena terkait dengan pengumpulan sampah).
kingdango
10
Setelah penetrasi memori server Heartbleed, mengungkapkan kata sandi, saya akan mengganti string "hanya peningkatan keamanan kecil" dengan "sangat penting untuk tidak menggunakan String untuk kata sandi, tetapi sebagai gantinya gunakan karakter []."
Peter vdL
9
@PetervdL heartbleed hanya membolehkan pembacaan kumpulan buffer yang digunakan kembali secara spesifik (digunakan untuk data penting keamanan dan I / O jaringan tanpa membersihkan di antara - karena alasan kinerja), Anda tidak dapat menggabungkan ini dengan String Java karena mereka dengan desain tidak dapat digunakan kembali. . Anda juga tidak dapat membaca ke dalam memori acak menggunakan Java untuk mendapatkan isi String. Masalah bahasa dan desain yang mengarah ke heartbleed tidak mungkin dengan Java Strings.
josefx
86

Saya tidak berpikir ini adalah saran yang valid, tapi, setidaknya saya bisa menebak alasannya.

Saya pikir motivasi ingin memastikan bahwa Anda dapat menghapus semua jejak kata sandi di memori segera dan dengan pasti setelah digunakan. Dengan char[]Anda dapat menimpa setiap elemen array dengan kosong atau sesuatu pasti. Anda tidak dapat mengedit nilai internal dengan Stringcara itu.

Tapi itu saja bukan jawaban yang bagus; mengapa tidak memastikan referensi ke char[]atau Stringtidak melarikan diri? Maka tidak ada masalah keamanan. Tetapi masalahnya adalah bahwa Stringobjek dapat intern()diedit secara teori dan tetap hidup di dalam kolam konstan. Saya kira menggunakan char[]melarang kemungkinan ini.

Sean Owen
sumber
4
Saya tidak akan mengatakan bahwa masalahnya adalah bahwa referensi Anda akan atau tidak akan "melarikan diri". Hanya saja string akan tetap tidak dimodifikasi dalam memori untuk beberapa waktu tambahan, sementara char[]dapat dimodifikasi, dan kemudian itu tidak relevan apakah itu dikumpulkan atau tidak. Dan karena string magang perlu dilakukan secara eksplisit untuk non-literal, itu sama seperti mengatakan bahwa a char[]dapat dirujuk oleh bidang statis.
Groo
1
bukankah kata sandi dalam memori sebagai string dari form post?
Dawesi
66

Jawabannya sudah diberikan, tetapi saya ingin berbagi masalah yang saya temukan belakangan ini dengan perpustakaan standar Java. Sementara mereka sangat berhati-hati saat mengganti string kata sandi dengan di char[]mana - mana (yang tentu saja merupakan hal yang baik), data keamanan-kritis lainnya tampaknya diabaikan ketika datang untuk membersihkannya dari memori.

Saya sedang memikirkan misalnya kelas PrivateKey . Pertimbangkan skenario di mana Anda akan memuat kunci RSA pribadi dari file PKCS # 12, menggunakannya untuk melakukan beberapa operasi. Sekarang dalam kasus ini, mengendus kata sandi saja tidak akan banyak membantu Anda selama akses fisik ke file kunci dibatasi dengan benar. Sebagai seorang penyerang, Anda akan jauh lebih baik jika Anda mendapatkan kunci secara langsung daripada kata sandi. Informasi yang diinginkan dapat bocor berlipat ganda, core dumps, sesi debugger atau swap file hanyalah beberapa contoh.

Dan ternyata, tidak ada yang memungkinkan Anda menghapus informasi pribadi dari PrivateKeydari memori, karena tidak ada API yang memungkinkan Anda menghapus byte yang membentuk informasi yang sesuai.

Ini adalah situasi yang buruk, karena makalah ini menjelaskan bagaimana keadaan ini dapat berpotensi dieksploitasi.

Pustaka OpenSSL misalnya menimpa bagian memori kritis sebelum kunci pribadi dibebaskan. Karena Java dikumpulkan dari sampah, kita perlu metode eksplisit untuk menghapus dan membatalkan informasi pribadi untuk kunci Java, yang akan diterapkan segera setelah menggunakan kunci tersebut.

menatah
sumber
Salah satu cara mengatasi hal ini adalah dengan menggunakan implementasi PrivateKeyyang tidak benar-benar memuat konten privatnya ke dalam memori: misalnya melalui token perangkat keras PKCS # 11. Mungkin implementasi perangkat lunak PKCS # 11 dapat menangani pembersihan memori secara manual. Mungkin menggunakan sesuatu seperti toko NSS (yang berbagi sebagian besar implementasinya dengan PKCS11jenis toko di Jawa) lebih baik. The KeychainStore(OSX keystore) beban isi penuh dari kunci pribadi ke dalam nya PrivateKeykasus, tetapi seharusnya tidak perlu. (Tidak yakin apa yang dilakukan WINDOWS-MYKeyStore di Windows.)
Bruno
@Bruno Tentu, token berbasis perangkat keras tidak menderita karena hal ini, tetapi bagaimana dengan situasi di mana Anda lebih atau kurang dipaksa untuk menggunakan kunci perangkat lunak? Tidak setiap penyebaran memiliki anggaran untuk membeli HSM. Penyimpanan kunci perangkat lunak pada suatu saat harus memuat kunci ke dalam memori, jadi IMO kita setidaknya harus diberi pilihan untuk menghapus memori sesuka hati.
emboss
Tentu saja, saya hanya ingin tahu apakah beberapa implementasi perangkat lunak yang setara dengan HSM mungkin berperilaku lebih baik dalam hal membersihkan memori. Sebagai contoh, ketika menggunakan otentikasi klien dengan Safari / OSX, proses Safari tidak pernah benar-benar melihat kunci privat, pustaka SSL yang mendasarinya disediakan oleh OS berbicara langsung ke daemon keamanan yang meminta pengguna untuk menggunakan kunci dari gantungan kunci. Walaupun ini semua dilakukan dalam perangkat lunak, pemisahan serupa seperti ini dapat membantu jika penandatanganan didelegasikan ke entitas yang berbeda (bahkan berbasis perangkat lunak) yang akan menurunkan atau menghapus memori dengan lebih baik.
Bruno
@ Bruno: Ide yang menarik, lapisan tipuan tambahan yang menangani penghapusan memori memang akan menyelesaikan masalah ini secara transparan. Menulis pembungkus PKCS # 11 untuk kunci toko perangkat lunak sudah bisa melakukan triknya?
emboss
51

Seperti yang dinyatakan Jon Skeet, tidak ada jalan lain kecuali menggunakan refleksi.

Namun, jika refleksi adalah pilihan bagi Anda, Anda dapat melakukan ini.

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);
    }
}

saat dijalankan

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

Catatan: jika karakter String [] telah disalin sebagai bagian dari siklus GC, ada kemungkinan salinan sebelumnya ada di suatu tempat di memori.

Salinan lama ini tidak akan muncul di heap dump, tetapi jika Anda memiliki akses langsung ke memori mentah proses, Anda bisa melihatnya. Secara umum Anda harus menghindari siapa pun yang memiliki akses seperti itu.

Peter Lawrey
sumber
2
Lebih baik melakukan sesuatu untuk mencegah pencetakan kata sandi yang kita peroleh '***********'.
chux - Reinstate Monica
@ chux Anda dapat menggunakan karakter nol lebar, meskipun ini mungkin lebih membingungkan daripada berguna. Tidak mungkin mengubah panjang array char tanpa menggunakan Unsafe. ;)
Peter Lawrey
5
Karena Deduplication String Java 8, saya pikir melakukan sesuatu seperti itu bisa sangat merusak ... Anda mungkin akan menyelesaikan string lain dalam program Anda yang kebetulan memiliki nilai yang sama dengan kata sandi String. Tidak mungkin, tetapi mungkin ...
bersenang-senang
1
@PeterLawrey Itu harus diaktifkan dengan argumen JVM, tetapi itu ada di sana. Dapat membacanya di sini: blog.codecentric.de/en/2014/08/…
jamp
1
Ada kemungkinan besar bahwa kata sandi masih ada di Scannerbuffer internal dan, karena Anda tidak menggunakannya System.console().readPassword(), dalam bentuk yang dapat dibaca di jendela konsol. Tetapi untuk sebagian besar kasus penggunaan praktis, durasi usePasswordeksekusi adalah masalah yang sebenarnya. Misalnya, ketika membuat koneksi ke komputer lain, dibutuhkan waktu yang signifikan dan memberi tahu penyerang bahwa ini adalah waktu yang tepat untuk mencari kata sandi di heap. Satu-satunya solusi adalah mencegah penyerang membaca memori heap ...
Holger
42

Ini semua alasannya, kita harus memilih array char [] daripada String untuk kata sandi.

1. Karena String tidak dapat diubah di Jawa, jika Anda menyimpan kata sandi sebagai teks biasa, kata itu akan tersedia dalam memori sampai pengumpul Sampah mengosongkannya, dan karena String digunakan dalam kumpulan String untuk penggunaan kembali, ada kemungkinan yang cukup tinggi bahwa itu akan tetap tersimpan dalam memori untuk waktu yang lama, yang menimbulkan ancaman keamanan.

Karena siapa pun yang memiliki akses ke dump memori dapat menemukan kata sandi dalam teks yang jelas, itu alasan lain Anda harus selalu menggunakan kata sandi terenkripsi daripada teks biasa. Karena String tidak dapat diubah, tidak mungkin konten String dapat diubah karena perubahan apa pun akan menghasilkan String baru, sementara jika Anda menggunakan karakter [] Anda masih dapat mengatur semua elemen sebagai kosong atau nol. Jadi menyimpan kata sandi dalam susunan karakter jelas mengurangi risiko keamanan mencuri kata sandi.

2. Java sendiri merekomendasikan menggunakan metode getPassword () dari JPasswordField yang mengembalikan karakter [], alih-alih metode getText () yang sudah usang yang mengembalikan kata sandi dalam teks yang jelas yang menyatakan alasan keamanan. Adalah baik untuk mengikuti saran dari tim Java dan mematuhi standar daripada menentangnya.

3. Dengan String selalu ada risiko mencetak teks biasa dalam file log atau konsol tetapi jika Anda menggunakan Array Anda tidak akan mencetak konten array, tetapi sebaliknya lokasi memorinya akan dicetak. Meski bukan alasan sebenarnya, itu tetap masuk akal.

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

Dirujuk dari blog ini . Saya harap ini membantu.

Manusia
sumber
10
Ini berlebihan. Jawaban ini adalah versi tepat dari jawaban yang ditulis oleh @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 . Tolong jangan menyalin paste atau menggandakan jawaban yang sama dua kali.
Lucky
Apa perbedaan antara 1.) System.out.println ("Kata sandi karakter:" + charPassword); dan 2.) System.out.println (charPassword); Karena itu memberikan "tidak dikenal" sama dengan output.
Vaibhav_Sharma
3
@ Lucky Sayangnya jawaban lama yang Anda tautkan dijiplak dari blog yang sama dengan jawaban ini, dan sekarang telah dihapus. Lihat meta.stackoverflow.com/questions/389144/… . Jawaban ini hanya memotong dan menempel dari blog yang sama dan tidak menambahkan apa pun, jadi seharusnya hanya komentar yang menghubungkan ke sumber aslinya.
skomisa
41

Sunting: Kembali ke jawaban ini setelah satu tahun penelitian keamanan, saya menyadari itu membuat implikasi yang agak disayangkan bahwa Anda akan benar-benar membandingkan kata sandi plaintext. Tolong jangan. Gunakan hash satu arah yang aman dengan garam dan sejumlah iterasi yang masuk akal . Pertimbangkan untuk menggunakan perpustakaan: hal-hal ini sulit untuk diperbaiki!

Jawaban asli: Bagaimana dengan fakta bahwa String.equals () menggunakan evaluasi hubung singkat , dan karenanya rentan terhadap serangan waktu? Ini mungkin tidak mungkin, tetapi Anda secara teoritis dapat mengatur waktu perbandingan kata sandi untuk menentukan urutan karakter yang benar.

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;
}

Beberapa lebih banyak sumber daya tentang serangan waktu:

Teori grafik
sumber
Tapi itu bisa ada dalam perbandingan char [] juga, di suatu tempat kita akan melakukan hal yang sama dalam validasi kata sandi juga. jadi bagaimana char [] lebih baik dari string?
Mohit Kanwar
3
Anda benar sekali, kesalahan itu bisa terjadi. Mengetahui masalah adalah hal yang paling penting di sini, mengingat tidak ada metode perbandingan kata sandi yang eksplisit di Jawa untuk kata sandi berbasis string atau char []. Saya akan mengatakan godaan untuk menggunakan compare () untuk Strings adalah alasan yang baik untuk menggunakan char []. Dengan begitu Anda setidaknya memiliki kontrol atas bagaimana perbandingan dilakukan (tanpa memperpanjang String, yang merupakan imo yang menyakitkan).
Teori Grafik
Selain membandingkan password plaintext bukanlah hal yang benar pula, godaan untuk menggunakan Arrays.equalsuntuk char[]setinggi untuk String.equals. Jika ada yang peduli, ada kelas kunci khusus yang mengenkapsulasi kata sandi yang sebenarnya dan mengurus masalah - oh, tunggu, paket keamanan sebenarnya telah mendedikasikan kelas-kelas utama, T&J ini hanya tentang kebiasaan di luar mereka, katakanlah, misalnya JPasswordField, untuk menggunakan char[]alih-alih String(di mana algoritma sebenarnya digunakan byte[]pula).
Holger
Keamanan perangkat lunak yang relevan harus melakukan sesuatu seperti sleep(secureRandom.nextInt())sebelum menolak upaya login, itu tidak hanya menghilangkan kemungkinan serangan waktu, tetapi juga membuat upaya penangkal kekuatan brutal.
Holger
36

Tidak ada yang array char memberi Anda vs String kecuali Anda membersihkannya secara manual setelah digunakan, dan saya belum melihat ada yang benar-benar melakukan itu. Jadi bagi saya preferensi char [] vs String agak berlebihan.

Lihatlah pustaka Spring Security yang banyak digunakan di sini dan tanyakan pada diri Anda - apakah password Spring Security tidak kompeten atau char [] tidak masuk akal. Ketika beberapa peretas jahat mengambil memori dump RAM Anda pastikan ia akan mendapatkan semua kata sandi bahkan jika Anda menggunakan cara canggih untuk menyembunyikannya.

Namun, Java berubah setiap saat, dan beberapa fitur menakutkan seperti Deduplikasi String Java 8 mungkin menginternir objek String tanpa sepengetahuan Anda. Tapi itu percakapan yang berbeda.

Oleg Mikheev
sumber
Mengapa deduplikasi String menakutkan? Ini hanya berlaku ketika setidaknya ada dua string yang memiliki konten yang sama, jadi bahaya apa yang akan timbul dari membiarkan kedua string yang sudah identik ini berbagi array yang sama? Atau mari kita bertanya sebaliknya: jika tidak ada string-deduplication, keuntungan apa yang muncul dari fakta bahwa kedua string memiliki array yang berbeda (dari konten yang sama)? Dalam kedua kasus itu, akan ada serangkaian konten yang hidup setidaknya selama String hidup terpanjang dari konten yang hidup ...
Holger
@Holger apa pun yang berada di luar kendali Anda adalah risiko potensial ... misalnya jika dua pengguna memiliki kata sandi yang sama, fitur luar biasa ini akan menyimpan keduanya dalam satu karakter [] sehingga jelas bahwa keduanya sama, tidak yakin apakah itu risiko besar tapi masih
Oleg Mikheev
Jika Anda memiliki akses ke memori tumpukan dan kedua instance string, tidak masalah apakah string menunjuk ke array yang sama atau ke dua array dari konten yang sama, masing-masing mudah untuk mengetahuinya. Terutama, karena itu tidak relevan. Jika Anda saat ini, Anda mengambil kedua kata sandi, apakah identik atau tidak. Kesalahan sebenarnya terletak pada penggunaan kata sandi plaintext bukan hash asin.
Holger
@ Holger untuk memverifikasi kata sandi harus dalam teks yang jelas dalam memori untuk beberapa waktu, 10 ms, bahkan jika itu hanya untuk membuat hash asin dari itu. Kemudian, jika itu terjadi bahwa ada dua kata sandi yang identik disimpan dalam memori bahkan untuk 10 ms, deduplikasi dapat ikut bermain. Jika benar-benar magang string mereka disimpan dalam memori untuk waktu yang lebih lama. Sistem yang tidak restart selama berbulan-bulan akan mengumpulkan banyak hal ini. Hanya berteori.
Oleg Mikheev
Tampaknya, Anda memiliki kesalahpahaman mendasar tentang String Deduplication. Itu tidak "intern string", semua itu, membiarkan string dengan konten yang sama menunjuk ke array yang sama, yang sebenarnya mengurangi jumlah instance array yang berisi kata sandi plaintext, karena semua kecuali satu instance array dapat direklamasi dan ditimpa oleh benda lain segera. String ini masih dikumpulkan seperti string lainnya. Mungkin itu membantu, jika Anda memahami bahwa de-duplikasi sebenarnya dilakukan oleh pengumpul sampah, untuk string yang telah bertahan beberapa siklus GC saja.
Holger
30

String tidak berubah dan tidak dapat diubah setelah mereka dibuat. Membuat kata sandi sebagai string akan meninggalkan referensi menyimpang ke kata sandi pada heap atau pada kumpulan String. Sekarang jika seseorang mengambil tumpukan proses Java dan dengan hati-hati memindai dia mungkin bisa menebak kata sandi. Tentu saja string ini tidak digunakan akan menjadi sampah yang dikumpulkan tetapi itu tergantung pada kapan GC masuk.

Di sisi lain, char [] dapat berubah begitu otentikasi selesai, Anda dapat menimpa mereka dengan karakter seperti semua M atau backslash. Sekarang bahkan jika seseorang mengambil heap dump, dia mungkin tidak bisa mendapatkan kata sandi yang saat ini tidak digunakan. Ini memberi Anda lebih banyak kontrol dalam arti seperti membersihkan konten Objek sendiri vs menunggu GC melakukannya.

Kutu buku
sumber
Mereka hanya akan GC jika JVM yang dimaksud adalah> 1.6. Sebelum 1,7, semua string disimpan di permgen.
avgvstvs
@avgvstvs: "semua string disimpan dalam permgen" benar-benar salah. Hanya string internal yang disimpan di sana dan jika mereka tidak berasal dari string literal yang dirujuk oleh kode, mereka masih mengumpulkan sampah. Pikirkan saja itu. Jika string umumnya tidak pernah GCed di JVMs sebelum 1,7, bagaimana mungkin aplikasi Java bertahan lebih dari beberapa menit?
Holger
@ Holger Ini salah. Dibatasi stringsDAN Stringkumpulan (kumpulan string yang sebelumnya digunakan) KEDUA disimpan di Permgen sebelum 1,7. Juga, Lihat bagian 5.1: docs.oracle.com/javase/specs/jvms/se6/html/... JVM selalu memeriksa Stringsuntuk melihat apakah mereka memiliki nilai referensi yang sama, dan akan memanggil String.intern()UNTUK ANDA. Hasilnya adalah bahwa setiap kali JVM mendeteksi String yang identik di dalam constant_poolatau menumpuknya akan memindahkan mereka ke permgen. Dan saya mengerjakan beberapa aplikasi dengan "creeping permgen" hingga 1,7. Itu masalah nyata.
avgvstvs
Jadi untuk rekap: Sampai 1,7, String dimulai di heap, ketika mereka digunakan mereka dimasukkan ke dalam constant_poolyang terletak di permgen, dan kemudian jika string digunakan lebih dari sekali, itu akan diinternir.
avgvstvs
@avgvstvs: Tidak ada "kumpulan string yang sebelumnya digunakan". Anda melemparkan hal-hal yang sama sekali berbeda. Ada satu kumpulan string runtime yang berisi string literal dan string yang diinternir secara eksplisit, tetapi tidak ada yang lain. Dan setiap kelas memiliki kumpulan konstan yang berisi konstanta waktu kompilasi . String ini secara otomatis ditambahkan ke pool runtime, tetapi hanya ini , tidak setiap string.
Holger
21

String tidak dapat diubah dan pergi ke kumpulan string. Setelah ditulis, tidak dapat ditimpa.

char[] adalah array yang harus Anda timpa setelah menggunakan kata sandi dan inilah yang harus dilakukan:

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');
}

Satu skenario di mana penyerang bisa menggunakannya adalah crashdump - ketika JVM crash dan menghasilkan dump memori - Anda akan dapat melihat kata sandi.

Itu belum tentu penyerang eksternal jahat. Ini bisa menjadi pengguna dukungan yang memiliki akses ke server untuk keperluan pemantauan. Dia bisa mengintip crashdump dan menemukan kata sandinya.

ACV
sumber
2
ch = nol; Anda tidak dapat melakukan ini
Yugerten
Tetapi belum request.getPassword()membuat string dan menambahkannya ke kolam?
Tvde1
1
ch = '0'mengubah variabel lokal ch; itu tidak berpengaruh pada array. Dan contoh Anda tidak ada gunanya, Anda mulai dengan instance string yang Anda panggil toCharArray(), membuat array baru dan bahkan ketika Anda menimpa array baru dengan benar, itu tidak mengubah instance string, sehingga tidak memiliki keuntungan dibandingkan hanya menggunakan string contoh.
Holger
@ Holger terima kasih. Mengoreksi kode pembersihan array char.
ACV
3
Anda cukup menggunakanArrays.fill(pass, '0');
Holger
18

Jawaban singkat dan langsung adalah karena char[]bisa berubah sementara Stringobjek tidak.

Stringsdi Jawa adalah objek abadi. Itulah sebabnya mereka tidak dapat dimodifikasi setelah dibuat, dan oleh karena itu satu-satunya cara agar isinya dihapus dari memori adalah dengan mengumpulkannya. Itu hanya akan terjadi ketika memori yang dibebaskan oleh objek dapat ditimpa, dan data akan hilang.

Sekarang pengumpulan sampah di Jawa tidak terjadi pada interval yang dijamin. ItuString demikian dapat bertahan dalam memori untuk waktu yang lama, dan jika suatu proses crash selama waktu ini, isi string mungkin berakhir di tempat penyimpanan memori atau log.

Dengan susunan karakter , Anda dapat membaca kata sandi, menyelesaikannya sesegera mungkin, dan kemudian segera mengubah isinya.

Pritam Banerjee
sumber
@fallenidol Tidak sama sekali. Baca dengan cermat dan Anda akan menemukan perbedaannya.
Pritam Banerjee
12

String dalam java tidak dapat diubah. Jadi, setiap kali string dibuat, itu akan tetap dalam memori sampai sampah dikumpulkan. Jadi siapa pun yang memiliki akses ke memori dapat membaca nilai string.
Jika nilai string diubah maka akhirnya akan membuat string baru. Jadi nilai asli dan nilai yang dimodifikasi tetap berada di memori sampai sampah dikumpulkan.

Dengan array karakter, isi array dapat dimodifikasi atau dihapus setelah tujuan kata sandi disajikan. Isi asli array tidak akan ditemukan dalam memori setelah dimodifikasi dan bahkan sebelum pengumpulan sampah dimulai.

Karena masalah keamanan, lebih baik menyimpan kata sandi sebagai array karakter.

Saathvik
sumber
2

Dapat diperdebatkan apakah Anda harus menggunakan String atau menggunakan Char [] untuk tujuan ini karena keduanya memiliki kelebihan dan kekurangan. Itu tergantung pada apa yang dibutuhkan pengguna.

Karena String di Java tidak dapat diubah, setiap kali beberapa orang mencoba untuk memanipulasi string Anda, itu menciptakan Obyek baru dan String yang ada tetap tidak terpengaruh. Ini bisa dilihat sebagai keuntungan untuk menyimpan kata sandi sebagai String, tetapi objek tetap ada dalam memori bahkan setelah digunakan. Jadi, jika ada orang yang entah bagaimana mendapatkan lokasi memori objek, orang itu dapat dengan mudah melacak kata sandi Anda yang tersimpan di lokasi itu.

Karakter [] bisa berubah, tetapi ia memiliki keuntungan bahwa setelah penggunaannya, pemrogram dapat secara eksplisit membersihkan array atau mengabaikan nilai. Jadi ketika sudah selesai digunakan itu sudah dibersihkan dan tidak ada yang bisa tahu tentang informasi yang Anda simpan.

Berdasarkan keadaan di atas, orang bisa mendapatkan ide apakah akan pergi dengan String atau pergi dengan Char [] untuk persyaratan mereka.

Neeraj
sumber
0

Tali Kasus:

    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

Kasus 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
Aditya Rewari
sumber