OpenPGP in Delphi: versleutelen, ondertekenen en verifiëren zonder GnuPG

· Componenten
OpenPGP in Delphi: versleutelen, ondertekenen en verifiëren zonder GnuPG

OpenPGP is voor veel organisaties nog steeds de manier om bestanden uit te wisselen. Een bank wil betalingsbatches versleuteld met haar publieke sleutel, een partner stuurt rapporten versleuteld met GnuPG, een release moet met een losse handtekening worden uitgeleverd. Vanuit Delphi betekende dat meestal GnuPG op elke machine installeren en gpg.exe aanroepen via de opdrachtregel, met alle afhandeling van paden, sleutelringen en exitcodes die daarbij hoort.

sgcCrypto implementeert OpenPGP nu zelf, in pure Object Pascal. Er is geen DLL om uit te leveren, geen opdrachtregelprogramma om aan te roepen en geen afhankelijkheid van Indy. Sleutels, versleuteling, ontsleuteling, handtekeningen en verificatie zijn een handvol functieaanroepen op TBytes, en het resultaat werkt in beide richtingen samen met GnuPG.

Versie 4 en versie 6

Twee generaties van het formaat worden ondersteund:

Wanneer je naar meerdere ontvangers versleutelt, kiest de library het formaat dat elk van hen kan lezen: het AEAD-formaat van RFC 9580 wanneer ze het allemaal ondersteunen, anders het klassieke formaat met integriteitsbescherming. Een bericht aan een mix van oude en nieuwe sleutels opent overal.

Een sleutel genereren

Eén aanroep maakt een complete sleutel: een primaire ondertekeningssleutel, een versleutelingssubsleutel, de gebruikers-ID en de zelfhandtekeningen die ze aan elkaar binden.

uses
  sgcCrypto_PGP, sgcCrypto_PGP_Cert;

var
  oAlice: TsgcPGPKey;
begin
  // v4 Ed25519 + Curve25519, the default key type of GnuPG
  oAlice := sgcPGP_GenerateKey('Alice <alice@example.com>', 'secret',
    pgpkEd25519, 4);
  try
    Memo1.Text := oAlice.SaveToArmored(False);  // public key, share it
    SaveSecretKey(oAlice.SaveToArmored(True));  // protected secret key
  finally
    oAlice.Free;
  end;
end;

De sleuteltypen zijn RSA 2048, 3072 en 4096, Ed25519, Ed448 (versie 6) en ECDSA met ECDH op NIST P-256, P-384 en P-521. Brainpool-sleutels kunnen worden geïmporteerd. Geheime sleutels worden beschermd met je wachtwoordzin, met geïtereerde SHA-256 voor versie 4 en Argon2 met AES-OCB voor versie 6.

Versleutelen en ontsleutelen

Versleutel naar een of meer publieke sleutels, desgewenst ondertekend door de afzender. De uitvoer is standaard ASCII armored, klaar om aan een mail te hangen of naar een server te uploaden.

// encrypt to Bob, signed by Alice, compressed with ZIP
vEncrypted := sgcPGP_Encrypt(vData, [oBob], oAlice);

// or protect a file with a passphrase only
vEncrypted := sgcPGP_EncryptSymmetric(vData, 'shared passphrase');

Voor ontsleuteling is een sleutelring met je geheime sleutels nodig. Een vergrendelde sleutel wordt met de wachtwoordzin ontgrendeld op een privékopie, zodat de sleutelring zelf nooit verandert. Het resultaat vertelt voor wie het bericht bedoeld was, hoe het is versleuteld en of de handtekening erin geldig is.

var
  oRing: TsgcPGPKeyRing;
  vResult: TsgcPGPDecryptResult;
begin
  oRing := TsgcPGPKeyRing.Create;
  try
    oRing.LoadFromFile('bob-secret.asc');
    vData := sgcPGP_Decrypt(vEncrypted, oRing, 'bob passphrase', oContacts,
      vResult);
    if vResult.Verify.Valid then
      Log('Signed by ' + vResult.Verify.SignerUserID);
  finally
    oRing.Free;
  end;
end;

Ondertekenen en verifiëren

Losse handtekeningen voor bestanden en releases, inline ondertekende berichten en cleartext-handtekeningen voor tekst die leesbaar moet blijven worden allemaal ondersteund, met tekstmodus voor handtekeningen die onafhankelijk zijn van regeleinden.

// detached signature, the classic file.sig
vSignature := sgcPGP_Sign(vData, oAlice);
if sgcPGP_VerifyDetached(vData, vSignature, oContacts, vVerify) then
  Log('Valid signature from ' + vVerify.SignerUserID);

// cleartext signed text, readable without any tool
vSigned := sgcPGP_ClearSign('Payment batch 2026-10 approved', oAlice);

Werkt met GnuPG

Elke functie is in beide richtingen getest met GnuPG 2.4.5: bestanden en handtekeningen die door GnuPG zijn gemaakt, worden in Delphi ontsleuteld en geverifieerd, en alles wat de library produceert wordt geaccepteerd door gpg --decrypt en gpg --verify. Dat geldt ook voor de met OCB versleutelde berichten die GnuPG 2.4 standaard schrijft voor sleutels die het zelf heeft gegenereerd, en die veel OpenPGP-libraries niet kunnen lezen. De ondersteuning voor versie 6 is gecontroleerd aan de hand van de voorbeeldsleutels en berichten uit RFC 9580.

Sleutels gaan als armored tekst heen en weer tussen beide werelden. Exporteer een sleutel uit Delphi en voer gpg --import uit, of laad de uitvoer van gpg --export --armor in een TsgcPGPKeyRing.

Gebouwd voor onbetrouwbare invoer

Een toepassing die bestanden van buiten ontsleutelt, moet ervan uitgaan dat sommige daarvan kwaadaardig zijn. De library weigert het oude onbeschermde versleutelingsformaat, meldt elke ontsleutelingsfout met dezelfde fout zodat die niet als orakel kan dienen, verwerpt SHA-1-handtekeningen op documenten en heeft limieten voor de werkfactoren van wachtwoordzinnen en de gedecomprimeerde grootte, zodat een geprepareerd bericht de server niet kan vastzetten.

Wat is er nog meer nieuw

De OpenPGP-units compileren met Delphi 7 tot en met Delphi 13 en C++Builder, voor Win32 en Win64. DSA- en ElGamal-sleutels kunnen worden geladen maar niet gebruikt, en de oude cijfers CAST5, 3DES en IDEA worden niet ondersteund.

Lees verder

Vragen over een specifieke integratie, een bankformaat of een sleutel die je moet importeren? Neem contact op. Je krijgt antwoord van de mensen die de code hebben geschreven.