Kısaca cevap
Java rastgele şifre oluşturma için java.util.Random veya Math.random() değil, java.security.SecureRandom kullanın. Havuzdaki her karakteri alfabe.charAt(rastgele.nextInt(alfabe.length())) ile seçmeniz yeterlidir; nextInt(bound) sapmayı kendisi önler. Aşağıdaki kodlar Java 21 ile derlenip çalıştırıldı; token örneğindeki HexFormat Java 17 ve sonrasını gerektirir.
Bu sayfada
Java Random class ile şifre üretmek neden güvensiz?
java.util.Random oyun, simülasyon ve test için yeterlidir ama Java belgeleri bu sınıfın kriptografik olarak güvenli olmadığını açıkça yazar ve güvenlik açısından hassas uygulamalar için SecureRandom önerir. Aynı tohumla oluşturulan iki Random nesnesi aynı çağrılar yapıldığında birebir aynı sayıları verir:
import java.util.Random;
public class TohumDemo {
public static void main(String[] args) {
Random a = new Random(2026);
Random b = new Random(2026);
for (int i = 0; i < 8; i++) System.out.print(a.nextInt(10) + " ");
System.out.println();
for (int i = 0; i < 8; i++) System.out.print(b.nextInt(10) + " ");
System.out.println();
}
}
9 0 7 0 9 8 3 6
9 0 7 0 9 8 3 6
İki satır aynıdır. Test yazarken işe yarayan bu özellik, şifre üretirken ciddi bir açıktır. Math.random() da arka planda bir java.util.Random nesnesi kullanır; ThreadLocalRandom için de belgeler kriptografik olarak güvenli olmadığını belirtir. Teorik arka plan için kriptografi ve rastgelelik yazısına bakabilirsiniz.
Java SecureRandom vs Random
| Özellik | java.util.Random | java.security.SecureRandom |
|---|---|---|
| Amaç | Oyun, simülasyon, test verisi | Şifre, token, anahtar, doğrulama kodu |
| Kriptografik olarak güvenli mi? | Hayır | Evet |
| Aynı tohum aynı diziyi verir mi? | Evet | setSeed mevcut tohumun yerine geçmez, onu destekler |
| Çoklu iş parçacığı | Güvenli, ama yoğun kullanımda yavaşlar | Güvenli |
| Kullanılan metotlar | nextInt(bound), nextBytes, ints(...) (SecureRandom, Random'dan türer) | |
SecureRandom, Random sınıfının alt sınıfı olduğu için alıştığınız metotları aynen kullanırsınız; yalnızca rastgelelik kaynağı değişir.
Java SecureRandom example: rastgele şifre üretici
import java.security.SecureRandom;
public class SifreUretici {
private static final String ALFABE =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+?";
private static final SecureRandom RASTGELE = new SecureRandom();
public static String sifreUret(int uzunluk) {
StringBuilder sb = new StringBuilder(uzunluk);
for (int i = 0; i < uzunluk; i++) {
sb.append(ALFABE.charAt(RASTGELE.nextInt(ALFABE.length())));
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(sifreUret(16));
System.out.println(sifreUret(24));
}
}
Örnek çıktı (her çalıştırmada farklı olur):
)jEfQ9m4$4EdMVh&
ZFBHxiEcqaw^IN((nln(Ll0A
Havuz 77 karakterdir (26 küçük harf, 26 büyük harf, 10 rakam, 15 özel karakter); 16 karakterlik bir şifre yaklaşık 100 bit rastgelelik taşır. SecureRandom nesnesi bir kez oluşturulup paylaşıldı; belgeler bu sınıfın birden fazla iş parçacığından aynı anda kullanılabileceğini belirtir.
nextInt(bound) kullanmanın bir avantajı da modulo sapmasını önlemesidir. Rastgele bir sayıyı % 77 ile küçültmek, sayı aralığı 77'ye tam bölünmediğinde bazı karakterleri daha sık seçtirir. Java belgelerindeki algoritma, eşit dağılmayacak değerleri atıp yeniden çekerek her sonucu eşit olasılıklı yapar.
ints() akışıyla kısa yazım
Döngü yerine akış (stream) tercih ederseniz ints(adet, alt, üst) metodu da aynı işi görür:
import java.security.SecureRandom;
import java.util.stream.Collectors;
public class StreamSifre {
public static void main(String[] args) {
String alfabe = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
SecureRandom rastgele = new SecureRandom();
String sifre = rastgele.ints(20, 0, alfabe.length())
.mapToObj(i -> String.valueOf(alfabe.charAt(i)))
.collect(Collectors.joining());
System.out.println(sifre);
}
}
Örnek çıktı (her çalıştırmada farklı olur): bwFqEHDIJYB8IQ4SaTKG. Bu örnekte yalnızca harf ve rakamdan oluşan 62 karakterlik havuz kullanıldı; özel karakter kabul etmeyen sistemler için uygundur.
Her karakter türünü garanti eden sürüm
"En az bir büyük harf, bir rakam ve bir özel karakter" kuralı olan sistemler için önce her gruptan bir karakter alın, kalanı tüm havuzdan doldurun ve sırayı aynı SecureRandom ile karıştırın:
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class GucluSifre {
private static final String[] GRUPLAR = {
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
"!@#$%^&*()-_=+?"
};
private static final SecureRandom RASTGELE = new SecureRandom();
public static String gucluSifre(int uzunluk) {
if (uzunluk < GRUPLAR.length) {
throw new IllegalArgumentException("Uzunluk en az " + GRUPLAR.length + " olmalı");
}
String tum = String.join("", GRUPLAR);
List<Character> karakterler = new ArrayList<>(uzunluk);
// Her gruptan en az bir karakter
for (String grup : GRUPLAR) {
karakterler.add(grup.charAt(RASTGELE.nextInt(grup.length())));
}
// Kalan konumlar tüm havuzdan
while (karakterler.size() < uzunluk) {
karakterler.add(tum.charAt(RASTGELE.nextInt(tum.length())));
}
// Sırayı SecureRandom ile karıştır
Collections.shuffle(karakterler, RASTGELE);
StringBuilder sb = new StringBuilder(uzunluk);
for (char c : karakterler) sb.append(c);
return sb.toString();
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) System.out.println(gucluSifre(16));
}
}
Örnek çıktı (her çalıştırmada farklı olur):
g7_I@Nm^e9p3psZf
OOONm9J0yJYwxt)L
ojr%sa=X^mn2P5-0
Collections.shuffle metoduna ikinci parametre olarak SecureRandom vermek önemlidir; parametresiz sürüm belgelere göre "varsayılan bir rastgelelik kaynağı" kullanır ve bu kaynağın kriptografik olarak güvenli olduğu belirtilmez. Karıştırma yapılmazsa ilk dört karakter her zaman aynı tür sırasıyla gelir. Fonksiyonu 4 karakter uzunluğunda 10.000 kez çalıştırdık; her çıktıda dört türün de bulunduğunu doğruladık.
Token ve API anahtarı üretmek
İnsanların yazmayacağı değerler için karakter havuzu yerine rastgele bayt üretip metne çevirmek daha kısadır:
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HexFormat;
public class TokenUret {
public static void main(String[] args) {
SecureRandom rastgele = new SecureRandom();
byte[] bayt = new byte[16]; // 128 bit
rastgele.nextBytes(bayt);
System.out.println(HexFormat.of().formatHex(bayt));
System.out.println(Base64.getUrlEncoder().withoutPadding().encodeToString(bayt));
}
}
Örnek çıktı (her çalıştırmada farklı olur):
b7e8bdd2df495b22fb0e6054619316b7
t-i90t9JWyL7DmBUYZMWtw
16 bayt, onaltılık biçimde 32 karakter, adres satırına uygun Base64 biçiminde 22 karakter eder. URL uyumlu Base64 çıktısı yalnızca harf, rakam, - ve _ içerir; şifre sıfırlama bağlantıları için uygundur.
6 haneli doğrulama kodu
E-posta veya SMS ile gönderilen tek kullanımlık kodlar da tahmin edilmemesi gereken değerlerdir. nextInt(1_000_000) 0 ile 999.999 arasında bir sayı verir; %06d biçimi küçük sayıların başına sıfır ekleyerek kodu her zaman 6 hane yapar:
import java.security.SecureRandom;
public class DogrulamaKodu {
public static void main(String[] args) {
SecureRandom rastgele = new SecureRandom();
String kod = String.format("%06d", rastgele.nextInt(1_000_000)); // 000000–999999
System.out.println(kod);
}
}
Örnek çıktı (her çalıştırmada farklı olur): 990069. Böyle bir kodun yalnızca bir milyon olasılığı vardır; bu yüzden sunucu tarafında deneme sayısını sınırlamak ve kodu kısa süre sonra geçersiz kılmak gerekir.
Java rastgele şifreler: uzunluk ve havuz seçimi
Rastgele bir şifrenin gücü havuzdaki karakter sayısına ve uzunluğa bağlıdır; entropi uzunluk × log2(havuz boyutu) olarak hesaplanır. Aşağıdaki değerleri bu formülle hesapladık:
| Havuz | Uzunluk | Yaklaşık entropi |
|---|---|---|
| Harf + rakam (62) | 12 | 71,5 bit |
| Harf + rakam (62) | 20 | 119,1 bit |
| Harf + rakam + 15 özel karakter (77) | 12 | 75,2 bit |
| Harf + rakam + 15 özel karakter (77) | 16 | 100,3 bit |
Özel karakter eklemek entropiyi biraz artırır, ama uzunluğu artırmak çok daha etkilidir. Özel karakter kabul etmeyen sistemlerde yukarıdaki ints() örneğindeki gibi 62 karakterlik havuzla 20 karakter kullanmak rahat bir çözümdür.
Sık yapılan hatalar
new Random()veyaMath.random()kullanmak: Çıktı rastgele görünür ama tahmin edilebilir bir üreteçten gelir. Çoğu zaman tek düzeltme, nesneyiSecureRandomile değiştirmektir.- Kendi mod hesabını yazmak:
Math.abs(rastgele.nextInt()) % alfabe.length()hem sapma yaratır hem deInteger.MIN_VALUEiçin negatif sonuç verebilir.nextInt(bound)kullanın. - Parametresiz
Collections.shuffle: Karıştırmada da aynıSecureRandomnesnesini verin. UUID.randomUUID()'yi şifre sanmak: Benzersiz kimlik için uygundur ama biçimi sabittir ve yalnızca onaltılık karakter içerir; kullanıcı şifresi için yukarıdaki fonksiyonlar daha uygundur.
SecureRandom kullanırken dikkat
- Kendi tohumunuzu vermeyin:
new SecureRandom()kendini güvenli biçimde tohumlar. Belgelere göre ilk kullanımdan öncesetSeedçağrılırsa bazı uygulamalar kendini otomatik tohumlamaz; sabit bir tohum vermek rastgeleliği tahmin edilebilir kılabilir. getInstanceStrong()şart değil: Belgeler bu yöntemi uzun ömürlü anahtarlar gibi yüksek değerli sırlar için önerir ve bazı sistemlerde entropi toplarken bekleyebileceğini belirtir. Şifre ve token için varsayılan yapıcı yeterlidir.- Şifreyi günlüğe yazmayın: Örneklerdeki
System.out.printlnyalnızca deneme içindir. - Saklarken özetleyin: Kullanıcı şifrelerini düz metin veya MD5/SHA-256 ile değil, tuzlu ve yavaş bir yöntemle saklayın. Java'da yerleşik PBKDF2 örneği Java ile MD5 ve SHA-256 hash hesaplama yazısında, genel anlatım şifre saklama yazısında.
Uzunluk ve havuz seçimi için şifre kaç karakter olmalı yazısına bakabilirsiniz. Aynı mantığın diğer dillerdeki karşılıkları: Python, JavaScript, C# ve PHP. Kod yazmadan bir şifreye ihtiyacınız varsa şifre üretici tarayıcınızda çalışır.
Sık sorulan sorular
Java'da Random ile SecureRandom arasındaki fark nedir?
java.util.Random hızlı ama öngörülebilir bir üreteçtir; aynı tohumla oluşturulan iki nesne aynı sayı dizisini verir ve Java belgeleri onun kriptografik olarak güvenli olmadığını açıkça yazar. SecureRandom ise kriptografik olarak güçlü bir üreteçtir ve şifre, token ya da anahtar gibi güvenlik açısından hassas değerler için önerilir.
SecureRandom nextInt(bound) sapmasız mı çalışır?
Evet. SecureRandom, java.util.Random sınıfından türer ve nextInt(bound) metodunu miras alır. Belgelerdeki algoritma, aralığa eşit dağılmayacak değerleri atıp yeniden çektiği için her sonuç eşit olasılıkla seçilir. Kendi mod hesabınızı yazmanıza gerek yoktur.
SecureRandom.getInstanceStrong kullanmalı mıyım?
Genellikle gerekmez. Şifre ve token üretmek için new SecureRandom() yeterlidir. getInstanceStrong uzun ömürlü anahtarlar gibi yüksek değerli sırlar için düşünülmüştür ve belgelere göre bazı sistemlerde entropi toplarken bekleyebilir; bu da sunucu uygulamalarında beklenmedik gecikmelere yol açabilir.
SecureRandom nesnesini her seferinde yeniden oluşturmak gerekir mi?
Hayır. SecureRandom nesneleri birden fazla iş parçacığından aynı anda kullanılabilir. Sınıf düzeyinde bir kez oluşturup paylaşmak hem yeterli hem de daha verimlidir.
Ürettiğim şifreyi veritabanına nasıl kaydetmeliyim?
Düz metin veya MD5, SHA-256 gibi hızlı özetler kullanmayın. Java'da yerleşik seçenek, SecretKeyFactory ile PBKDF2WithHmacSHA256 algoritmasıdır; Argon2 veya bcrypt için ayrı kütüphaneler bulunur. Her şifre için ayrı, rastgele bir tuz kullanın.



