Neden parolalar için karakter dizisi yerine char [] tercih edilir?


3422

Swing'de şifre alanında bir getPassword() (döner char[]yerine her zamanki) yöntemine getText()(döner String) yöntemiyle. Benzer şekilde, Stringşifreleri işlemek için kullanmama önerisine de rastladım .

StringParolalar söz konusu olduğunda neden güvenlik için bir tehdit oluşturuyor? Kullanmak zor geliyorchar[] .

Yanıtlar:


4290

Teller değişmez . Bu String, başka bir işlem belleği dökebiliyorsa, çöp toplanmadan önce verilerinden kurtulmanın ( yansıma dışında ) hiçbir yolu olmadığı anlamına gelir. içinde tekme.

Bir diziyle, işiniz bittikten sonra verileri açıkça silebilirsiniz. İstediğiniz herhangi bir şeyle dizinin üzerine yazabilirsiniz ve parola çöp toplama işleminden önce bile sistemin herhangi bir yerinde bulunmaz.

Yani evet, bu ise bir güvenlik endişesi - ancak bu bile kullanarak char[]sadece bir saldırganın fırsat penceresi azaltır ve sadece saldırı bu özel tip için.

Yorumlarda belirtildiği gibi, çöp toplayıcı tarafından taşınan dizilerin verilerin kopyalarını hafızada bırakması mümkündür. Ben bu gerçeklenime özel inanıyoruz - Çöp toplayıcı olabilir gidiyor, bütün hafızayı temizlemek bu tür bir şey önlemek için. Olsa bile char[], gerçek karakterleri bir saldırı penceresi olarak içeren zaman vardır .


3
Bir işlemin uygulamanızın belleğine erişimi varsa, bu zaten bir güvenlik ihlalidir, değil mi?
Yeti

5
@Yeti: Evet, ama siyah ve beyaz gibi değil. Yalnızca belleğin bir anlık görüntüsünü alabilirlerse, anlık görüntünün ne kadar hasar verebileceğini azaltmak veya gerçekten ciddi bir anlık görüntünün alınabileceği pencereyi azaltmak istersiniz.
Jon Skeet

11
Yaygın bir saldırı yöntemi, çok fazla bellek ayıran ve ardından parola gibi soldan, kullanışlı veriler için tarayan bir işlem çalıştırmaktır. İşlem, başka bir işlemin bellek alanına sihirli bir erişime ihtiyaç duymaz; yalnızca hassas verileri temizlemeden ölen diğer işlemlere dayanır ve işletim sistemi de yeni bir işlem için kullanılabilir hale getirmeden önce belleği (veya sayfa arabelleklerini) temizlemez. char[]Konumlarda saklanan parolaları silmek, bu saldırı hattını keser, kullanırken mümkün olmayan bir şey String.
Ted Hopp

İşletim sistemi başka bir işleme vermeden önce belleği temizlemezse, işletim sisteminin önemli güvenlik sorunları vardır! Bununla birlikte, teknik olarak temizleme genellikle korumalı mod hileleriyle yapılır ve CPU bozulursa (örn. Intel Meldown) eski bellek içeriğini okumak hala mümkündür.
Mikko Rantalainen

1222

Buradaki diğer öneriler geçerli görünse de, bir başka iyi neden daha var. Plain ile parolayı yanlışlıkla günlüklere , monitörlere veya başka bir güvensiz yere basmaString şansınız çok daha yüksektir . daha az savunmasızdır.char[]

Bunu düşün:

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

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

Baskılar:

String: Password
Array: [C@5829428e

40
@voo, ancak akış ve birleştirme için doğrudan yazma yoluyla oturum açacağınızdan şüpheliyim. günlük çerçevesi char [] 'i iyi çıktıya dönüştürür
bestsss

41
Thr4wn Standart uygulanması @ ait toStringolduğunu classname@hashcode. [Ctemsil eder char[], geri kalanı onaltılı karma kodudur.
Konrad Garus

15
İlginç fikir. Bunun diziler için önemli bir diziye sahip olan Scala'ya aktarılmadığını belirtmek isterim.
mauhiz

37
Bunun Passwordiçin bir sınıf türü yazardım . Daha az belirsiz ve yanlışlıkla bir yere geçmek daha zor.
rightfold

8
Neden birisi char dizisinin bir Object olarak yayınlanacağını varsayar? Herkesin bu cevabı neden sevdiğini bilmiyorum. Bunu yaptığınızı varsayalım: System.out.println ("Password" .toCharArray ());
GC_

680

Resmi bir belge alıntı için, Java Kriptografi Mimarlık kılavuzu hakkında şunları söyler char[]vs String(şifre tabanlı şifreleme hakkında, ancak bu elbette şifreler hakkında daha genel olarak) şifreleri:

Şifreyi tür bir nesnede toplamak ve saklamak mantıklı görünebilir java.lang.String . Ancak, burada bir uyarıdır: ObjectÇeşidi ler Stringbir içeriklerinden dışarı değişikliği (üzerine yazma) ya da sıfır sizi izin tanımlı hiçbir yöntem vardır, yani iletmenin String kullanıldıktan sonra. Bu özellik, Stringnesneleri kullanıcı parolaları gibi güvenliğe duyarlı bilgileri depolamak için uygun hale getirmez. Bunun yerine her zaman güvenliğe duyarlı bilgileri bir chardizide toplayıp saklamanız gerekir .

Sürüm 4.0, Java Programlama Dili için Güvenli Kodlama Yönergeleri, Sürüm 4.0'da da benzer bir şey söylüyor (başlangıçta günlük kaydı bağlamında olmasına rağmen):

Yönerge 2-2: Çok hassas bilgileri günlüğe kaydetme

Sosyal Güvenlik numaraları (SSN'ler) ve şifreler gibi bazı bilgiler son derece hassastır. Bu bilgiler, yöneticiler tarafından bile gerekenden daha uzun süre ve ne görülebileceği konusunda saklanmamalıdır. Örneğin, günlük dosyalarına gönderilmemeli ve varlığı aramalar aracılığıyla algılanmamalıdır. Bazı geçici veriler karakter dizileri gibi değişken veri yapılarında tutulabilir ve kullanımdan hemen sonra silinebilir. Veri yapılarının temizlenmesi, nesneler belleğe şeffaf bir şekilde programlayıcıya taşındığından tipik Java çalışma zamanı sistemlerinde etkinliği azaltmıştır.

Bu kılavuzda, uğraştıkları veriler hakkında anlam bilgisi bulunmayan alt düzey kitaplıkların uygulanması ve kullanımı üzerinde de etkileri vardır. Örnek olarak, düşük düzeyli bir dize ayrıştırma kitaplığı üzerinde çalıştığı metni günlüğe kaydedebilir. Bir uygulama, bir SSN'yi kitaplıkla ayrıştırabilir. Bu, SSN'lerin günlük dosyalarına erişimi olan yöneticiler tarafından kullanılabildiği bir durum oluşturur.


4
Bu tam olarak Jon'un cevabının altında bahsettiğim kusurlu / sahte referans, çok fazla eleştiri içeren iyi bilinen bir kaynak.
bestsss

37
@bestass ayrıca referans gösterebilir misiniz?
user961954

10
@bestass üzgünüm, ama Stringoldukça iyi anlaşılmaktadır ve JVM nasıl davranacağını ... kullanmak için iyi nedenler vardır char[]yerine Stringgüvenli bir şekilde şifreleri ile uğraşırken.
SnakeDoc

3
bir kez daha şifre tarayıcıdan bir dize olarak char 'değil' string 'olarak geçirilmesine rağmen? yani ne yaparsanız yapın bu bir dize, hangi noktada harekete geçmeli ve atılmalı, asla bellekte saklanmamalıdır?
Dawesi

3
@Dawesi - At which point- bu uygulamaya özgüdür, ancak genel kural, bir parola (düz metin veya başka türlü) olması gereken bir şeyi alır almaz bunu yapmaktır. Örneğin, bir HTTP isteğinin parçası olarak tarayıcıdan alırsınız. Teslimatı kontrol edemezsiniz, ancak kendi depolama alanınızı kontrol edebilirsiniz, bu yüzden alır almaz bir char [] içine koyun, ne yapmanız gerektiğini yapın, sonra hepsini '0' olarak ayarlayın ve gc'ye izin verin geri alın.
luis.espinal

354

Karakter dizileri ( char[]) kullanımdan sonra her karakter sıfıra ayarlanarak silinebilir ve Dizeler değil. Birisi bellek görüntüsünü bir şekilde görebiliyorsa, Dizeler kullanılıyorsa düz metin olarak bir parola görebilir, ancak char[]kullanılırsa, 0'lı veriler temizlendikten sonra parola güvenlidir.


13
Varsayılan olarak güvenli değildir. Bir web uygulaması hakkında konuşuyorsak, çoğu web kapsayıcıları şifreyi HttpServletRequestdüz metin olarak nesneye geçirir. JVM sürümü 1.6 veya daha düşükse, permgen uzayında olacaktır. 1.7 sürümündeyse toplanıncaya kadar okunabilir olmaya devam eder. (Ne zaman olursa.)
avgvstvs

4
@avgvstvs: dizeler otomatik olarak permgen boşluğuna taşınmaz, bu sadece stajyer dizeler için geçerlidir. Bunun yanı sıra, permen alanı da daha düşük bir oranda çöp toplamaya tabidir. Kalıcı boşlukla ilgili asıl sorun, sabit boyutudur, bu da hiç kimsenin dikkatsizce intern()keyfi dizeleri çağırmamasının sebebidir . Ancak, Stringörneklerin ilk etapta (toplanana kadar) var olması ve bunları char[]daha sonra dizilere dönüştürmesi haklı değildir.
Holger

3
@Holger bkz. Docs.oracle.com/javase/specs/jvms/se6/html/… "Aksi takdirde, CONSTANT_String_info yapısı tarafından verilen Unicode karakter dizisini içeren yeni bir String dizisi örneği oluşturulur; bu sınıf örneği, string literal derivation. Son olarak, yeni String örneğinin intern yöntemi çağrılır. " 1.6'da, JVM aynı dizileri tespit ettiğinde sizin için stajyer çağırır.
avgvstvs

3
@Holger, haklısın Sabit havuz ve dize havuzunu kapattım, ancak permenüs alanının sadece interned dizelere uygulandığı da yanlıştır . 1.7'den önce, hem constant_pool hem de string_pool permgen uzayında oturuyordu. Bu, öbek için ayrılan Dizelerin tek sınıfının sizin söylediğiniz gibi olduğu new String()veya StringBuilder.toString() çok sayıda dize sabitine sahip uygulamaları yönettiğim ve sonuç olarak çok sayıda permen sürüntüsü olduğu anlamına gelir. 1.7 ye kadar.
avgvstvs

5
@avgvstvs: JLS'nin zorunlu kıldığı gibi, dize sabitleri daima interned olduğundan, interned dizelerin permgen uzayında sona erdiği, dize sabitlerine dolaylı olarak uygulanan ifadedir. Tek fark, dize sabitlerinin ilk olarak permgen uzayında yaratılması, oysa intern()keyfi bir stringin çağrılması permgen uzayında eşdeğer bir dizenin tahsisine neden olabilir. İkincisi GC'ed alabilir, eğer aynı nesneyi paylaşan aynı içerik dizesi yoksa…
Holger

220

Bazı insanlar, parolayı artık gerekmediğinde saklamak için kullanılan belleğin üzerine yazmanız gerektiğine inanır. Bu, bir saldırganın sisteminizden parolayı okuması gereken zaman penceresini azaltır ve saldırganın bunu yapmak için JVM belleğini ele geçirmek için zaten yeterli erişime ihtiyacı olduğu gerçeğini tamamen yok sayar. Bu kadar erişime sahip bir saldırgan, önemli etkinliklerinizi yakalayabilir ve bu tamamen işe yaramaz hale gelir (AFAIK, bu yüzden lütfen yanılıyorsam beni düzeltin).

Güncelleme

Yorumlar sayesinde cevabımı güncellemeliyim. Görünüşe göre, bir parolanın sabit diske inme süresini azalttığı için bunun (çok) küçük bir güvenlik iyileştirmesi ekleyebileceği iki durum vardır. Yine de çoğu kullanım vakası için aşırı dolu olduğunu düşünüyorum.

  • Hedef sisteminiz kötü yapılandırılmış olabilir veya sisteminizin var olduğunu ve çekirdek dökümleri konusunda paranoyak olmanız gerektiğini (sistemlerin bir yönetici tarafından yönetilmemesi durumunda geçerli olabileceğini) varsaymanız gerekir.
  • Saldırganın donanıma erişmesini ( TrueCrypt (üretilmiyor), VeraCrypt veya CipherShed gibi ) kullanarak veri sızıntılarını önlemek için yazılımınızın aşırı paranoyak olması gerekir .

Mümkünse, çekirdek dökümlerini ve takas dosyasını devre dışı bırakmak her iki sorunu da halleder. Bununla birlikte, yönetici hakları gerektirirler ve işlevselliği azaltabilir (kullanılacak daha az bellek) ve çalışan bir sistemden RAM almak hala geçerli bir endişe kaynağı olacaktır.


33
"Tamamen yararsız" ı "sadece küçük bir güvenlik iyileştirmesi" ile değiştirirdim. Örneğin, bir tmp dizinine, kötü yapılandırılmış bir makineye ve uygulamanızdaki bir çökmeye okuma erişiminiz varsa bir bellek dökümü erişimine sahip olabilirsiniz. Bu durumda bir keylogger yüklemek mümkün olmaz, ancak olabilir çekirdek dökümü analiz edin.
Joachim Sauer

44
İşiniz bittiğinde şifrelenmemiş verileri bellekten silmek, kusursuz olduğu için değil, en iyi uygulama olarak kabul edilir; ancak tehdit maruziyet seviyenizi azalttığı için. Bunu yaparak gerçek zamanlı saldırılar engellenmez; ancak bir bellek anlık görüntüsüne geriye dönük saldırı sırasında maruz kalan veri miktarını önemli ölçüde azaltarak bir hasar azaltma aracı sunduğu için (örneğin, takas dosyasına yazılan ya da takılan bellekten okunan uygulama belleğinizin bir kopyası) çalışan bir sunucudan ve durumu başarısız olmadan önce farklı bir sunucuya taşındı).
Dan Is Fiddling By Firelight

9
Bu yanıtın tutumuna katılıyorum. Sonuçta güvenlik ihlallerinin çoğunun bellekteki bitlerden çok daha yüksek bir soyutlama düzeyinde ortaya çıkmasını öneriyorum. Elbette, bunun büyük bir endişe kaynağı olabileceği, ancak bu seviyede ciddi bir şekilde düşünmenin, .NET veya Java'nın (çöp toplama ile ilgili olarak) kullanıldığı uygulamaların% 99'u için aşırı derecede ağır olduğunu düşündüğü muhtemelen senaryolar vardır.
kingdango

10
Sunucu belleğinin Heartbleed penetrasyonundan sonra, şifreleri açığa çıkardıktan sonra, "sadece küçük bir güvenlik iyileştirmesi" dizesini parolalar için String kullanmamak için kesinlikle gerekli, ancak bunun yerine bir char [] kullanacağım.
Peter vdL

9
@PetervdL heartbleed yalnızca belirli bir yeniden kullanılan arabellek koleksiyonunun okunmasına izin verdi (hem güvenlik açısından kritik veriler hem de ağ G / Ç için - performans nedenlerinden dolayı temizlenmeden kullanılır), tasarım gereği yeniden kullanılamadığından bunu bir Java Dizesi ile birleştiremezsiniz . Ayrıca bir String içeriğini almak için Java kullanarak rasgele belleğe okuyamazsınız. Kalp atışlarına yol açan dil ve tasarım sorunları Java Dizeleri ile mümkün değildir.
josefx

86

Bunun geçerli bir öneri olduğunu düşünmüyorum, ama en azından sebebini tahmin edebiliyorum.

Bence motivasyon şifre kullanıldıktan sonra derhal ve kesin olarak parola tüm izini silmek emin olmak istiyor. Bir ile char[]dizinin her elemanının üzerine bir boşluk veya kesin bir şey yazabilirsiniz. Bu şekilde dahili değerini düzenleyemezsiniz String.

Ancak bu tek başına iyi bir cevap değildir; neden sadece bir referansın kaçmamasını char[]veya Stringkaçmamasını sağlamak değil? Güvenlik sorunu yok. Ama mesele şu ki, Stringnesneler intern()teoride edinebilir ve sabit havuzun içinde hayatta kalabilirler . Sanırım char[]bu olasılığı yasaklıyor.


4
Sorun, referanslarınızın "kaçacağı" ya da olmayacağı olduğunu söyleyemem. Sadece dizeler ek bir süre için bellekte değiştirilmeden kalır, ancak char[]değiştirilebilir, ve sonra toplanıp toplanmaması önemlidir. Ve dizgi stajının değişmez olmayanlar için açıkça yapılması gerektiğinden, a'nın char[]statik bir alan tarafından referans alınabileceğini söylemek gibi .
Groo

1
parola bellekteki form gönderisinden bir dize olarak değil mi?
Dawesi

66

Yanıt zaten verildi, ancak son zamanlarda keşfettiğim bir sorunu Java standart kitaplıklarıyla paylaşmak istiyorum. Artık şifre dizelerini değiştirmekle çok ilgilenirken,char[] her yerle (elbette iyi bir şeydir) , güvenlik açısından kritik olan diğer veriler, bellekten temizlenmesi söz konusu olduğunda gözden kaçıyor gibi görünüyor.

Örneğin PrivateKey sınıfını . Bazı işlemleri gerçekleştirmek için PKCS # 12 dosyasından özel RSA anahtarını yükleyeceğiniz bir senaryo düşünün. Şimdi bu durumda, yalnızca şifrenin koklanması, anahtar dosyaya fiziksel erişim düzgün bir şekilde kısıtlandığı sürece size yardımcı olmaz. Saldırgan olarak, anahtarı doğrudan şifre yerine ederseniz çok daha iyi durumda olursunuz. İstenilen bilgiler sızdırılabilir manifold, çekirdek dökümleri, bir hata ayıklayıcı oturumu veya takas dosyaları sadece bazı örneklerdir.

Ve ortaya çıktığı gibi PrivateKey, ilgili bilgileri oluşturan baytları silmenizi sağlayan bir API olmadığından, a'nın özel bilgilerini bellekten silmenize izin veren hiçbir şey yoktur.

Bu makalede , bu durumun potansiyel olarak nasıl kullanılabileceği açıklandığı için bu kötü bir durumdur .

Örneğin OpenSSL kütüphanesi, özel anahtarlar serbest bırakılmadan önce kritik bellek bölümlerinin üzerine yazar. Java çöp toplandığından, Java anahtarları için özel bilgileri silmek ve geçersiz kılmak için açık yöntemler kullanmamız gerekir; bunlar, anahtarı kullandıktan hemen sonra uygulanacaktır.


Bunun bir yolu PrivateKey, özel içeriğini belleğe yüklemeyen bir uygulamayı kullanmaktır : örneğin bir PKCS # 11 donanım jetonu aracılığıyla. Belki de PKCS # 11'in bir yazılım uygulaması, belleği manuel olarak temizlemeye özen gösterebilir. Belki de (uygulamalarının çoğunu PKCS11Java'daki mağaza türüyle paylaşan) NSS mağazası gibi bir şey kullanmak daha iyidir. KeychainStore(OSX deposu) yükleri onun içine özel anahtarı tam içeriğini PrivateKeyörnekleri, ancak gerek olmamalıdır. ( WINDOWS-MYKeyStore'un Windows'ta ne yaptığından emin değilim .)
Bruno

@Bruno Elbette, donanım tabanlı belirteçler bundan muzdarip değildir, ancak bir yazılım anahtarını kullanmaya az çok zorlandığınız durumlar ne olacak? Her dağıtımın bir HSM'yi karşılama bütçesi yoktur. Yazılım anahtar depoları bir noktada anahtarı belleğe yüklemelidir, bu nedenle IMO'ya en azından belleği tekrar silme seçeneği verilmelidir.
kabartma

Kesinlikle, sadece bir HSM'ye eşdeğer bazı yazılım uygulamalarının belleği temizleme açısından daha iyi davranıp davranamayacağını merak ediyordum. Örneğin, Safari / OSX ile istemci kimlik doğrulaması kullanıldığında, Safari işlemi hiçbir zaman özel anahtarı görmez, işletim sistemi tarafından sağlanan temel SSL kütüphanesi doğrudan kullanıcıdan anahtarlıktaki anahtarı kullanmasını isteyen güvenlik arka plan programı ile konuşur. Tüm bunlar yazılımda yapılırken, imzalama belleği daha iyi boşaltacak veya temizleyecek farklı bir varlığa (hatta yazılım tabanlı) devredilirse buna benzer bir ayırma yardımcı olabilir.
Bruno

@Bruno: İlginç bir fikir, hafızayı temizlemeye özen gösteren ek bir dolaylama katmanı bunu gerçekten şeffaf bir şekilde çözecektir. Yazılım anahtar deposu için bir PKCS # 11 sarıcısı yazmak zaten hile yapabilir mi?
kabartma

51

Jon Skeet'in belirttiği gibi, yansıma kullanmaktan başka bir yolu yoktur.

Ancak, yansıma sizin için bir seçenekse bunu yapabilirsiniz.

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

koştuğunda

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

Not: Dizenin char [] öğesi bir GC döngüsünün parçası olarak kopyalandıysa, önceki kopyanın bellekte bir yerde olma olasılığı vardır.

Bu eski kopya bir yığın dökümünde görünmez, ancak sürecin ham belleğine doğrudan erişiminiz varsa onu görebilirsiniz. Genel olarak böyle bir erişime sahip olanlardan kaçınmalısınız.


2
Parola uzunluğunu aldığımız yazdırmayı engelleyecek bir şey yapmak daha iyidir '***********'.
chux - Monica'yı geri yükle

@chux, sıfır genişlikli bir karakter kullanabilirsiniz, ancak bu yararlı olmaktan daha kafa karıştırıcı olabilir. Güvenli olmayan bir karakter kullanmadan char dizisinin uzunluğunu değiştirmek mümkün değildir. ;)
Peter Lawrey

5
Java 8'in Dize Tekilleştirme nedeniyle, böyle bir şey yapmak oldukça yıkıcı olabilir düşünüyorum ... şans eseri aynı parola String değerine sahip programınızdaki diğer dizeleri temizleyebilirsiniz. Olası değil, ama mümkün ...
jamp

1
@PeterLawrey Bir JVM bağımsız değişkeni ile etkinleştirilmelidir, ancak oradadır. Burada bu konuda okuyabilir: blog.codecentric.de/en/2014/08/...
Jamp

1
Parolanın hala Scannerdahili arabelleğinde ve kullanmadığınızdan beri System.console().readPassword()konsol penceresinde okunabilir biçimde olma olasılığı yüksektir . Ancak çoğu pratik kullanım durumunda, uygulama süresi usePasswordasıl problemdir. Örneğin, başka bir makineyle bağlantı kurarken, önemli bir zaman alır ve bir saldırgana yığınta parola aramanın doğru zaman olduğunu söyler. Tek çözüm, saldırganların yığın hafızasını okumasını önlemektir ...
Holger

42

Tüm nedenler, bir şifre için String yerine bir char [] dizisi seçmelisiniz .

1. Dizeler Java ile değiştirilemediğinden, parolayı düz metin olarak saklarsanız, Çöp toplayıcı temizlenene kadar bellekte kullanılabilir olur ve String, String havuzunda yeniden kullanılabilirlik için kullanıldığından, bir güvenlik tehdidi oluşturan uzun süre hafızada kalır.

Bellek dökümüne erişimi olan herkes parolayı düz metin olarak bulabildiğinden, bu nedenle düz metin yerine her zaman şifreli bir parola kullanmanız gerekir. Dizeler değişmez olduğu için, Dizelerin içeriğinin değiştirilmesinin bir yolu yoktur, çünkü herhangi bir değişiklik yeni bir Dize üretirken, bir char [] kullanırsanız tüm öğeleri yine de boş veya sıfır olarak ayarlayabilirsiniz. Dolayısıyla, bir şifreyi bir karakter dizisinde saklamak, bir şifreyi çalmanın güvenlik riskini açıkça azaltır.

2. Java, güvenlik nedenlerini açık bir şekilde parolaları döndüren kullanımdan kaldırılmış getText () yöntemi yerine, char [] döndüren JPasswordField öğesinin getPassword () yöntemini kullanmanızı önerir. Java ekibinin tavsiyelerine uymak ve onlara karşı olmak yerine standartlara uymak iyidir.

3. String ile her zaman bir günlük dosyasına veya konsola düz metin yazdırma riski vardır, ancak Array kullanırsanız bir dizinin içeriğini yazdırmazsınız, bunun yerine bellek konumu yazdırılır. Gerçek bir neden olmasa da, yine de mantıklı.

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

Bu blogdan referans alındı . Umarım bu yardımcı olur.


10
Bu gereksizdir. Bu cevap @SrujanKumarGulla stackoverflow.com/a/14060804/1793718 tarafından yazılan yanıtın tam bir sürümüdür . Lütfen aynı cevabı iki kez kopyalamayın veya kopyalamayın.
Şanslı

1. arasındaki fark nedir?) System.out.println ("Karakter şifresi:" + charPassword); ve 2.) System.out.println (charPassword); Çünkü çıktıyla aynı "bilinmeyen" i veriyor.
Vaibhav_Sharma

3
@Lucky Maalesef bağlandığınız eski yanıt bu yanıtla aynı blogdan intihal edildi ve şimdi silindi. Bkz. Meta.stackoverflow.com/questions/389144/… . Bu cevap aynı blogdan kesilip yapıştırıldı ve hiçbir şey eklemedi, bu yüzden orijinal kaynağa bağlantı veren bir yorum olmalıydı.
skomisa

41

Edit: Güvenlik araştırması bir yıl sonra bu cevabı geri geliyor, ben aslında hiç düz metin şifreleri karşılaştırmak oldukça talihsiz ima yapar farkındayız. Lütfen yapma. Tuz ve makul sayıda yineleme ile güvenli tek yönlü bir karma kullanın . Bir kütüphane kullanmayı düşünün: bu işi doğru yapmak zordur!

Orijinal yanıt: String.equals () yönteminin kısa devre değerlendirmesi kullandığı ve bu nedenle bir zamanlama saldırısına karşı savunmasız olduğu gerçeğine ne dersiniz ? Bu olası olmayabilir, ancak doğru karakter sırasını belirlemek için teorik olarak şifre karşılaştırmasını zamanlayabilirsiniz.

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

Zamanlama saldırıları hakkında daha fazla kaynak:


Ama bu char [] karşılaştırmasında da olabilir, bir yerlerde parola doğrulamasında da aynısını yapacağız. peki [] karakter dizeden nasıl daha iyi?
Mohit Kanwar

3
Kesinlikle haklısın, hata her iki şekilde de yapılabilir. Java'da Dize tabanlı veya char [] tabanlı parolalar için açık bir parola karşılaştırma yöntemi olmadığı düşünüldüğünde, problemi bilmek burada en önemli şeydir. Dizeler için Compare () kullanmak için günaha char [] ile gitmek için iyi bir neden olduğunu söyleyebilirim. Bu şekilde en azından karşılaştırmanın nasıl yapıldığını kontrol edebilirsiniz (bir ağrı imo olan String'i genişletmeden).
Grafik Teorisi

Düz metin parolaları karşılaştırmanın yanı sıra doğru bir şey değil, kullanmak Arrays.equalsiçin cazip char[]olduğu kadar yüksek String.equals. Eğer kimse umursamaz, özel bir anahtar sınıfını gerçek şifreyi enkapsüle ve bakımı konularında-oh gerçek güvenlik paketleri, beklemek yoktu sahip adanmış anahtar sınıfları, bu soru-cevap sadece hakkındadır alışkanlık örneğin bunlardan dışarıdan, diyelim ki, JPasswordFieldkullanımı, char[]yerine String(gerçek algoritmalar byte[]yine de kullanılır).
Holger

Güvenlikle ilgili yazılım sleep(secureRandom.nextInt())bir oturum açma girişimini reddetmeden önce böyle bir şey yapmalıdır , bu sadece zamanlama saldırıları olasılığını ortadan kaldırmakla kalmaz, aynı zamanda karşı saldırıları kaba kuvvet girişimleri yapar.
Holger

36

Char dizi kullanımdan sonra elle temizlemek sürece size String vs verir hiçbir şey yoktur ve ben aslında bunu yapan kimseyi görmedim. Yani bana göre char [] vs String tercihi biraz abartılı.

Bir göz atın yaygın olarak kullanılan Bahar Güvenlik kütüphanesinde burada ve kendinize sorun - Bahar Güvenlik adamlar beceriksiz ya şar [] şifreleri sadece pek mantıklı değil. Bazı kötü hackerlar RAM'inizin bellek dökümlerini kaptığında, gizlemek için sofistike yollar kullansanız bile tüm şifreleri alacağından emin olun.

Bununla birlikte, Java her zaman değişir ve Java 8'in Dize Tekilleştirme özelliği gibi bazı korkunç özellikler sizin haberiniz olmadan String nesnelerini staj yapabilir. Ama bu farklı bir konuşma.


Dize tekilleştirme neden korkutucu? Yalnızca aynı içeriğe sahip en az iki dize olduğunda geçerlidir, bu yüzden zaten bu iki özdeş dizenin aynı diziyi paylaşmasına izin vermekten ne gibi tehlikeler doğabilir? Ya da diğer taraftan soralım: dize tekilleştirmesi yoksa, her iki dizgenin de (aynı içeriğe sahip) farklı bir dizisi olması gerçeğinden ne gibi bir avantaj ortaya çıkar? Her iki durumda da, en az yaşayan en uzun String canlı olduğu sürece içeriklerin bir dizi canlı olacaktır…
Holger

@Kontrolünüz dışındaki herhangi bir şey potansiyel bir risktir ... örneğin, iki kullanıcı aynı şifreye sahipse bu harika özellik her ikisini de tek bir karakterde saklar [] aynı olduklarını açıkça gösterir; büyük bir risk ama yine de
Oleg Mikheev

Yığın belleğine ve her iki dize örneğine erişiminiz varsa, dizelerin aynı diziyi mi yoksa aynı içeriğin iki dizisini gösterip göstermediğinin önemi yoktur, her birinin öğrenilmesi kolaydır. Özellikle, zaten alakasız olduğu için. Bu noktadaysanız, aynı olsun ya da olmasın her iki şifreyi de alırsınız. Asıl hata, tuzlanmış karmalar yerine düz metin parolaların kullanılmasıdır.
Holger

@Holger şifresini doğrulamak için, sadece tuzlanmış bir hash oluşturmak olsa bile, bir süre 10ms, hafızada açık metin olması gerekir. Daha sonra, 10 ms için bile bellekte tutulan iki özdeş şifre varsa, veri tekilleştirme işlemi devreye girebilir. Dizeleri gerçekten stajyer ise çok daha uzun süre hafızada tutulur. Aylarca yeniden başlatılmayan sistem bunlardan bir sürü toplar. Sadece teorileşiyor.
Oleg Mikheev

Dize Tekilleştirme hakkında temel bir yanlış anlama var gibi görünüyor. “Staj dizeleri” yapmaz, tek yaptığı, aynı içeriğe sahip dizelerin aynı diziyi göstermesine izin vermektir, bu da düz metin parolasını içeren dizi örneği sayısını azaltır , çünkü bir dizi örneği geri alınabilir ve üzerine yazılabilir derhal diğer nesneler tarafından. Bu dizeler hala diğer dizeler gibi toplanmaktadır. Çoğaltma işleminin aslında çöp toplayıcı tarafından gerçekleştirildiğini anlarsanız, yalnızca birden fazla GC döngüsünden kurtulan dizeler için yardımcı olabilir.
Holger

30

Dizeler değişmezdir ve oluşturulduktan sonra değiştirilemezler. Dize olarak bir parola oluşturmak, öbek veya Dize havuzundaki parola için başıboş başvurular bırakır. Birisi Java sürecinin yığın dökümü alır ve dikkatlice tararsa, şifreleri tahmin edebilir. Tabii ki bu kullanılmayan dizeler çöp toplanacak, ancak GC ne zaman devreye girdiğine bağlı.

Diğer tarafta char [], kimlik doğrulaması yapılır yapılmaz değiştirilebilir ve tüm M veya ters eğik çizgiler gibi herhangi bir karakterle bunların üzerine yazabilirsiniz. Birisi bir yığın dökümü alsa bile, şu anda kullanılmayan şifreleri alamayabilir. Bu, Object içeriğini kendiniz temizlemek veya GC'nin bunu yapmasını beklemek gibi anlamda daha fazla kontrol sağlar.


Yalnızca söz konusu JVM> 1,6 ise GC'd olur. 1.7'den önce, tüm ipler permende saklandı.
avgvstvs

@avgvstvs: “tüm dizeler permende saklandı” sadece yanlış. Sadece stajyer dizeler orada saklanmıştı ve kod tarafından atıfta bulunulan dize değişmezlerinden gelmediyse, yine de çöp toplanmışlardı. Bunun hakkında düşün. Dizeler 1.7'den önceki JVM'lerde genellikle hiç GC oluşturulmamışsa, herhangi bir Java uygulaması birkaç dakikadan fazla nasıl hayatta kalabilir?
Holger

@Holger Bu yanlış. Stajyer stringsVE Stringhavuzu (daha önce kullanılmış olan dizelerin havuzu) 1.7'den önce Permgen'de depolanmıştır. Ayrıca, bkz. Bölüm 5.1: docs.oracle.com/javase/specs/jvms/se6/html/… JVM her zaman Stringsaynı referans değerine sahip olup olmadıklarını kontrol etti ve String.intern()SİZİN için arayacağım . Sonuç olarak, JVM her constant_pooldizinde veya öbekte özdeş Dizeler algıladığında onları kalıcı olarak hareket ettirecektir. Ve 1.7'ye kadar "sürünen permen" ile çeşitli uygulamalar üzerinde çalıştım. Bu gerçek bir sorundu.
avgvstvs

constant_poolÖzetlemek gerekirse : 1.7'ye kadar, Dizeler öbekte başladılar, kullanıldıklarında IN permgen'in içine yerleştirildiler ve daha sonra bir dize birden fazla kullanılmışsa, stajyer olacaktı.
avgvstvs

@avgvstvs: “Önceden kullanılan dizeler havuzu” yoktur. Tamamen farklı şeyler bir araya getiriyorsunuz. Dize değişmezleri ve açıkça interned dize içeren bir çalışma zamanı dize havuzu vardır, ancak başka değil. Ve her sınıfın derleme zamanı sabitleri içeren sabit havuzu vardır . Bu dizeler otomatik olarak çalışma zamanı havuzuna eklenir, ancak her dizeye değil, yalnızca bunlara eklenir .
Holger

21

String değişmezdir ve string havuzuna gider. Yazıldıktan sonra üzerine yazılamaz.

char[] parolayı kullandıktan sonra üzerine yazmanız gereken bir dizidir ve şu şekilde yapılması gerekir:

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

Saldırganın kullanabileceği bir senaryo - JVM çöküp bir bellek dökümü oluşturduğunda - bir çökme dökümüdür - şifreyi görebilirsiniz.

Bu mutlaka kötü amaçlı bir harici saldırgan değildir. Bu, izleme amacıyla sunucuya erişimi olan bir destek kullanıcısı olabilir. Bir çöküşe bakabilir ve şifreleri bulabilir.


2
ch = null; yapamazsın
Yugerten

Ancak request.getPassword()zaten dizeyi oluşturup havuza eklemiyor mu?
Tvde1

1
ch = '0'yerel değişkeni değiştirir ch; dizi üzerinde hiçbir etkisi yoktur. Ve örneğiniz yine de anlamsız, aradığınız bir dize örneğiyle başlıyorsunuz toCharArray(), yeni bir dizi oluşturuyorsunuz ve yeni dizinin üzerine doğru yazsanız bile, dize örneğini değiştirmiyor, bu nedenle sadece dize kullanmaya göre hiçbir avantajı yok örneği.
Holger

@Holger teşekkürler. Karakter dizisi temizleme kodu düzeltildi.
ACV

3
Sadece kullanabilirsinizArrays.fill(pass, '0');
Holger

18

Kısa ve anlaşılır cevap, nesneler olmasa char[]da değişken olabileceğidir String.

StringsJava'da değişmez nesnelerdir. Bu yüzden bir kez oluşturulduklarında değiştirilemezler ve bu nedenle içeriklerinin bellekten kaldırılmasının tek yolu çöplerin toplanmasıdır. Ancak o zaman nesne tarafından serbest bırakılan belleğin üzerine yazılabilir ve veriler kaybolacaktır.

Artık Java'da çöp toplama herhangi bir garantili aralıkta gerçekleşmiyor. StringBöylece uzun süre hafızada sürebilen ve süreç bu süre içinde çökerse, dize içeriği bir bellek dökümü veya bazı günlüğüne sona erebilir.

Bir karakter dizisiyle şifreyi okuyabilir, mümkün olan en kısa sürede onunla çalışmayı bitirebilir ve ardından içeriği hemen değiştirebilirsiniz.


@fallenidol Hiç de değil. Dikkatlice okuyun ve farkları bulacaksınız.
Pritam Banerjee

12

Java dizesi değişmez. Bu nedenle, bir dize oluşturulduğunda, çöp toplanana kadar bellekte kalır. Böylece, belleğe erişimi olan herkes dizenin değerini okuyabilir.
Dizenin değeri değiştirilirse, sonunda yeni bir dize oluşturulur. Böylece hem orijinal değer hem de değiştirilen değer, çöp toplanana kadar bellekte kalır.

Karakter dizisiyle, dizinin içeriği parolanın amacına ulaşıldığında değiştirilebilir veya silinebilir. Dizinin orijinal içeriği, değiştirildikten sonra ve hatta çöp toplama işlemi devreye girmeden önce bellekte bulunmaz.

Güvenlik nedeniyle, şifreyi bir karakter dizisi olarak saklamak daha iyidir.


2

Bu amaçla String veya Char [] kullanmanız gerekip gerekmediği tartışmalıdır, çünkü her ikisinin de avantajları ve dezavantajları vardır. Kullanıcının neye ihtiyacı olduğuna bağlıdır.

Java'daki Dizeler değişmez olduğundan, bazıları dizenizi değiştirmeye çalıştığında yeni bir Nesne oluşturur ve varolan Dize etkilenmez. Bu, parolayı Dize olarak saklamanın bir avantajı olarak görülebilir, ancak nesne kullanımdan sonra bile bellekte kalır. Dolayısıyla, herhangi bir şekilde nesnenin hafıza konumu varsa, o kişi o konumda saklanan parolanızı kolayca izleyebilir.

Char [] değiştirilebilir, ancak kullanımından sonra programcının diziyi açıkça temizleyebilmesi veya değerleri geçersiz kılabilmesi avantajına sahiptir. Bu nedenle, kullanıldığında temizlenir ve hiç kimse sakladığınız bilgileri bilemez.

Yukarıdaki koşullara dayanarak, gereksinimleri için String ile mi yoksa Char [] ile mi gidileceği hakkında bir fikir edinebilirsiniz.


0

Vaka Dizesi:

    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

Vaka CHAR DİZİSİ:

    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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.