← Kursa Dön
📄 Text · 22 min

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 sonra MessageDigest sı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() yerine MessageDigest.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 — IllegalBlockSizeException alı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: keytool komut satırı aracıyla da KeyStore yönetebilirsiniz. Java 9'dan itibaren keytool varsayı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() veya new Random() ile API token, session ID veya anahtar üretmeyin. Saldırganlar önceki çıktılardan sonrakini hesaplayabilir. Güvenlik gerektiren her yerde SecureRandom kullanı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-jwt veya nimbus-jose-jwt kü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, SecureRandom tü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.