E2EE Kimlik Doğrulaması: Aradaki Anahtar Değişimini Durdurmak | eSeGeCe Blog

E2EE Kimlik Doğrulaması: Aradaki Anahtar Değişimini Durdurmak

· Bileşenler

sgcWebSockets'te Uçtan Uca Şifrelemeyi tanıttığımızda verdiğimiz güvence açıktı: Alice ve Bob, ECDH ile ortak bir gizli anahtar türetir, AES-GCM ile şifreler ve trafiği yönlendiren sunucu şifreli metinden başka bir şey görmez. Bu kısım sağlamdır. Ancak bütün bunlardan önce, şifrelemenin kendisinin koruyamadığı bir adım vardır ve gerçek saldırılar tam olarak o adımda olur: Alice, Bob'un açık anahtarını bir yerden almak zorundadır ve onu sunucu üzerinden alır.

Saldırıyı somut olarak canlandırın. Alice, sunucudan Bob'un açık anahtarını ister. Ele geçirilmiş bir sunucu, kötü niyetli bir aktarıcı ya da o durağı devralmış herhangi biri, Bob'un anahtarını iletmez. Kendi anahtar çiftini üretir ve kendi açık anahtarını "Bob" etiketiyle Alice'e verir. Sonra aynısını diğer yönde de yapar ve Bob'a kendi farklı bir anahtarını "Alice" etiketiyle verir. Alice artık kusursuz şekilde şifreler, ama saldırgana. Saldırgan şifreyi çözer, okur, muhtemelen yeniden yazar, Bob'un gerçek anahtarıyla yeniden şifreler ve iletir. Bob da kusursuz şekilde şifreyi çözer, ama saldırgandan. Her iki taraf da yeşil bir asma kilit görür. Her iki taraf da yanılıyordur. Bu, klasik ortadaki adam anahtar değişimidir ve hiçbir miktarda AES-GCM bunu düzeltmez, çünkü konuşmanın her iki yarısı da gerçekten, doğru biçimde şifrelenmiştir. Sadece yanlış kişiye şifrelenmiştir.

2026.7 sürümünden itibaren sgcWebSockets bu açığı kapatıyor. Her uç nokta bir uzun ömürlü kimlik anahtarı tutabilir, geçici şifreleme anahtarını onunla imzalayabilir ve eş, ortak bir gizli anahtar türetmeden önce bu imzayı doğrular. Eşin parmak iziyle ne yapılacağına uygulamanız karar verir: ilk kullanımda kabul edip sabitlemek, bant dışı bir kanalda karşılaştırmak ya da hiç imzalanmamış olan her şeyi reddetmek. Varsayılan olarak kapalıdır ve siz açana kadar ağ üzerindeki biçim değişmez.

Şifreleme, kimlik doğrulama değildir

Bunlar iki farklı özelliktir ve hangisine sahip olduğunuz konusunda açık sözlü olmakta fayda var.

Şifreleme, "üçüncü bir taraf bunu okuyabilir mi?" sorusunu yanıtlar. ECDH ile AES-GCM bunu yanıtlar: hayır. Kimlik doğrulama, "şifrelediğim anahtar gerçekten konuştuğumu sandığım kişinin anahtarı mı?" sorusunu yanıtlar. Sade ECDH bunu hiç yanıtlamaz. Anahtarı kim gönderdiyse onunla seve seve bir ortak gizli anahtar üzerinde anlaşır ve o kişinin kim olduğu konusunda hiçbir görüşü yoktur.

Bu yazıdaki her şey ikinci soruyla ilgilidir. Buradaki hiçbir şey şifre paketini, anahtar türetmeyi veya mesaj biçimini değiştirmez. Geçici anahtarın üzerine bir imza ekler ve uygulamanızın arkasındaki eşe evet ya da hayır diyebileceği bir yer açar.

Uzun ömürlü kimlik anahtarları

Bir E2EE oturumunda artık iki tür anahtar vardır ve bunları birbirinden ayırmak işin özüdür.

Geçici şifreleme anahtarı, istemcinin zaten kullandığı ECDH anahtar çiftidir. Oturum boyunca var olur, ortak gizli anahtar ondan türetilir ve serbestçe yeniden üretilebilir. Kim olduğunuz hakkında hiçbir şey söylemez.

Kimlik anahtarı ise uzun ömürlü bir ECDSA P-256 anahtar çiftidir. Onu bir kez üretirsiniz, özel yarısını cihazda saklarsınız ve yeniden başlatmalar, yeniden bağlanmalar ve yeni oturumlar boyunca korursunuz. Tek görevi geçici anahtarı imzalamaktır: "az önce aldığın geçici açık anahtar gerçekten bu kimlik anahtarının sahibi tarafından yayımlandı". Eş, bu imzayı yanında gelen kimlik açık anahtarıyla doğrular ve hâlâ ikisini de görüp aktarabilen sunucu, kimlik özel anahtarına sahip olmadığı için imzayı taklit edemez.

Bu, ortadaki adam sorununu tek bir soruya indirger, yalnızca birine: bu kimlik açık anahtarı gerçekten eşime mi ait? Bu, uygulamanızın yanıtlayabileceği bir sorudur, çünkü rastgele bir geçici anahtarın aksine kimlik anahtarı kararlıdır, dolayısıyla hatırlanabilir, sabitlenebilir ve bir insan tarafından karşılaştırılabilir.

Kimlik anahtarı üretmek ve saklamak

sgcSSL_E2EE içindeki bir yardımcı, anahtar çiftini PEM dizeleri olarak oluşturur. Onu kurulum başına, kullanıcı başına bir kez çağırın ve özel anahtarı uygulamanız gizli bilgileri nerede saklıyorsa oraya koyun. Özel anahtarı asla hiçbir yere göndermeyin.

uses
  sgcSSL_E2EE;

var
  vPrivateKeyPEM, vPublicKeyPEM: string;
begin
  // ... generate ONCE, then persist. Regenerating it on every start
  // ... defeats the whole point: your peers would see a new identity every time.
  sgcE2EE_CreateIdentityKeyPair(vPrivateKeyPEM, vPublicKeyPEM);

  SaveIdentity(vPrivateKeyPEM, vPublicKeyPEM);  // your own secure storage
end;

Aynı anahtar çifti, bileşen üzerinde tutmayı tercih ederseniz, E2EE istemcisinin kendisinden GenerateIdentityKeyPair ile de üretilebilir:

var
  vPrivateKeyPEM, vPublicKeyPEM: string;
begin
  oE2EEClient.GenerateIdentityKeyPair(vPrivateKeyPEM, vPublicKeyPEM);
end;

Saklama biçimi sizin kararınızdır ve özen gösterilmeyi hak eden kısım da budur. Özel anahtar, sizin siz olduğunuzu kanıtlayan şeydir. Onu platformun anahtar deposuna, DPAPI ile korunan bir ikili nesneye, şifreli bir ayar dosyasına, tehdit modeliniz neresi diyorsa oraya koyun. Sızarsa, bir saldırgan o kimliğin yerine geçebilir ve kullanıcılarınızın karşılaştırdığı parmak izi yine de eşleşir.

Özelliği açmak

Kimlik imzalama, E2EE_Options.Identity altında yer alır. Saklanmış anahtar çiftini yükleyin, Enabled değerini ayarlayın; istemci o andan itibaren geçici anahtarını imzalar ve aldığı imzaları doğrular.

uses
  sgcWebSocket, sgcWebSocket_Protocols;

begin
  WSClient := TsgcWebSocketClient.Create(nil);
  WSClient.Host := '127.0.0.1';
  WSClient.Port := 80;

  E2EE := TsgcWSPClient_E2EE.Create(nil);
  E2EE.Client := WSClient;
  E2EE.E2EE_Options.UserId := 'client-1';

  // ... identity verification
  E2EE.E2EE_Options.Identity.Enabled    := True;
  E2EE.E2EE_Options.Identity.PrivateKey := LoadIdentityPrivateKey;  // PEM
  E2EE.E2EE_Options.Identity.PublicKey  := LoadIdentityPublicKey;   // PEM

  E2EE.OnE2EEVerifyPeerIdentity := OnE2EEVerifyPeerIdentityEvent;
  E2EE.OnE2EEKeyChange          := OnE2EEKeyChangeEvent;

  WSClient.Active := True;
end;

Bu noktadan sonra istemcinin yayımladığı geçici açık anahtara, kimlik açık anahtarı ve o geçici anahtarın üzerindeki imza eşlik eder. Bir eşin anahtarı geldiğinde, imza onunla birlikte gelen kimlik anahtarına karşı denetlenir. İmza doğrulanmazsa eşin anahtarı reddedilir ve ondan hiçbir ortak gizli anahtar türetilmez.

Geçerli bir imzanın neyi kanıtladığına, neyi kanıtlamadığına dikkat edin. Geçici anahtarın o kimlik özel anahtarının sahibi tarafından imzalandığını kanıtlar. Tek başına, o kimlik anahtarının eşinize ait olduğunu kanıtlamaz, çünkü ortadaki bir adam kendi kimlik anahtarını, kendi kusursuz geçerli imzasıyla sunabilir. O son adım için bir sonraki iki bölüm vardır.

Bir eşi doğrulamak: ilk kullanımda güven ve sabitleme

Bir imza doğrulandığında, OnE2EEVerifyPeerIdentity olayı eşin kullanıcı kimliği, kimlik açık anahtarı ve parmak iziyle tetiklenir. aAccept parametresi bir var parametresidir ve True olarak gelir, dolayısıyla hiçbir şey yapmazsanız eş kabul edilir. Karar kasıtlı olarak sizindir, çünkü bu kişiyi daha önce görüp görmediğini yalnızca uygulamanız bilir.

Standart kalıp, ilk kullanımda güven ve buna eklenen sabitlemedir. Bir kullanıcıyı ilk gördüğünüzde parmak izini kaydedin. Sonraki her seferde karşılaştırın. Eşleşiyorsa sessizce kabul edin. Eşleşmiyorsa kabul etmeyin ve kullanıcıya haber verin.

procedure TForm1.OnE2EEVerifyPeerIdentityEvent(Sender: TObject;
  const aUserId, aIdentityPublicKey, aFingerprint: string; var aAccept: Boolean);
var
  vPinned: string;
begin
  vPinned := GetPinnedFingerprint(aUserId);   // '' the first time we see this user

  if vPinned = '' then
  begin
    // ... trust on first use: remember it, and from now on it must not change
    SetPinnedFingerprint(aUserId, aFingerprint);
    aAccept := True;
    DoLog('pinned ' + aUserId + ': ' + aFingerprint);
  end
  else if SameText(vPinned, aFingerprint) then
  begin
    // ... same identity key as last time
    aAccept := True;
  end
  else
  begin
    // ... a different identity key for a user we already know. Refuse it and
    // ... let the user decide, do not silently trust it.
    aAccept := False;
    DoLog('REJECTED ' + aUserId + ': fingerprint mismatch');
  end;
end;

İlk kullanımda güvenin, dile getirmeye değer dürüst bir sınırı vardır: saldırgan tam da o ilk temasta zaten ortadaysa, sabitlediğiniz kişi saldırgan olur. Size kazandırdığı şey, saldırganın en baştan orada olması ve sonsuza kadar orada kalması zorunluluğudur; ayrıca anahtarı değiştirmeye yönelik sonraki her girişim gürültü çıkarır. İlk temas açığını da kapatmanız gerekiyorsa, parmak izini bant dışı bir kanalda karşılaştırırsınız, bu da bir sonraki bölümün konusu.

Kullanıcılarınızın gerçekten karşılaştırabileceği parmak izleri

Parmak izi, kimlik açık anahtarının sgcE2EE_IdentityFingerprint tarafından üretilen bir özetidir. Aynı kimlik anahtarını taşıyan iki uç nokta aynı parmak izini üretir ve farklı bir anahtar taşıyan ortadaki bir adam eşleşen bir parmak izi üretemez.

Bu, tam olarak Signal'in "güvenlik numarası" ile WhatsApp'ın "güvenlik kodu" arkasındaki mekanizmadır. Değeri, saldırganın denetlemediği bir kanal üzerinden bir insan tarafından karşılaştırılabilir olmasıdır. Alice telefonda Bob'a okur, ya da yüz yüze birbirlerine bir QR kodu gösterirler, ya da mevcut güvenilir bir sohbete yapıştırırlar. İki değer eşleşiyorsa arada kimse yoktur. Farklıysalar vardır.

Onu kullanıcılarınıza gerçekten sesli okuyabilecekleri bir biçimde verin. Özeti kısa bloklar hâlinde gruplamak yeterlidir:

uses
  sgcSSL_E2EE;

function FormatFingerprint(const aFingerprint: string): string;
var
  i: Integer;
begin
  // ... 'A1B2C3D4...' -> 'A1B2 C3D4 ...' so a human can read it over the phone
  Result := '';
  for i := 1 to Length(aFingerprint) do
  begin
    if (i > 1) and ((i - 1) mod 4 = 0) then
      Result := Result + ' ';
    Result := Result + aFingerprint[i];
  end;
end;

procedure TForm1.ShowMyFingerprint;
begin
  lblFingerprint.Caption :=
    FormatFingerprint(sgcE2EE_IdentityFingerprint(LoadIdentityPublicKey));
end;

Uygulamada kendi parmak izinizi gösterin, eşin parmak izini kişinin yanında gösterin ve kullanıcı ikisini karşılaştırdıktan sonra bir kişiyi doğrulanmış olarak işaretlemesine izin verin. Şifrelemenin, kullanıcılarınızın sandığı anlama gerçekten kavuştuğu nokta budur.

Bir eşin kimlik anahtarı değiştiğinde

OnE2EEKeyChange, bir kullanıcı kendisi için en son gördüğünüzden farklı bir kimlik anahtarı sunduğunda tetiklenir ve size hem eski hem de yeni parmak izini verir.

Bunu nasıl sunduğunuza dikkat edin. Değişen bir kimlik anahtarı otomatik olarak bir saldırı değildir. Uygulamayı yeniden kuran, bir cihazı sıfırlayan ya da yeni bir telefonda oturum açan bir kullanıcının meşru olarak yeni bir kimlik anahtarı olacaktır ve en yaygın sebep de tek başına budur. Ancak bu, bir anahtar değişiminin üreteceği sinyalin tam olarak aynısıdır, dolayısıyla kullanıcıya haber verilmesi gereken an ve uygulamanızın sabitlemeyi düşürüp yeni bir bant dışı karşılaştırma isteyebileceği andır.

procedure TForm1.OnE2EEKeyChangeEvent(Sender: TObject;
  const aUserId, aOldFingerprint, aNewFingerprint: string);
begin
  // ... "Your security code with <user> has changed."
  // ... Usually a reinstall or a new device. Sometimes not.
  DoLog(Format('identity key changed for %s: %s -> %s',
    [aUserId, aOldFingerprint, aNewFingerprint]));

  ClearPinnedFingerprint(aUserId);           // force a re-verification
  ShowSecurityCodeChangedWarning(aUserId, FormatFingerprint(aNewFingerprint));
end;

Bunu yukarıdaki işleyiciyle birlikte kullanın: OnE2EEKeyChange size anahtarın değiştiğini söyler, OnE2EEVerifyPeerIdentity ise konuşmaya devam edip etmeyeceğinize karar verdiğiniz yerdir.

RequireAuthentication ile güvensiz durumda reddetmek

Identity.Enabled ayarlanmış ve RequireAuthentication varsayılan değeri olan False olarak bırakılmışken istemci, elinden gelenin en iyisini yapma kipindedir. Geçerli bir imza sunan eşler doğrulanır. Hiç imza sunmayan eşler, eski bir istemci ya da kimliği etkinleştirmemiş bir istemci, yine de kabul edilir. Bu, her uç noktanın güncellenmediği bir yayılma döneminde kullanışlıdır, ancak bir güvenlik sınırı değildir: bir saldırgan imzayı basitçe çıkarıp eski bir istemci gibi görünebilir.

RequireAuthentication := True, güvensiz durumda reddetme anahtarıdır. İmzasız bir eş anahtarı ya da imzası doğrulanmayan bir anahtar, kabul edilmek yerine reddedilir.

// ... reject any peer that does not present a valid identity signature
E2EE.E2EE_Options.Identity.Enabled             := True;
E2EE.E2EE_Options.Identity.PrivateKey          := LoadIdentityPrivateKey;
E2EE.E2EE_Options.Identity.PublicKey           := LoadIdentityPublicKey;
E2EE.E2EE_Options.Identity.RequireAuthentication := True;

Bunu iki adımda yayın. Kimlik anahtarlarını RequireAuthentication kapalıyken dağıtın, tüm filonuz bunlara sahip olana kadar bekleyin, sonra açın. Bir kez açıldığında, imzayı kaldıran bir sürüm düşürme saldırısı artık işe yaramaz, çünkü eksik bir imza omuz silkme değil, bir reddetmedir.

Grup sohbetleri

Grup mesajlaşması da aynı muameleyi görür. Her üye kendi geçici anahtarını kendi kimlik anahtarıyla imzalar ve kimlik açık anahtarı ile imza her üyenin girdisiyle birlikte taşınır; böylece bir gruba katılmak, yalnızca sizi davet eden eşi değil, kendisine şifreleme yapacağınız her üyeyi doğrular. OnE2EEVerifyPeerIdentity üye başına tetiklenir, dolayısıyla birebir sohbetler için yazdığınız sabitleme mantığı olduğu gibi geçerlidir ve anahtarını reddettiğiniz bir üye, kendisiyle gizli anahtar türeteceğiniz bir üye olmaz. RequireAuthentication, bir grupta da doğrudan bir konuşmadaki gibi geçerlidir.

Varsayılan olarak geriye dönük uyumlu

Identity.Enabled kutudan False olarak çıkar. Kapalıyken hiçbir kimlik anahtarı gönderilmez, hiçbir imza üretilmez, hiçbiri beklenmez ve anahtar değişimi bayt bayt eskisiyle aynıdır. Kütüphaneyi yükseltmek, siz özelliği açıkça ayarlayana kadar mevcut bir E2EE dağıtımında hiçbir şeyi değiştirmez.

Kimlik alanları ağ üzerinde eklemelidir, dolayısıyla kimliği etkin bir istemci, RequireAuthentication kapalı olduğu sürece kimliksiz bir istemciyle hâlâ konuşur. Hepsini aynı anda güncellemediğiniz bir filoya bunu kademeli olarak dağıtmanızı sağlayan da budur.

Kullanılabilirlik

E2EE kimlik doğrulaması, sgcWebSockets 2026.7 sürümünde Delphi 7'den 13'e kadar olan sürümlerde ve C++Builder'da, Win32/Win64, Linux64, macOS, Android ve iOS üzerinde sunulur. E2EE protokolünün bir parçasıdır, Enterprise ve All-Access sürümlerinde bulunur ve hem birebir mesajlarda hem de grup sohbetlerinde çalışır.

Etkin aboneliği olan müşteriler yeni derlemeyi müşteri alanından indirebilir. Deneme kullanıcıları güncellenmiş yükleyiciyi esegece.com/products/websockets/download adresinden edinebilir.

Sorularınız, geri bildirimleriniz veya kimlik doğrulamasını uygulamanıza bağlama konusunda yardıma mı ihtiyacınız var? Bize ulaşın, yanıtı kodu yazan kişilerden alacaksınız.