Java ile Kriptoloji: Hash, AES, RSA, EC ve Dijital İmza
Giriş — Kriptoloji Nedir?
Kriptoloji, bilgiyi yetkisiz erişimden koruma bilimidir. Günlük hayatta kapı kilidi ne işe yarıyorsa, yazılımda kriptografi de aynı işi görür — verini "kilitlersin", sadece anahtarı olan açabilir.
Java'da java.security paketi hash ve dijital imzaları, javax.crypto paketi şifreleme işlemlerini kapsar. İkisi birlikte JCA (Java Cryptography Architecture) altyapısını oluşturur. JCA provider-based çalışır: algoritmalar soyut tanımlanır, arka planda SunJCE gibi bir provider implementasyonu çalıştırır.
🧠 Analoji: JCA'yı bir priz standardı gibi düşün. Priz şekli (API) sabittir ama arkasındaki elektrik şirketi (provider) değişebilir. Cihazın prize takılması yeterli — elektriğin nereden geldiğini bilmek zorunda değilsin.
1. Hash Fonksiyonları — MessageDigest
Hash fonksiyonu, herhangi boyuttaki veriyi sabit uzunlukta çıktıya dönüştürür. Tek yönlüdür — hash'ten orijinal veriye dönemezsin. Aynı girdi her zaman aynı hash'i üretir ama girdide tek bit değişse hash tamamen farklılaşır.
Java'da MessageDigest sınıfı kullanılır. SHA-256 minimum standart olmalı — MD5 artık güvenli kabul edilmez.
🧠 Analoji: Hash bir et kıyma makinesidir. Etten kıymaya dönüşüm kolay ama kıymadan ete geri dönemezsin.
SHA-256 ile String Hash'leme
import java.security.MessageDigest;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
public class HashExample {
public static void main(String[] args) throws Exception {
String message = "Merhaba Dünya!";
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = digest.digest(message.getBytes(StandardCharsets.UTF_8));
System.out.println("SHA-256: " + HexFormat.of().formatHex(hashBytes));
}
}Büyük Veriler İçin Parçalı Hash
Büyük dosyaları tek seferde belleğe yüklemek istemeyiz. update() ile parça parça besle, sonunda digest() ile sonucu al:
import java.security.MessageDigest;
import java.util.HexFormat;
public class ChunkedHashExample {
public static void main(String[] args) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update("İlk parça...".getBytes());
digest.update("İkinci parça...".getBytes());
byte[] hash = digest.digest(); // Nihai hash
System.out.println("Hash: " + HexFormat.of().formatHex(hash));
}
}⚠️ Dikkat:
digest()çağrıldıktan sonraMessageDigestsıfırlanır (reset). Önceki state kaybolur — birden fazla hash hesaplıyorsanız buna dikkat edin.
2. HMAC — Mac Sınıfı ile Mesaj Doğrulama
Hash tek başına mesaj bütünlüğünü garanti etmez çünkü herkes hesaplayabilir. HMAC, hash'e gizli anahtar ekleyerek hem bütünlük hem kimlik doğrulama sağlar.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.HexFormat;
public class HmacExample {
public static void main(String[] args) throws Exception {
String message = "Ödeme tutarı: 1500 TL";
byte[] keyBytes = "gizli-anahtar-2024".getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(keySpec);
byte[] hmac = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
System.out.println("HMAC: " + HexFormat.of().formatHex(hmac));
}
}HMAC'ın en yaygın kullanımı API isteklerinin doğrulanması ve JWT token imzalamadır.
💡 İpucu: HMAC doğrulamasında
String.equals()yerineMessageDigest.isEqual()kullanın. Normal karşılaştırma ilk farklı karakterde durduğu için timing attack'a açıktır.isEqual()her zaman tüm byte'ları karşılaştırır.
3. Password Hashing — Neden Önemli?
Şifreleri veritabanında asla düz metin saklamayın. SHA-256 bile yeterli değildir — çok hızlıdır, saldırgan saniyede milyarlarca deneme yapabilir. Parola hash'leme algoritmaları kasıtlı olarak yavaştır.
Java standart kütüphanesinde PBKDF2 mevcuttur:
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.security.SecureRandom;
import java.util.HexFormat;
public class PasswordHashExample {
public static void main(String[] args) throws Exception {
String password = "kullanici_sifresi_123";
byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 600_000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();
System.out.println("Salt: " + HexFormat.of().formatHex(salt));
System.out.println("Hash: " + HexFormat.of().formatHex(hash));
spec.clearPassword(); // Şifreyi bellekten temizle!
}
}600.000 iterasyon OWASP 2024 tavsiyesidir. Salt her kullanıcı için farklı olmalı. Üretim ortamında BCrypt veya Argon2 daha güçlü alternatiflerdir — Spring Security'de BCryptPasswordEncoder hazır gelir.
4. Simetrik Şifreleme — AES
Simetrik şifrelemede aynı anahtar hem şifreleme hem çözme için kullanılır. AES (Advanced Encryption Standard) en yaygın simetrik algoritmadır ve 128, 192, 256 bit anahtar destekler.
🧠 Analoji: Simetrik şifreleme, iki kişinin aynı kilide sahip olduğu bir kasa gibidir. Kasayı kilitleyen de açan da aynı anahtarı kullanır.
AES/CBC/PKCS5Padding
CBC modunda her blok öncekiyle XOR'lanır, bu yüzden bir IV (Initialization Vector) gerekir:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.IvParameterSpec;
import java.security.SecureRandom;
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class AesCbcExample {
public static void main(String[] args) throws Exception {
String plaintext = "Gizli mesaj: Proje 15 Mart'ta lansman yapacak!";
byte[] keyBytes = new byte[32]; // 256-bit
byte[] ivBytes = new byte[16]; // 128-bit IV
new SecureRandom().nextBytes(keyBytes);
new SecureRandom().nextBytes(ivBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Şifreli: " + Base64.getEncoder().encodeToString(encrypted));
System.out.println("Çözülen: " + new String(decrypted, StandardCharsets.UTF_8));
}
}IV gizli olmak zorunda değildir ama her şifrelemede benzersiz olmalıdır — genellikle şifreli metnin başına eklenerek saklanır.
AES-GCM — Authenticated Encryption
CBC veriyi şifreler ama bütünlük kontrolü yapmaz. AES-GCM hem şifreleme hem bütünlük garantisi verir — modern uygulamalarda tercih edilir:
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class AesGcmExample {
public static void main(String[] args) throws Exception {
String plaintext = "AES-GCM ile korunan veri";
byte[] keyBytes = new byte[32];
new SecureRandom().nextBytes(keyBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
byte[] nonce = new byte[12]; // GCM için 12 byte önerilir
new SecureRandom().nextBytes(nonce);
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonce);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec);
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Çözülen: " + new String(decrypted, StandardCharsets.UTF_8));
}
}GCM'de 128-bit authentication tag otomatik eklenir. Nonce aynı anahtarla asla tekrar kullanılmamalıdır. Padding gerekmez çünkü GCM stream cipher gibi çalışır.
💡 İpucu: Yeni projelerde CBC yerine GCM tercih edin.
updateAAD()ile şifrelenmeyecek ama bütünlüğü korunacak veri (Associated Data) de ekleyebilirsiniz — örneğin HTTP header'ları.
5. Asimetrik Şifreleme — RSA
Asimetrik şifrelemede iki farklı anahtar kullanılır: public key ile şifrelenen veri yalnızca private key ile çözülebilir. Herkese public key'ini verebilirsin, private key sadece sende kalır.
🧠 Analoji: Asimetrik şifreleme bir posta kutusu gibidir. Herkes mektup atabilir (public key) ama kutuyu sadece ev sahibi açabilir (private key).
RSA Anahtar Çifti Üretme ve Şifreleme
import java.security.*;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class RsaExample {
public static void main(String[] args) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048); // Minimum 2048 bit
KeyPair pair = keyGen.generateKeyPair();
String plaintext = "RSA ile şifrelenmiş gizli mesaj";
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pair.getPublic());
byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, pair.getPrivate());
byte[] decrypted = cipher.doFinal(encrypted);
System.out.println("Şifreli: " + Base64.getEncoder().encodeToString(encrypted));
System.out.println("Çözülen: " + new String(decrypted, StandardCharsets.UTF_8));
}
}OAEP padding modern ve güvenlidir. Eski PKCS1Padding kullanmayın — padding oracle saldırılarına açıktır.
RSA Boyut Sınırlaması ve Hybrid Encryption
RSA'nın kritik sınırı: şifrelenecek veri boyutu anahtar uzunluğuna bağlıdır. 2048-bit + OAEP-SHA256 ile maksimum ~190 byte. Büyük veriler için hybrid encryption kullanılır — veriyi AES ile şifrele, AES anahtarını RSA ile şifrele:
import java.security.*;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
public class HybridExample {
public static void main(String[] args) throws Exception {
KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA");
rsaGen.initialize(2048);
KeyPair rsaPair = rsaGen.generateKeyPair();
KeyGenerator aesGen = KeyGenerator.getInstance("AES");
aesGen.init(256);
SecretKey aesKey = aesGen.generateKey();
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, rsaPair.getPublic());
byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());
System.out.println("AES key: " + aesKey.getEncoded().length + " byte");
System.out.println("RSA encrypted key: " + encryptedKey.length + " byte");
System.out.println("Büyük veri → AES, AES anahtarı → RSA ile şifrele!");
}
}Bu hybrid pattern TLS, PGP ve pek çok modern protokolün temelidir.
⚠️ Dikkat: RSA ile doğrudan büyük veri şifrelemeye çalışmayın —
IllegalBlockSizeExceptionalırsınız. Her zaman hybrid encryption kullanın.
6. Elliptic Curve (EC) Kriptografi
ECC, RSA'ya göre çok daha kısa anahtarlarla aynı güvenliği sağlar. 256-bit EC ≈ 3072-bit RSA. Daha küçük anahtar, daha hızlı işlem, daha az bant genişliği.
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
public class EcKeyGenExample {
public static void main(String[] args) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(new ECGenParameterSpec("secp256r1")); // NIST P-256
KeyPair pair = keyGen.generateKeyPair();
System.out.println("Algoritma: " + pair.getPublic().getAlgorithm());
System.out.println("Public key boyutu: " + pair.getPublic().getEncoded().length + " byte");
}
}EC anahtarları genellikle dijital imza (ECDSA) ve anahtar anlaşma (ECDH) için kullanılır — doğrudan şifreleme için değil.
ECDSA ile İmzalama
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class EcdsaExample {
public static void main(String[] args) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair pair = keyGen.generateKeyPair();
String message = "ECDSA ile imzalandı";
Signature signer = Signature.getInstance("SHA256withECDSA");
signer.initSign(pair.getPrivate());
signer.update(message.getBytes(StandardCharsets.UTF_8));
byte[] sig = signer.sign();
Signature verifier = Signature.getInstance("SHA256withECDSA");
verifier.initVerify(pair.getPublic());
verifier.update(message.getBytes(StandardCharsets.UTF_8));
System.out.println("Doğrulama: " + (verifier.verify(sig) ? "GEÇERLİ ✓" : "GEÇERSİZ ✗"));
}
}ECDSA; Bitcoin, TLS sertifikaları ve modern SSH anahtarlarında kullanılır. RSA imzasından daha kısa ve daha hızlıdır.
7. Dijital İmza — Signature Sınıfı
Dijital imza, mesajın kimden geldiğini ve değiştirilmediğini garanti eder. Private key ile imzala, public key ile doğrula.
🧠 Analoji: Dijital imza noter tasdiki gibidir. Belgeyi sen imzalarsın (private key), herkes noter kayıtlarıyla (public key) doğrulayabilir.
RSA ile İmzalama ve Doğrulama
import java.security.*;
import java.nio.charset.StandardCharsets;
public class RsaSignatureExample {
public static void main(String[] args) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
String document = "Sözleşme: Taraflar anlaşmıştır.";
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(pair.getPrivate());
signer.update(document.getBytes(StandardCharsets.UTF_8));
byte[] signature = signer.sign();
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(pair.getPublic());
verifier.update(document.getBytes(StandardCharsets.UTF_8));
System.out.println("İmza boyutu: " + signature.length + " byte");
System.out.println("Doğrulama: " + (verifier.verify(signature) ? "GEÇERLİ ✓" : "GEÇERSİZ ✗"));
}
}Değiştirilmiş Belge Testi
import java.security.*;
import java.nio.charset.StandardCharsets;
public class TamperedSignatureExample {
public static void main(String[] args) throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
String original = "Tutar: 1000 TL";
String tampered = "Tutar: 9999 TL";
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(pair.getPrivate());
signer.update(original.getBytes(StandardCharsets.UTF_8));
byte[] sig = signer.sign();
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(pair.getPublic());
verifier.update(tampered.getBytes(StandardCharsets.UTF_8));
System.out.println("Değiştirilmiş belge: " + (verifier.verify(sig) ? "GEÇERLİ" : "GEÇERSİZ ✗"));
}
}Tek bir karakter bile değişse imza doğrulaması başarısız olur. Bankacılık, e-devlet ve yazılım dağıtımında dijital imza vazgeçilmezdir.
8. KeyStore — Anahtar Saklama ve Yönetimi
Anahtarları düz dosyalarda saklamak güvenlik açığı yaratır. KeyStore sınıfı anahtarları şifreli bir konteyner içinde saklar. JKS eski Java formatıdır, PKCS12 endüstri standardıdır — her zaman PKCS12 tercih edin.
🧠 Analoji: KeyStore bir kasa-içinde-kasa sistemidir. Dıştaki kasa bir şifre ile korunur, içindeki her bölme kendi şifresiyle ayrıca korunabilir.
import java.security.*;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.*;
public class KeyStoreExample {
public static void main(String[] args) throws Exception {
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(null, null); // Boş keystore
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
ks.setEntry("myAesKey", new KeyStore.SecretKeyEntry(secretKey),
new KeyStore.PasswordProtection("entry-pass".toCharArray()));
// Kaydet
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ks.store(baos, "ks-pass".toCharArray());
// Yükle ve oku
KeyStore loaded = KeyStore.getInstance("PKCS12");
loaded.load(new ByteArrayInputStream(baos.toByteArray()), "ks-pass".toCharArray());
SecretKey loadedKey = (SecretKey) loaded.getKey("myAesKey", "entry-pass".toCharArray());
System.out.println("Anahtar: " + loadedKey.getAlgorithm());
System.out.println("Eşit mi: " + MessageDigest.isEqual(
secretKey.getEncoded(), loadedKey.getEncoded()));
}
}💡 İpucu:
keytoolkomut satırı aracıyla da KeyStore yönetebilirsiniz. Java 9'dan itibarenkeytoolvarsayılan olarak PKCS12 kullanır.
9. SecureRandom — Kriptografik Güvenli Rastgele Sayı
java.util.Random kriptografi için yetersizdir — çıktısı tahmin edilebilir. SecureRandom işletim sisteminin entropi kaynağından beslenir ve tüm güvenlik operasyonlarında kullanılmalıdır.
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HexFormat;
public class SecureRandomExample {
public static void main(String[] args) {
SecureRandom random = new SecureRandom();
// IV, salt vb. için rastgele bytes
byte[] bytes = new byte[32];
random.nextBytes(bytes);
System.out.println("Bytes: " + HexFormat.of().formatHex(bytes));
// 6 haneli OTP
int otp = 100000 + random.nextInt(900000);
System.out.println("OTP: " + otp);
// URL-safe token
byte[] tokenBytes = new byte[32];
random.nextBytes(tokenBytes);
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes);
System.out.println("Token: " + token);
}
}⚠️ Dikkat:
Math.random()veyanew Random()ile API token, session ID veya anahtar üretmeyin. Saldırganlar önceki çıktılardan sonrakini hesaplayabilir. Güvenlik gerektiren her yerdeSecureRandomkullanın.
10. SSL/TLS — SSLContext Temel Tanıtım
SSL/TLS ağ iletişimini şifreleyen protokoldür — HTTPS'in arkasındaki teknolojidir. Java'da SSLContext ile yapılandırılır:
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import java.util.Arrays;
public class SslContextExample {
public static void main(String[] args) throws Exception {
SSLContext context = SSLContext.getDefault();
SSLParameters params = context.getDefaultSSLParameters();
System.out.println("Protokol: " + context.getProtocol());
System.out.println("Desteklenen: " + Arrays.toString(params.getProtocols()));
System.out.println("Cipher sayısı: " + params.getCipherSuites().length);
}
}Java 11+ HttpClient TLS'i otomatik yönetir:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class HttpsExample {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder().build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/get")).build();
HttpResponse<String> resp = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Status: " + resp.statusCode());
System.out.println("TLS: " + resp.sslSession()
.map(s -> s.getProtocol()).orElse("N/A"));
}
}Günümüzde TLS 1.3 tercih edilmeli. HttpClient varsayılan olarak JVM'in cacerts trust store'una güvenir.
11. Base64 Encode/Decode
Kriptografik çıktılar byte dizileridir ve metin olarak gösterilemez. Base64, byte'ları yazdırılabilir ASCII karakterlerine dönüştürür:
import java.util.Base64;
import java.nio.charset.StandardCharsets;
public class Base64Example {
public static void main(String[] args) {
String original = "Java Kriptoloji 2024! 🔐";
byte[] bytes = original.getBytes(StandardCharsets.UTF_8);
// Standart
String standard = Base64.getEncoder().encodeToString(bytes);
// URL-safe (JWT'lerde kullanılır)
String urlSafe = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
// Decode
String decoded = new String(Base64.getDecoder().decode(standard), StandardCharsets.UTF_8);
System.out.println("Base64: " + standard);
System.out.println("URL-safe: " + urlSafe);
System.out.println("Decoded: " + decoded);
}
}Standard encoder genel amaçlıdır. URL encoder +// yerine -/_ kullanır — JWT token'larda bu şarttır. MIME encoder e-posta ekleri için her 76 karakterde satır sonu ekler.
Pratikte IV'yi şifreli verinin başına ekleyip birlikte Base64'lemek yaygındır. Çözme tarafında ilk N byte IV olarak ayrılır, kalanı şifreli veri olarak işlenir.
12. Gerçek Dünya: JWT Token Oluşturma Mantığı
JWT (JSON Web Token) web uygulamalarında kimlik doğrulama standardıdır. Üç parçadan oluşur: Header (algoritma), Payload (veri), Signature (imza). Her parça Base64Url encode edilir ve . ile birleştirilir.
🧠 Analoji: JWT bir kimlik kartıdır. Üzerinde adın ve yetkilerin yazılı (payload), noter tasdikli (signature) ve hangi noter'in tasdik ettiği belli (header). Herkes okuyabilir ama sadece noter tasdik edebilir.
JWT Oluşturma (HS256)
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class JwtExample {
public static void main(String[] args) throws Exception {
Base64.Encoder enc = Base64.getUrlEncoder().withoutPadding();
String header = enc.encodeToString(
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
String payload = enc.encodeToString(
"{\"sub\":\"user123\",\"role\":\"admin\",\"iat\":1708400000}".getBytes(StandardCharsets.UTF_8));
String signingInput = header + "." + payload;
byte[] secret = "super-secret-key-min-256-bits-long!!!!!!".getBytes(StandardCharsets.UTF_8);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret, "HmacSHA256"));
String signature = enc.encodeToString(
mac.doFinal(signingInput.getBytes(StandardCharsets.UTF_8)));
System.out.println("JWT: " + header + "." + payload + "." + signature);
}
}JWT Doğrulama
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
public class JwtVerifyExample {
public static void main(String[] args) throws Exception {
byte[] secret = "super-secret-key-min-256-bits-long!!!!!!".getBytes(StandardCharsets.UTF_8);
Base64.Encoder enc = Base64.getUrlEncoder().withoutPadding();
// Token oluştur (simülasyon)
String h = enc.encodeToString("{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8));
String p = enc.encodeToString("{\"sub\":\"user123\",\"role\":\"admin\"}".getBytes(StandardCharsets.UTF_8));
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret, "HmacSHA256"));
String s = enc.encodeToString(mac.doFinal((h + "." + p).getBytes(StandardCharsets.UTF_8)));
String jwt = h + "." + p + "." + s;
// Doğrula
String[] parts = jwt.split("\\.");
Mac verifyMac = Mac.getInstance("HmacSHA256");
verifyMac.init(new SecretKeySpec(secret, "HmacSHA256"));
String expected = enc.encodeToString(
verifyMac.doFinal((parts[0] + "." + parts[1]).getBytes(StandardCharsets.UTF_8)));
boolean valid = MessageDigest.isEqual(parts[2].getBytes(), expected.getBytes());
System.out.println("JWT geçerli mi: " + (valid ? "EVET ✓" : "HAYIR ✗"));
String decodedPayload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
System.out.println("Payload: " + decodedPayload);
}
}💡 İpucu: Üretim ortamında JWT'yi elle oluşturmayın —
java-jwtveyanimbus-jose-jwtkütüphaneleri kullanın. Ayrıca JWT payload'ı şifreli değildir, sadece Base64'tür — hassas veriyi payload'a koymayın!
Özet
Hash fonksiyonları (
MessageDigest) veriyi sabit uzunlukta tek yönlü özete dönüştürür. SHA-256 minimum standarttır; parolalar için PBKDF2 veya BCrypt gibi kasıtlı olarak yavaş algoritmalar kullanılmalıdır.HMAC (
Mac) hash'e gizli anahtar ekleyerek hem bütünlük hem kimlik doğrulama sağlar. API doğrulama ve JWT imzalamada temel yapı taşıdır. Doğrulamada timing attack'a karşıMessageDigest.isEqual()kullanın.Simetrik şifreleme (AES) aynı anahtarla şifreler ve çözer. AES-GCM hem gizlilik hem bütünlük sağladığından CBC'ye tercih edilmelidir. IV/nonce her operasyonda benzersiz olmalıdır.
Asimetrik şifreleme (RSA, EC) farklı anahtar çiftleri kullanır. RSA boyut sınırlı olduğundan büyük veriler için hybrid encryption (AES + RSA) kullanılır. EC daha kısa anahtarla aynı güvenliği sunar — ECDSA imzaları giderek daha yaygın.
Dijital imzalar (
Signature) mesajın kimden geldiğini ve değiştirilmediğini matematiksel olarak kanıtlar.KeyStore(PKCS12) anahtarları şifreli saklar,SecureRandomtüm kriptografik rastgelelik ihtiyaçlarını karşılar.Base64 binary kriptografik çıktıları metin formatına çevirir. JWT token'lar Header.Payload.Signature yapısında Base64Url + HMAC/RSA imza kullanır — web güvenliğinin temel taşıdır.
AI Asistan
Sorularını yanıtlamaya hazır