Net-Base Dergi

30.05.2026

AES şifreleme Delphi içinde: IV, Salt, Header ve Streaming içeren sağlam bir kaynak kodu örneği

Pratikte kullanılabilir bir Delphi kaynak kodu kesiti: AES şifrelemesi için rastgele salt ve IV, net bir dosya üstbilgi yapısı, PBKDF2 tabanlı anahtar türetimi ve akışlı (streaming) işleme — legacy formatlardaki tipik tuzaklar, bütünlük ve işletim konularını içerir.

30.05.2026

Dergi konusundan proje pratiğine

İçeriğe Uygun Hizmet ve Teknik Sayfalar

Uygulamada başarısızlık nadiren „AES’in kendisinden“ kaynaklanır; asıl sorun sınır koşullarındadır: veriler akış (stream) olarak işlenmelidir (dosyalar, BLOB’lar, yedekler), eski formatlar okunabilir kalmalı ve işletimde hata ayıklanabilirlik gerekir (başlık, sürümlendirme) ve güvenli varsayılanlar (Salt/IV rastgele, tekrar kullanım yok). Bu kaynak parçası bu nedenle sadece „Şifrele/Şifre Çöz“ göstermiyor, aynı zamanda başlık, sürüm, Salt ve IV içeren küçük, dayanıklı bir format — ayrıca anahtar türetimi için PBKDF2 ve bütünlüğün mantıklı şekilde eklenebileceği bir nokta.

Neden „AES ile string şifrelemek“ neredeyse hiç yeterli değildir

Özel kurumsal yazılımlarda şifreleme tipik olarak üç yerde karşımıza çıkar: (1) yapılandırma/gizli bilgiler (ör. erişim verileri), (2) değişim-/export dosyaları ve (3) durağan veriler (ör. arşivler, belge konteynerleri). Basit yaklaşım „parola → AES-anahtar → string içeri/dışarı“ hızla başarısız olur:

  • IV’nin tekrar kullanımı: CBC veya GCM gibi modlarda her şifreleme için bir başlatma vektörü (IV) benzersiz olmalıdır. Sabit bir IV bir sızıntıdır, parola güçlü olsa bile.
  • KDF olmadan paroladan anahtar: Parolayı doğrudan anahtar olarak kullanmak (veya tek seferlik hashlemek) çevrimdışı saldırılara davetiye verir. Bir KDF (Key Derivation Function) örneğin PBKDF2, saldırganları amaçlı olarak yavaşlatır.
  • Sürüm yok: Başlık/sürüm olmadan iterasyon sayısını, algoritmayı veya parametreleri sonradan değiştirdiğinizde eski veriler büyük olasılıkla kullanılamaz hale gelir.
  • Bütünlük yok: AES-CBC şifreler ama manipülasyonu engellemez. Kimlik doğrulama olmadan (ör. HMAC veya GCM gibi AEAD) bitflipping/padding sorunları ve zor teşhis edilen hata durumlarıyla karşılaşırsınız.

Bu yazının özü: akışı destekleyen, sürümlendirilebilir ve standart hataları önleyen küçük bir konteyner formatı.

Header, Salt, IV ve PBKDF2 ile AES şifreleme Delphi

Veritabanı-BLOB’larında veya mesaj yüklerinde de kullanılabilecek basit bir konteyner formatı tanımlıyoruz:

  • Magic: 4 Bytes, z. B. NBAE (schneller „Ist das unser Format?“-Check)
  • Sürüm: 1 Byte (geçişe olanak sağlar)
  • KDF-Parameter: İterasyon sayısı (4 Bytes)
  • Salt: 16 Bytes (dosya başına rastgele)
  • IV: 16 Bytes (AES-CBC için dosya başına rastgele)
  • Şifreli veri: şifrelenmiş kullanıcı verisi (streamingfähig)

Önemli: Salt ve IV gizli değildir. Her şifreleme için sadece yeni olmaları gerekir. Parola gizli kalır; buradan türetilen anahtar saklanmaz.

Akışta AES şifreleme Delphi: Konteyner yazma/okuma

Kod kasıtlı olarak bir „bina planı“ olarak yazılmıştır: net ayrılmış fonksiyonlar, doğrulanabilir başlıklar, gizli global değişken yok. AES ve PBKDF2 için birçok ekip kanıtlanmış bir kripto kütüphanesi kullanır (z. B. DEC). Bu kesit formatı ve streaming modelini gösterir; AES-/PBKDF2 çağrıları kütüphaneye bağlı olarak değiştirilebilecek şekilde kapsüllenmiştir.

unit Nb.AesContainer;

interface

uses
System.SysUtils, System.Classes, System.NetEncoding;

type
ENbCryptoError = class(Exception);

TNbAesContainer = class
public
class procedure EncryptStreamToStream(const AIn: TStream; const AOut: TStream;
const APassword: string; const AIterations: Cardinal = 200000);

class procedure DecryptStreamToStream(const AIn: TStream; const AOut: TStream;
const APassword: string);

class function EncryptBytesToBase64(const APlain: TBytes; const APassword: string): string;
class function DecryptBase64ToBytes(const ACipherB64: string; const APassword: string): TBytes;
end;

implementation

const
CMagic: array[0..3] of AnsiChar = (‚N‘,’B‘,’A‘,’E‘);
CVersion: Byte = 1;
CSaltLen = 16;
CIvLen = 16;

type
TNbHeaderV1 = packed record
Magic: array[0..3] of AnsiChar;
Version: Byte;
Iterations: Cardinal; // little endian
Salt: array[0..CSaltLen-1] of Byte;
IV: array[0..CIvLen-1] of Byte;
end;

// — Abhängigkeiten, die Sie je nach Crypto-Stack implementieren —

procedure FillRandomBytes(var B: TBytes);
begin
// Kriptografik rastgelelik için: OS-CSPRNG kullanın (Windows BCryptGenRandom,
// Linux getrandom/urandom). Burada kasıtlı olarak yer tutucu.
raise ENbCryptoError.Create(‚FillRandomBytes: CSPRNG bağlı değil‘);
end;

function PBKDF2_HMAC_SHA256(const APassword: string; const ASalt: TBytes;
const AIterations, AKeyLen: Cardinal): TBytes;
begin
// Örneğin DEC (PBKDF2) veya başka bir kütüphane ile uygulama.
// Sonuç: AKeyLen bayt.
raise ENbCryptoError.Create(‚PBKDF2_HMAC_SHA256: bağlı değil‘);
end;

procedure AES256_CBC_EncryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
// Kütüphane ile uygulama:
// – KeyLen = 32 bayt
// – IVLen = 16 bayt
// – PKCS#7 Padding
// Önemli: Akış odaklı işleyin, her şeyi belleğe yüklemeyin.
raise ENbCryptoError.Create(‚AES256_CBC_EncryptStream: bağlı değil‘);
end;

procedure AES256_CBC_DecryptStream(const AKey, AIV: TBytes; const AIn, AOut: TStream);
begin
raise ENbCryptoError.Create(‚AES256_CBC_DecryptStream: bağlı değil‘);
end;

// — Helper —

procedure WriteHeaderV1(const AOut: TStream; const H: TNbHeaderV1);
begin
if AOut.Write(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Başlık yazılamadı‘);
end;

function ReadHeaderV1(const AIn: TStream): TNbHeaderV1;
var
H: TNbHeaderV1;
begin
if AIn.Read(H, SizeOf(H)) <> SizeOf(H) then
raise ENbCryptoError.Create(‚Başlık eksik‘);

if (H.Magic[0] <> CMagic[0]) or (H.Magic[1] <> CMagic[1]) or
(H.Magic[2] <> CMagic[2]) or (H.Magic[3] <> CMagic[3]) then
raise ENbCryptoError.Create(‚Geçerli bir konteyner değil (Magic uyuşmuyor)‘);

if H.Version <> CVersion then
raise ENbCryptoError.CreateFmt(‚Bilinmeyen konteyner sürümü: %d‘, [H.Version]);

if (H.Iterations < 10000) or (H.Iterations > 5000000) then
raise ENbCryptoError.Create(‚İterasyon sayısı makul sınırların dışında‘);

Result := H;
end;

class procedure TNbAesContainer.EncryptStreamToStream(const AIn, AOut: TStream;
const APassword: string; const AIterations: Cardinal);
var
H: TNbHeaderV1;
Salt, IV, Key: TBytes;
begin
if APassword = “ then
raise ENbCryptoError.Create(‚Parola boş olmamalı‘);

// Salt/IV erzeugen
SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
FillRandomBytes(Salt);
FillRandomBytes(IV);

// Header befüllen
Move(CMagic[0], H.Magic[0], Length(CMagic));
H.Version := CVersion;
H.Iterations := AIterations;
Move(Salt[0], H.Salt[0], CSaltLen);
Move(IV[0], H.IV[0], CIvLen);

WriteHeaderV1(AOut, H);

// Key ableiten (32 Bytes für AES-256)
Key := PBKDF2_HMAC_SHA256(APassword, Salt, AIterations, 32);

// Nutzdaten verschlüsseln (Ciphertext folgt direkt nach Header)
AES256_CBC_EncryptStream(Key, IV, AIn, AOut);
end;

class procedure TNbAesContainer.DecryptStreamToStream(const AIn, AOut: TStream;
const APassword: string);
var
H: TNbHeaderV1;
Salt, IV, Key: TBytes;
begin
if APassword = “ then
raise ENbCryptoError.Create(‚Parola boş olmamalı‘);

H := ReadHeaderV1(AIn);

SetLength(Salt, CSaltLen);
SetLength(IV, CIvLen);
Move(H.Salt[0], Salt[0], CSaltLen);
Move(H.IV[0], IV[0], CIvLen);

Key := PBKDF2_HMAC_SHA256(APassword, Salt, H.Iterations, 32);

// Entschlüsseln ab aktueller Stream-Position (nach Header)
AES256_CBC_DecryptStream(Key, IV, AIn, AOut);
end;

class function TNbAesContainer.EncryptBytesToBase64(const APlain: TBytes;
const APassword: string): string;
var
InS, OutS: TBytesStream;
begin
InS := TBytesStream.Create(APlain);
try
OutS := TBytesStream.Create;
try
EncryptStreamToStream(InS, OutS, APassword);
Result := TNetEncoding.Base64.EncodeBytesToString(OutS.Bytes, 0, OutS.Size);
finally
OutS.Free;
end;
finally
InS.Free;
end;
end;

class function TNbAesContainer.DecryptBase64ToBytes(const ACipherB64,
APassword: string): TBytes;
var
Cipher: TBytes;
InS, OutS: TBytesStream;
begin
Cipher := TNetEncoding.Base64.DecodeStringToBytes(ACipherB64);
InS := TBytesStream.Create(Cipher);
try
OutS := TBytesStream.Create;
try
DecryptStreamToStream(InS, OutS, APassword);
Result := OutS.Bytes;
SetLength(Result, OutS.Size);
finally
OutS.Free;
end;
finally
InS.Free;
end;
end;

end.

Amaç: Dosyalar ve BLOB’lar için uygun, sürümleme ve KDF parametrelerini içeren minimal bir konteyner. Kısıtlar: Altında gerçek bir CSPRNG bağlantısı (işletim sisteminden kriptografik olarak güvenli rastgele) ve sağlam bir AES/PBKDF2 uygulaması kullanmanız gerekir. Tuzaklar: „Herhangi bir“ Random kullanmayın ( Random() yok), sabit IV’ler kullanmayın ve decrypt sırasında açık ayrım yapabilen hata işleme planlayın (yanlış parola vs. bozuk veri). Varyantlar: CBC yerine tercih olarak AEAD (aşağıya bakın) veya header’ı algoritma-ID’si ve HMAC ile genişletme.

Bütünlük: AES-CBC’nin işletmede tek başına neden riskli olduğu

AES-CBC birçok legacy bağlamda hâlâ mevcut ve ek olarak bir bütünlük kontrolü kullanıldığında çalışabilir. Bütünlük olmadan bir saldırgan ciphertext’i manipüle edebilir; aktif bir saldırgan olmasa bile taşıma hataları veya bozuk depolama katmanları, teşhisi zor „padding“ hatalarına yol açabilir.

Pragmatik seçenekler:

  • Encrypt-then-HMAC: Ciphertext’in arkasına bir HMAC (ör. HMAC-SHA-256) yazın; HMAC, header+Ciphertext üzerinde hesaplanır. Okumada önce HMAC doğrulanır, sonra deşifre edilir. Bunun için ideal olan, PBKDF2’den iki anahtar türetmektir (ör. 64 Bayt: AES için 32, HMAC için 32) ve aynı anahtarı iki amaç için tekrar kullanmamaktır.
  • AES-GCM: AEAD modu (Authenticated Encryption with Associated Data). Ciphertext + Auth-Tag sağlar. Eğer Delphi kütüphaneniz GCM’i stabil olarak destekliyorsa bu günümüzde çoğu durumda en temiz seçimdir. Header alanları AAD olarak doğrulanabilir; bunları şifrelemenize gerek yoktur.

Eğer CBC’de kalmanız gerekiyorsa (ör. interop nedeniyle), Encrypt-then-HMAC sağlam bir tamamlayıcıdır. Yeni formatlar için GCM tercih etmek faydalıdır; böylece doğrulama ile birlikte gelir ve hata durumları daha net olur.

Olağandışı önemli: „Kriptografik Rastgelelik“ ve neden System.Hash yetmez

Delphi projelerinde sık görülen bir reflex: „Zaman damgası + bir şeylerin SHA256’sını alırız, rastgele olur.“ Bu güvenilir bir temel değildir. Salt ve IV için işletim sisteminin bir CSPRNG’sine (Cryptographically Secure Pseudo Random Number Generator) ihtiyacınız vardır. Windows altında bu tipik olarak BCrypt-API’sidir (CNG), Linux altında ise kernel tarafı üreteci örn. getrandom() veya /dev/urandom’dır. Fark pratikte şudur: Bir CSPRNG, gözlemlenen değerlerden sonraki değerlerin tahmin edilemeyeceği şekilde tasarlanmıştır.

Mimari ipucu: Bunu küçük bir „RandomProvider“ biriminde kapsülleyin ve testlerde mock’layın. Böylece iki uç vakayı çözersiniz: tekrar üretilebilir testler (mock içinde sabit seed) ve üretimde gerçek güvenlik (OS-CSPRNG). Bu, bir hotfix’te „çabuk olsun“ diye tekrar Random()’ın sahneye girmesini engeller.

Hata ayıklama ve eski sistem geçişi: Sürümleme lüks değil

Header sadece „kriptografik güzellik“ için değildir; bakım için gereklidir:

  • İterasyon ayarlamaları: PBKDF2 iterasyon sayıları yıllar içinde değişir. Header alanı sayesinde daha sonra iterasyonu yükseltebilirsiniz, eski verileri okunamaz hale getirmeden.
  • Format değişiklikleri: Versiyon 2 örneğin AES-GCM’e geçiş yapabilir veya bir HMAC ekleyebilir.
  • Saha teşhisi: Magic/Version loglarda ve araçlarda hızlı kontroller sağlar; veriyi deşifre etmeden durum değerlendirmesi yapılabilir.

Pratik ipucu: Sadece başlığı (Magic/Version/Iterations) okuyan ve bir loga yazan küçük bir „Inspector“ uygulayın. Böylece birçok destek vakasını („Burada hangi sürüm var?“) parola yönetimi olmadan çözersiniz.

Temiz migrieren: „Read old, write new“ statt Big Bang

Eski bir formatı değiştirecekseniz (ör. sabit IV, KDF yok, Blowfish/3DES veya kendi yapılmış XOR), Delphi projelerinde şu yaklaşım işe yaradı: Beim Lesen birden fazla formatı tespit edin (Magic/Version veya bir fallback-heuristik), beim Schreiben ise yalnızca yeni formatı üretin. Ayrıca başarılı bir şifre çözme sonrası arka planda yeniden şifreleme (re-encrypten, „lazy migration“) yapabilirsiniz; süreç uygunsa bu işe yarar. Bu şekilde rollout riskini azaltır ve bakım penceresi olarak „her şeyi bir kerede yeniden şifreleme“ gereksiniminden kaçınırsınız.

Threading und Streaming: typische Kanten in Delphi

Şifreleme genellikle worker thread’lerde çalışır (ör. dışa aktarma sırasında, bir müşteri portalı’na yükleme sırasında, büyük arşivler yazılırken). Delphi projelerinde düzenli olarak göze çarpan iki nokta:

  • Stream pozisyonları: Şifrelemeden/şifre çözmeden önce net kontratlar belirleyin: Girdi akışı (Input-Stream) mevcut pozisyondan okunur, çıktı akışı (Output-Stream) mevcut pozisyondan yazılır. Stream’leri yeniden kullanırken mutlaka Position := 0 olarak bilinçli bir şekilde ayarlayın.
  • Bellek zirveleri: „Her şeyi TBytes’ta tutma“dan kaçının. Stream yaklaşımı özellikle büyük dosyalar için önemlidir. Kripto kütüphaneniz yalnızca byte dizilerini kabul ediyorsa, stream destekli bir uygulamaya geçmek veya tamponlu bir adaptör yazmak için ek çalışma yapmaya değer.

Servislerde ( Windows- veya Linux-Services) şifreleme yapıyorsanız, temiz exception-logging’e dikkat edin: „yanlış parola“, „header bozuk“, „Tag/HMAC geçersiz“ farklı işletim durumlarıdır ve ayırt edilebilir olmalıdır. Önemli: Hata mesajları dışarıya çok ayrıntılı olmamalıdır (API hatası olarak „Padding blok 7’de yanlış“ gibi), fakat dahili loglarda ayrıntı verilebilir.

Yaklaşım ne zaman işe yarar – ve nerede ters tepebilir

İşe yarar, eğer siz: (a) şifrelenmiş dışa/içe aktarma verilerini uzun süreli saklıyorsanız, (b) farklı program sürümlerini paralel çalıştırıyorsanız, (c) verileri akışlar (streams) halinde işliyorsanız veya (d) birden fazla modül (Client/Server/Tooling) için temiz bir kripto arayüzüne ihtiyaç duyuyorsanız.

Ters teper, eğer bununla „her şeyi“ çözmeye çalışıyorsanız: Transport için TLS sorumludur, kendi yaptığınız bir AES-wrapper değil. Secrets (parolalar, tokenlar) için genellikle bir OS-Secret-Store veya bir Vault daha uygundur. Ve başka dillerle interop gerekiyorsa, header, endianness ve encoding’i tam olarak belgelemelisiniz (veya yerleşik bir format kullanmalısınız).

Fazit: Delphi’de AES daha az algoritma, daha çok mühendislik işidir

Bu parçadan asıl kazanç „AES çalışıyor“ değil, bir işletilebilir format‚tır: rastgele Salt ve IV, sürümlü header, payload içinde PBKDF2 parametreleri ve stream destekli işleme. Yeni formatlar için mümkünse bütünlüğü (AES-GCM veya Encrypt-then-HMAC) ekleyin. Böylece „bir şeyler şifreliyoruz“ durumundan, dijital kurumsal çözümlerde yıllar sonra bile bakım ve migrasyon yapılabilir bir yapı taşı ortaya çıkar.

Eğer böyle bir konteyneri yerleşik bir Delphi ortamına entegre etmeniz veya bir legacy formatından düzgün bir göç gerçekleştirmeniz gerekiyorsa, kısa bir mimari kontrolü (anahtar yönetimi, format sürümleri, işletim/günlükleme) yapılması uygundur. Detayları ihtiyaç halinde görüşerek netleştiririz:

Teknik bağlamda, entegrasyonlar, veri akışları ve devam eden geliştirme birlikte güvenilir biçimde çalışması gerektiğinde Delphi Aes ve Pbkdf2 Delphi de önemli bir rol oynar.

Proje veya modernizasyon girişimini Net-Base ile görüşün.

Sonraki adım

Konu gerçek bir projeye dönüştüğünde, mimari, mevcut yapı ve işletme erken aşamada birlikte ele alınmalıdır.

Bireysel sorularda destek vermekle kalmıyoruz; kaynak kodu parçacıklarından, legacy konularından veya portal fikirlerinden sağlam bir kurumsal projeye dönüşene kadar da destek veriyoruz.

  • Mevcut durum, hedef durum ve teknik riskler birlikte değerlendirilir.
  • REST, veri erişimi, portallar ve Rollout sonraki işler olarak ertelenmez.
  • Hangi yolun ekonomik ve işletme açısından uygulanabilir olduğunu erken görürsünüz.

Gönderiyi paylaş

Bu gönderiyi doğrudan paylaş

LinkedIn, X, XING, Facebook, WhatsApp ve e-posta hemen kullanılabilir. Instagram için bağlantı ve kısa metni doğrudan hazırlıyoruz.

E-posta

Instagram yeni bir sekmede açılır. Bağlantı ve kısa metin önceden panoya kopyalanır.