Mert Özen Her Satırda Daha İleri
Güvenlik Geliştirici için Kriptografi — #07

TLS'in İçi: Record Layer, Key Schedule, Forward Secrecy ve .NET'te SslStream Ayarları

Mert Özen 14 Kas 2026 12 dk 1 görüntülenme
TLS'in İçi: Record Layer, Key Schedule, Forward Secrecy ve .NET'te SslStream Ayarları

Kaydedilmiş TLS 1.3 trafiği sunucunun private key'iyle çözülemez, çünkü trafik anahtarları her bağlantıda yeni X25519 key exchange'inden HKDF ile türetilir. Key schedule'ı RFC 8448 test vektörleriyle doğruluyor, record layer'daki AES-GCM'i, .NET'in SSLKEYLOGFILE desteğiyle kendi trafiğimizi çözmeyi, resumption ve 0-RTT riskini, EnabledSslProtocols ve CipherSuitesPolicy ayarlarını gerçek çıktılarla gösteriyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10 ve xUnit v3 4.0 ile derlenip denenmiştir; çıktılar Ubuntu 24.04 (OpenSSL 3.0) üzerinde alınmıştır.

Önce Tahmin Et

Bookstore'da Ordering ile Bookstore.Api arasındaki bir istek ara sıra bozuk geliyor ve ekipten biri şöyle bir plan öneriyor: "Sunucunun önünde trafiği kaydedelim; sorun olunca sunucunun private key'ini Wireshark'a verir, kaydı açıp bakarız." Bir önceki yazıda kurduğumuz mTLS bağlantısı TLS (Transport Layer Security; HTTPS'in altındaki şifreli bağlantı protokolü) 1.3 ile konuşuyor. Kayıt elimizde, private key de elimizde. Kaydı çözebilir miyiz?

Çözemeyiz. TLS 1.3'te sunucunun sertifikadaki anahtarı yalnız imza atıyor; trafiği şifreleyen anahtarlar her bağlantıda yeniden üretilen geçici bir key exchange'ten türetiliyor ve bağlantı bitince yok oluyor. Bu özelliğin adı forward secrecy. Bu yazıda o anahtarların nasıl türetildiğini (key schedule), record layer'da AEAD'in (Authenticated Encryption with Associated Data; hem gizleyen hem bütünlüğü doğrulayan şifreleme) nasıl kullanıldığını, .NET'te TLS ayarlarını ve kendi trafiğimizi .NET'in key log'uyla nasıl çözebileceğimizi görüyoruz. Handshake'in mesaj akışı, sertifika zinciri, SNI (Server Name Indication; istemcinin bağlanmak istediği host adını handshake'te bildirmesi) ve ALPN (Application-Layer Protocol Negotiation; HTTP/2 gibi uygulama protokolünün handshake'te seçilmesi) Networking ve HTTP serisinin TLS 1.3 handshake yazısında anlatıldı; burada onun devamından, handshake'ten çıkan secret'lardan başlıyoruz.

Key Schedule: Secret'lardan Trafik Anahtarlarına

TLS 1.3'ün bütün anahtarları, serinin ikinci yazısında gördüğümüz HKDF (HMAC tabanlı anahtar türetme fonksiyonu) ile üretiliyor. Zincir üç aşamalı: Early Secret, Handshake Secret ve Master Secret. Handshake Secret'a key exchange'in, yani X25519 (Curve25519 üzerinde tanımlı key agreement) ile hesaplanan ortak sırrın girdiği yer; bu sır her bağlantıda yeniden üretiliyor. Her aşamadan, her yön için ayrı bir traffic secret türetiliyor, her traffic secret'tan da o yönün AES (Advanced Encryption Standard; standart blok şifre) anahtarı ve IV'si (initialization vector; nonce'un sabit kısmı) çıkıyor:

07-key-schedule-tr.png

Bütün türetmelerin altında tek bir yardımcı fonksiyon var: HKDF-Expand-Label. RFC (Request for Comments; internet standartlarının yayımlandığı belge serisi) 8446'ya göre bu, HKDF-Expand'e verilen info parametresini belirli bir yapıda kurmaktan ibaret: istenen uzunluk, başına "tls13 " eklenmiş etiket ve bağlam. Serinin ikinci yazısında anahtarları info ile ayırmıştık; TLS aynı fikri kullanıyor. Bookstore.Crypto library'sine yalnız öğrenme ve teşhis amacıyla küçük bir Tls klasörü ekledik:

// HKDF-Expand-Label(Secret, Label, Context, Length): info = uzunluk(2) | "tls13 " + etiket (1+n) | bağlam (1+m)
public static byte[] ExpandLabel(HashAlgorithmName hash, ReadOnlySpan<byte> secret, string label, ReadOnlySpan<byte> context, int length)
{
    byte[] fullLabel = Encoding.ASCII.GetBytes("tls13 " + label);
    byte[] info = [(byte)(length >> 8), (byte)length, (byte)fullLabel.Length, .. fullLabel, (byte)context.Length, .. context];
    byte[] output = new byte[length];
    HKDF.Expand(hash, secret, output, info);
    return output;
}

// Bir traffic secret'tan [sender]_write_key ve [sender]_write_iv
public static TlsTrafficKeys TrafficKeys(HashAlgorithmName hash, ReadOnlySpan<byte> trafficSecret, int keyLength) =>
    new(ExpandLabel(hash, trafficSecret, "key", [], keyLength), ExpandLabel(hash, trafficSecret, "iv", [], 12));

Kendi yazdığın kripto kodunu bir test vektörüne karşı doğrulamadan güvenme. RFC 8448, TLS 1.3 için baştan sona bir handshake'in bütün ara değerlerini yayımlıyor; testlerimiz bu değerleri kullanıyor. Handshake Secret ve transcript hash'ten "s hs traffic" secret'ı, ondan da anahtar ve IV birebir RFC'deki değerler çıkıyor:

[Theory]
[InlineData("b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38", "3fce516009c21727d0f2e4e86ee403bc", "5d313eb2671276ee13000b30")]
[InlineData("9e40646ce79a7f9dc05af8889bce6552875afa0b06df0087f792ebb7c17504a5", "17422dda596ed5d9acd890e3c63f5051", "5b78923dee08579033e523d9")]
public void Traffic_key_and_iv_match_rfc8448(string trafficSecret, string key, string iv)
{
    TlsTrafficKeys keys = TlsKeySchedule.TrafficKeys(HashAlgorithmName.SHA256, Hex(trafficSecret), keyLength: 16);
    Assert.Equal(key, Convert.ToHexStringLower(keys.Key));
    Assert.Equal(iv, Convert.ToHexStringLower(keys.Iv));
}

Transcript hash'in türetmeye girmesi önemli bir ayrıntı: traffic secret'lar o ana kadarki bütün handshake mesajlarının hash'ine bağlı. Handshake'te tek bir byte değişirse iki taraf farklı anahtarlar türetir ve bağlantı Finished mesajında kopar.

Record Layer'da AEAD

Handshake bittikten sonra her şey record'larla taşınıyor: 5 byte'lık bir başlık (tip, eski sürüm alanı, uzunluk) ve şifreli içerik. TLS 1.3'te şifreli bütün record'ların dışarıdan görünen tipi application_data; gerçek tip şifreli içeriğin sonunda saklı. Bir record'u çözmek için serinin üçüncü yazısındaki AES-GCM bilgisi yetiyor:

// RFC 8446 5.2-5.3: nonce = iv XOR sıra numarası, AAD = record başlığı,
// açık metin = içerik | gerçek içerik tipi | sıfır dolgu
public static (TlsContentType Type, byte[] Content) Decrypt(TlsTrafficKeys keys, ulong sequenceNumber, TlsRecord record)
{
    byte[] nonce = keys.Iv.ToArray();
    Span<byte> sequence = stackalloc byte[8];
    BinaryPrimitives.WriteUInt64BigEndian(sequence, sequenceNumber);
    for (int i = 0; i < 8; i++)
    {
        nonce[nonce.Length - 8 + i] ^= sequence[i];
    }

    const int TagSize = 16;
    ReadOnlySpan<byte> fragment = record.Fragment;
    byte[] plaintext = new byte[fragment.Length - TagSize];
    using AesGcm aes = new(keys.Key, TagSize);
    aes.Decrypt(nonce, fragment[..^TagSize], fragment[^TagSize..], plaintext, record.Header);

    int end = plaintext.Length - 1;
    while (end >= 0 && plaintext[end] == 0)
    {
        end--;
    }

    return ((TlsContentType)plaintext[end], plaintext[..end]);
}

Üçüncü yazıdaki nonce kuralını hatırla: bir anahtarla aynı nonce iki kez kullanılmamalı. TLS bunu sayaçla çözüyor: her yönün kendi sıra numarası var, her record'da bir artıyor ve sabit IV ile XOR'lanıyor (bit bit dışlayan veya ile birleştiriliyor). Sayaç tekrar etmediği sürece nonce da tekrar etmiyor ve hiçbir yerde rastgele nonce yok. Record başlığı AAD (associated data; şifrelenmeyen ama doğrulanan ek veri) olarak giriyor; başlıktaki tek bir byte'ı ya da sıra numarasını değiştirmek doğrulamayı bozuyor. Testlerimiz RFC 8448'deki istemci record'unu çözüyor ve bu iki bozma durumunu da kilitliyor:

$ dotnet test
Test run summary: Passed!
  total: 61
  failed: 0
  succeeded: 61
  skipped: 0
  duration: 5s 358ms

Kendi Trafiğini Çözmek: SSLKEYLOGFILE

Önce Tahmin Et'teki sorunun gerçek cevabı burada. Trafiği sonradan çözmek için private key'e değil, bağlantının traffic secret'larına ihtiyacın var. Bu secret'ları bir dosyaya yazmanın standart biçimi SSLKEYLOGFILE; Temmuz 2026'da RFC 9850 olarak yayımlandı ve Wireshark gibi araçlar bu dosyayı okuyabiliyor. RFC, mekanizmanın yalnız geliştirme ve teşhis için olduğunu ve production sistemde kullanılmaması gerektiğini açıkça yazıyor.

.NET'te durum şöyle: SslStream, Linux'ta (OpenSSL kullanan yolda) SSLKEYLOGFILE environment variable'ı tanımlıysa secret'ları oraya yazabiliyor. Release build'lerde bunun için ayrıca System.Net.EnableSslKeyLogging AppContext switch'inin açık olması gerekiyor; bunu dotnet/runtime deposundaki SslKeyLogger kaynağından doğruladım. Aynı kaynağa göre Windows'ta SslStream'in kullandığı Schannel yolu bu dosyayı yazmıyor. Lab komutu switch'i ilk TLS bağlantısından önce açıyor, iki uç arasında loopback üzerinden bir bağlantı kuruyor, istemci tarafındaki ham byte'ları bir sarmalayıcı stream ile kaydediyor ve sonra key log'daki secret'larla bu kaydı çözüyor:

string path = Environment.GetEnvironmentVariable("SSLKEYLOGFILE") ?? throw new InvalidOperationException("Set SSLKEYLOGFILE");
// .NET'in Release build'lerinde bu switch de gerekir; ilk TLS bağlantısında bir kez okunur
AppContext.SetSwitch("System.Net.EnableSslKeyLogging", true);
$ SSLKEYLOGFILE=certs/keylog.txt dotnet run --project src/Bookstore.CryptoLab -- tls-keylog
Tls13, TLS_AES_256_GCM_SHA384
key log labels: SERVER_HANDSHAKE_TRAFFIC_SECRET, CLIENT_HANDSHAKE_TRAFFIC_SECRET, EXPORTER_SECRET, SERVER_TRAFFIC_SECRET_0, CLIENT_TRAFFIC_SECRET_0
connection 1: client_random 21b63924684e7c35..., CLIENT_TRAFFIC_SECRET_0 c06953236efc8c6d...
client -> server
  Handshake          277 B  plaintext: ClientHello
  ChangeCipherSpec     1 B  plaintext: (compatibility, ignored in TLS 1.3)
  ApplicationData     69 B  c hs traffic, seq 0: Handshake Finished
  ApplicationData    103 B  c ap traffic, seq 0: ApplicationData "{"orderId":"0197f001-0000-7000-8000-0000..."
server -> client
  Handshake          122 B  plaintext: ServerHello
  ChangeCipherSpec     1 B  plaintext: (compatibility, ignored in TLS 1.3)
  ApplicationData     23 B  s hs traffic, seq 0: Handshake EncryptedExtensions
  ApplicationData   1060 B  s hs traffic, seq 1: Handshake Certificate
  ApplicationData     95 B  s hs traffic, seq 2: Handshake CertificateVerify
  ApplicationData     69 B  s hs traffic, seq 3: Handshake Finished
  ApplicationData    282 B  s ap traffic, seq 0: Handshake NewSessionTicket
  ApplicationData    282 B  s ap traffic, seq 1: Handshake NewSessionTicket
  ApplicationData     25 B  s ap traffic, seq 2: ApplicationData "reserved"
connection 2: client_random d8fb21b0d5e575af..., CLIENT_TRAFFIC_SECRET_0 ed444d0d4b2b1bc6...
same certificate, different traffic secret: True

Bu çıktı key schedule'ı canlı gösteriyor. ClientHello ve ServerHello açık metin; key exchange tam bu iki mesajla yapılıyor. Sunucunun sertifikası bile şifreli: TLS 1.2'de ağdan okunabilen sertifika, 1.3'te handshake traffic anahtarıyla gidiyor. Her yön önce kendi handshake anahtarıyla 0'dan sayıyor; Finished mesajından sonra application traffic anahtarına geçip sıra numarasını yeniden 0'dan başlatıyor. Sunucunun gönderdiği iki NewSessionTicket ise ileride kullanılacak oturum devam ettirme bileti. Dış tipi ApplicationData olan record'ların içinden handshake mesajları ve bizim JSON'umuz çıkıyor. Lab, key log'u loopback bağlantısı için kendisi yazıyor ve sonra siliyorum; secret'ları ve client random'ı burada kısaltarak gösterdim.

Forward Secrecy, Resumption ve 0-RTT

Çıktının son iki satırı forward secrecy'nin kaynağını gösteriyor: aynı sertifikayla kurulan ikinci bağlantının traffic secret'ı tamamen farklı, çünkü Handshake Secret'a giren X25519 ortak sırrı her bağlantıda yeni üretilen geçici anahtarlardan geliyor. Sertifikanın anahtarı yalnız CertificateVerify mesajında imza atıyor; sonradan çalınsa bile geçmiş bağlantıların secret'larını yeniden hesaplamaya yetmiyor. TLS 1.2'de RSA (en yaygın public key algoritması) ile key transport kullanan cipher suite'lerde durum farklıydı: ön secret sunucunun public key'iyle şifrelenip gönderiliyordu ve private key'e sahip olan, kaydedilmiş trafiği sonradan açabiliyordu. RFC 8446 bu statik RSA ve Diffie-Hellman cipher suite'lerini TLS 1.3'ten tamamen çıkardı.

Resumption, yani oturumu devam ettirmek, NewSessionTicket ile alınan bir PSK (pre-shared key; önceden paylaşılmış anahtar) ile tam handshake'in maliyetini azaltıyor. openssl s_client ile lab'ın tls-server komutuna bağlanıp bileti kaydettim ve ikinci bağlantıda kullandım:

$ echo hi | openssl s_client -connect 127.0.0.1:7444 -servername localhost -CAfile certs/root-ca.crt.pem -sess_out certs/sess.pem -ign_eof
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
$ echo hi | openssl s_client -connect 127.0.0.1:7444 -servername localhost -CAfile certs/root-ca.crt.pem -sess_in certs/sess.pem -ign_eof
Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Early data was not sent
    Max Early Data: 0

TLS 1.3 resumption'da PSK ile birlikte yeni bir key exchange yapılabiliyor ve forward secrecy korunuyor. Asıl dikkat isteyen 0-RTT: istemci, PSK ile ilk mesajında sunucunun yanıtını beklemeden veri gönderebiliyor. RFC 8446'ya göre bu "early data" yalnız PSK'den türetilen anahtarlarla şifreleniyor, yani forward secrecy'si yok, ve bağlantılar arasında tekrar oynatmaya karşı korumalı değil; ağdaki biri aynı isteği sunucuya yeniden gönderebilir. Bir stok rezervasyonunun ya da ödemenin iki kez çalışması tam olarak istemediğimiz şey. .NET tarafında bu riski seçmek zaten mümkün değil: SslClientAuthenticationOptions ve SslServerAuthenticationOptions'un public property'leri arasında early data ayarı yok, yalnız resumption'ı kapatan AllowTlsResume var. Sunucunun bilette "Max Early Data: 0" duyurması da bunu gösteriyor. 0-RTT'yi bir reverse proxy ya da CDN (Content Delivery Network; içeriği kullanıcıya yakın sunuculardan dağıtan ağ) üzerinde açarsan, yalnız idempotent isteklere izin verildiğinden emin ol.

TLS 1.2 ve 1.3 Cipher Suite Modeli

TLS 1.2'de bir cipher suite adı her şeyi söylüyordu: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 key exchange'i (ECDHE), sertifika imzasını (ECDSA), şifrelemeyi ve hash'i birlikte seçiyor. TLS 1.3'te suite yalnız AEAD ve hash'i seçiyor: TLS_AES_256_GCM_SHA384. Key exchange grubu (X25519 gibi) ve imza algoritması ayrı extension'larla anlaşılıyor; RFC 8446 yalnız beş suite tanımlıyor. tls-handshake komutu aynı sertifikayla farklı ayarları deniyor:

$ dotnet run --project src/Bookstore.CryptoLab -- tls-handshake
OS defaults             : Tls13, TLS_AES_256_GCM_SHA384
both TLS 1.2            : Tls12, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
server 1.3, client 1.2  : AuthenticationException: SSL Handshake failed with OpenSSL error - SSL_ERROR_SSL.
server ChaCha20 only    : Tls13, TLS_CHACHA20_POLY1305_SHA256

İlk satır en önemlisi: EnabledSslProtocols'ü hiç ayarlamadığımızda (değeri SslProtocols.None) sürümü işletim sistemi seçiyor ve sonuç TLS 1.3. Microsoft'un TLS rehberi de sürümü koda yazmamayı, kararı işletim sistemine bırakmayı öneriyor: yeni sürümler geldiğinde kod değişmeden kullanılıyor, güvensiz bulunan sürümler de işletim sistemi tarafından kapatılıyor. Üçüncü satır sürümü sabitlemenin bedeli: yalnız TLS 1.2 isteyen bir istemci, yalnız 1.3 kabul eden sunucuya bağlanamıyor. Hata mesajındaki OpenSSL, bu çıktının Linux'tan geldiğini gösteriyor; Windows'ta aynı durum Schannel hatasıyla döner.

Dördüncü satırda sunucu CipherSuitesPolicy ile yalnız ChaCha20-Poly1305'e izin verdi. Bu ayar Linux'ta çalışıyor; Microsoft'un SslStream rehberine göre Windows'ta CipherSuitesPolicy oluşturmak bile NotSupportedException fırlatıyor ve cipher suite'ler uygulama koduyla değil, Enable-TlsCipherSuite ve Disable-TlsCipherSuite PowerShell cmdlet'leriyle makine düzeyinde ayarlanıyor. Kodda bir suite listesi tutmak, işletim sisteminin güncellemeyle getirdiği iyileştirmeleri de dışarıda bırakıyor; zorunlu bir uyumluluk gereksinimin yoksa dokunma.

openssl s_client ile Gözlem

Karşı tarafın ne konuştuğunu görmenin en hızlı yolu openssl s_client. Lab'ın sunucusuna bağlanınca anlaşılan sürüm, suite ve key exchange grubu tek bakışta görünüyor:

$ echo hi | openssl s_client -connect 127.0.0.1:7444 -servername localhost -CAfile certs/root-ca.crt.pem -brief
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: O = Bookstore, CN = api.bookstore.test
Hash used: SHA256
Signature type: ECDSA
Verification: OK
Server Temp Key: X25519, 253 bits
$ echo hi | openssl s_client -connect 127.0.0.1:7444 -servername localhost -CAfile certs/root-ca.crt.pem -tls1_2 -brief
Protocol version: TLSv1.2
Ciphersuite: ECDHE-ECDSA-AES256-GCM-SHA384
Server Temp Key: X25519, 253 bits

"Server Temp Key" satırı forward secrecy'nin kanıtı: geçici bir X25519 anahtarı. TLS 1.2 bağlantısı da ECDHE kullandığı için forward secrecy'ye sahip; sorun TLS 1.2'nin kendisi değil, ona izin verilen eski RSA key transport suite'leri ve zayıf algoritmalar. Sunucun TLS 1.2'yi hâlâ kabul ediyorsa ECDHE'siz suite'lerin kapalı olduğunu bu şekilde kontrol edebilirsin.

Üretimde Dikkat

TLS'in nerede sonlandığına bilinçli karar ver. Reverse proxy'de sonlanıyorsa proxy ile uygulama arasındaki ağ da bir saldırı yüzeyi; iç trafik için ayrı bir TLS bağlantısı ya da önceki yazıdaki mTLS gerekebilir. Web Server'lar ve Hosting serisi bu kurulumları anlatıyor. Uygulamada sonlanıyorsa sürüm ve suite kararı Kestrel'in çalıştığı makinenin işletim sistemine bağlı; Windows sunucunda Schannel ayarlarını, Linux container'ında image'daki OpenSSL sürümünü ve /etc/ssl/openssl.cnf'i envantere ekle.

Key log'u production'a sızdırmamak için iki kat koruman var: switch'i yalnız geliştirme konfigürasyonunda aç ve container image'larında SSLKEYLOGFILE tanımlı olmadığını bir build kontrolüyle doğrula. Key log dosyası, içindeki bağlantıların tamamını açan bir anahtar dosyası; onu private key gibi koru ve işin bitince sil.

Ölçüm için hangi sürümün ve suite'in anlaşıldığını logla ya da metrik olarak topla: SslStream.SslProtocol ve NegotiatedCipherSuite bunu veriyor. "TLS 1.2'yi kapatabilir miyiz?" sorusunun cevabı bu metrikte.

Sık Düşülen Tuzaklar

Birinci tuzak, Önce Tahmin Et'teki: kaydedilen TLS 1.3 trafiğinin sunucu private key'iyle çözülebileceğini sanmak. Teşhis için traffic secret gerekiyor; production'da ise doğru araç uygulama seviyesinde log ve tracing.

İkincisi, "uyumluluk için" eski protokolleri açmak. TLS 1.0 ve 1.1, RFC 8996 ile kullanımdan kaldırıldı. Eski bir iş ortağı için gerekiyorsa o bağlantıyı ayrı bir endpoint'e, ayrı bir makineye ya da ayrı bir proxy'ye izole et; bütün servisin sürüm tabanını düşürme.

Üçüncüsü, sürümü koda sabitlemek. EnabledSslProtocols = SslProtocols.Tls12 yazan bir istemci, işletim sistemi TLS 1.3'ü desteklese bile 1.2'de kalıyor. Microsoft rehberi ayrıca SslProtocols.Default'un eski protokolleri seçtiği konusunda uyarıyor.

Dördüncüsü, key log mekanizmasını teşhis için açıp kapatmayı unutmak ya da dosyayı paylaşılan bir dizine yazmak.

Beşincisi, CipherSuitesPolicy'yi her platformda çalışacak sanmak. Windows'ta nesneyi oluşturmak exception fırlatıyor; kodun Windows'ta da çalışacaksa bu ayarı platform kontrolünün arkasına koy; lab komutumuz OperatingSystem.IsLinux() ile koruyor.

Küçük Bir Deneme

★☆☆ tls-handshake komutuna, sunucunun CipherSuitesPolicy ile yalnız TLS_AES_128_GCM_SHA256'ya izin verdiği bir satır ekle. tls-keylog bu suite'te çalışıyor mu? Hangi hash ve anahtar uzunluğu kullanılıyor?

★★☆ RFC 8448'deki istemcinin şifreli Finished record'unu (17 03 03 00 35 ...) çözen bir test yaz. Bunun için önce "c hs traffic" secret'ını RFC'deki Handshake Secret ve transcript hash'ten DeriveSecret ile türetmen gerekecek.

★★★ tls-keylog çıktısında sunucunun gönderdiği NewSessionTicket mesajlarını çözümle: ticket lifetime'ını ve bilet uzunluğunu yazdır. Sonra AllowTlsResume = false ile sunucunun bilet göndermeyi bırakıp bırakmadığını kontrol et.

Bu yazıda trafiği şifreleyen anahtarların her bağlantıda X25519 ile yeniden üretildiğini gördük. Bu, bugünün saldırganına karşı yeterli; ama trafiği bugün kaydedip yıllar sonra bir kuantum bilgisayarla çözmeyi planlayan birine karşı değil. Bir sonraki yazıda post-quantum kriptografiye geçiyoruz: NIST'in (ABD Ulusal Standartlar ve Teknoloji Enstitüsü) yeni standartları ve TLS'teki hybrid key exchange.