OpenPGP

OpenPGP is de standaard achter PGP en GnuPG. Het versleutelt bestanden en berichten voor een of meer personen, ondertekent ze zodat de ontvanger kan controleren wie ze schreef en dat er niets is gewijzigd, en doet beide zonder certificeringsinstantie: elke gebruiker heeft een sleutelpaar en publiceert de publieke helft. Het is het formaat voor versleutelde e-mail, ondertekende softwarereleases, pakketrepositories en bestandsuitwisseling met partners en banken.

De sgcCrypto-units implementeren OpenPGP in pure Object Pascal, zonder externe DLL, zonder OpenSSL en zonder Indy. Ze compileren van Delphi 7 tot en met Delphi 13 op Win32 en Win64, en de units compileren ook voor Linux64. Alles werkt in het geheugen op TBytes: gepantserde (armored) uitvoer is ASCII-tekst die als TBytes wordt teruggegeven, en de hulpfuncties sgcPGP_UTF8 en sgcPGP_UTF8ToString zetten om tussen strings en bytes.

Versie 4 of versie 6

Er worden twee versies van het formaat ondersteund, en beide worden gelezen en geschreven.

Versie 4 (RFC 4880 en RFC 6637) is wat GnuPG, Kleopatra, Thunderbird en bijna elk ingezet hulpmiddel tegenwoordig gebruiken. Kies deze versie wanneer de andere kant GnuPG draait of u hun software niet beheert. Berichten worden beschermd met SEIPD v1 (AES in CFB-modus plus een MDC-integriteitscontrole).

Versie 6 (RFC 9580) is de huidige herziening van de standaard. Sleutels gebruiken native Ed25519/X25519 of Ed448/X448, geheime sleutels worden beschermd met Argon2, en berichten gebruiken SEIPD v2, een echte AEAD-constructie (standaard OCB). Kies deze versie wanneer alle partijen RFC 9580 ondersteunen, bijvoorbeeld tussen twee toepassingen die u zelf bouwt. GnuPG 2.4 leest geen v6-sleutels.

U hoeft niet per bericht te kiezen. Bij het versleutelen wordt SEIPD v2 alleen gebruikt als elke ontvanger het ondersteunt (v6-sleutels). Heeft een ontvanger een v4-sleutel, dan wordt het bericht als SEIPD v1 geschreven, zodat een gemengde ontvangerslijst leesbaar blijft voor GnuPG.

Ondersteunde functies

Gebied Ondersteund
Formaten OpenPGP v4 (RFC 4880, RFC 6637, compatibel met GnuPG) en v6 (RFC 9580)
Public key-algoritmen RSA 2048/3072/4096, Ed25519, Ed448 (alleen v6), X25519, X448, ECDSA en ECDH op NIST P-256/P-384/P-521, ECDSA en ECDH op Brainpool-curven (alleen import)
Symmetrische cijfers AES-128, AES-192, AES-256
Versleutelde gegevens SEIPD v1 (CFB plus MDC), SEIPD v2 AEAD met OCB, EAX of GCM
Sessiesleutels Public key (PKESK v3 en v6) en wachtwoordzin (SKESK v4 en v6)
Sleutelbescherming (S2K) Iterated and salted S2K, Argon2 S2K
Compressie ZIP (raw DEFLATE) en ZLIB
Handtekeningen Detached, inline (one pass) en cleartext handtekeningen, binaire en tekstmodus
Codering ASCII armor en binair
GnuPG 2.4 LibrePGP Leest OCB-versleutelde berichten (pakket 20) en v5 SKESK-pakketten, schrijft ze nooit

Units

Unit Inhoud
sgcCrypto_PGP API op hoog niveau: versleutelen, ontsleutelen, ondertekenen, verifiëren, resultaatrecords
sgcCrypto_PGP_Cert Overdraagbare sleutels, sleutelringen en sleutelgeneratie: TsgcPGPKey, TsgcPGPKeyRing, sgcPGP_GenerateKey
sgcCrypto_PGP_Message Berichtpakketten: sessiesleutels, versleutelde gegevens, literal en gecomprimeerde gegevens
sgcCrypto_PGP_Signature Handtekeningpakketten, aanmaken en verifiëren
sgcCrypto_PGP_Keys Sleutelpakketten en bescherming van geheime sleutels
sgcCrypto_PGP_Packets Pakketopbouw, ASCII armor, S2K, constanten en hulpfuncties
sgcCrypto_Modes AES-OCB en AES-EAX, gebruikt door SEIPD v2 (zie Symmetrische versleuteling)
sgcCrypto_Deflate DEFLATE- en ZLIB-compressie (zie WinZip AE-2-archiefversleuteling)

De meeste toepassingen hebben alleen sgcCrypto_PGP, sgcCrypto_PGP_Cert en sgcCrypto_PGP_Packets nodig in de uses-clausule. De units maken deel uit van het Cryptography-pakket en zijn uitgesloten van FIPS-builds (SGC_CRYPTO_FIPS, zie FIPS-modus).

Sleutelbeheer

sgcPGP_GenerateKey maakt een primaire ondertekeningssleutel plus een versleutelingssubsleutel, koppelt de gebruikers-ID met een zelfhandtekening en geeft een TsgcPGPKey terug. De wachtwoordzin beschermt de geheime sleutel wanneer die wordt opgeslagen; het teruggegeven object is al ontgrendeld en kan direct ondertekenen en ontsleutelen. Een lege wachtwoordzin slaat de geheime sleutel onbeschermd op. aExpirationSeconds stelt een levensduur van de sleutel in, geteld vanaf het aanmaken, 0 betekent dat de sleutel nooit verloopt.

TsgcPGPKeyType Versie 4 Versie 6
pgpkEd25519EdDSA Ed25519 + ECDH Curve25519Ed25519 + X25519
pgpkEd448niet beschikbaarEd448 + X448
pgpkRSA2048RSA 2048niet beschikbaar (het minimum van RFC 9580 is 3072)
pgpkRSA3072RSA 3072RSA 3072
pgpkRSA4096RSA 4096RSA 4096
pgpkNISTP256ECDSA + ECDH P-256ECDSA + ECDH P-256
pgpkNISTP384ECDSA + ECDH P-384ECDSA + ECDH P-384
pgpkNISTP521ECDSA + ECDH P-521ECDSA + ECDH P-521


function sgcPGP_GenerateKey(const aUserID, aPassphrase: string;
  aKeyType: TsgcPGPKeyType = pgpkEd25519; aVersion: Integer = 4;
  aExpirationSeconds: Cardinal = 0): TsgcPGPKey;

var
  oAlice: TsgcPGPKey;
  vPublic, vPrivate: string;
begin
  oAlice := sgcPGP_GenerateKey('Alice <alice@example.com>', 'alice passphrase',
    pgpkEd25519, 4);
  try
    vPublic := oAlice.SaveToArmored(False);  { share this one }
    vPrivate := oAlice.SaveToArmored(True);  { keep this one, protected by the passphrase }
    WriteLn(oAlice.FingerprintHex + ' ' + oAlice.PrimaryUserID);
  finally
    oAlice.Free;
  end;
end;

Een sleutel die uit een bestand wordt geladen, is vergrendeld als hij met een wachtwoordzin is opgeslagen. Roep Unlock aan voordat u hem gebruikt om te ondertekenen; de methode geeft False terug als de wachtwoordzin onjuist is. Protect versleutelt het geheime materiaal opnieuw met een nieuwe wachtwoordzin, zo wijzigt u die. v4-sleutels worden beschermd met AES-256 en de iterated and salted S2K; v6-sleutels met AES-256 in OCB-modus en een Argon2 S2K.


var
  oKey: TsgcPGPKey;
begin
  oKey := TsgcPGPKey.Create;
  try
    oKey.LoadFromBytes(sgcPGP_UTF8(vPrivate));  { armored or binary }
    if not oKey.Unlock('alice passphrase') then
      raise Exception.Create('wrong passphrase');
    oKey.Protect('new passphrase');
    vPrivate := oKey.SaveToArmored(True);
  finally
    oKey.Free;
  end;
end;

Een TsgcPGPKeyRing bevat een willekeurig aantal sleutels en is er eigenaar van: een sleutel die aan Add wordt doorgegeven, wordt samen met de ring vrijgegeven, dus geef hem zelf niet vrij. LoadFromFile en LoadFromBytes accepteren gepantserde of binaire gegevens met een of veel sleutels (een GnuPG-export van een volledige sleutelring werkt) en voegen toe aan wat al is geladen. Zoekfuncties:

Elke sleutel biedt Version, FingerprintHex, KeyIDHex, PrimaryUserID, HasSecret, IsValid, IsRevoked, IsExpired en ExpirationTime, plus zijn gebruikers-ID's en subsleutels. Zelfhandtekeningen, subsleutelkoppelingen, verloopdatum en intrekking worden bij het laden gecontroleerd, en een ingetrokken of verlopen sleutel wordt nooit gekozen voor versleuteling of ondertekening.

Versleutelen en ontsleutelen

sgcPGP_Encrypt versleutelt voor een of meer publieke sleutels. Wanneer u een ondertekenaar opgeeft, worden de gegevens binnen het versleutelde bericht ondertekend, wat gpg --sign --encrypt produceert. De ondertekenaar moet ontgrendeld zijn. De gegevens worden standaard met ZIP gecomprimeerd; geef CSGC_PGP_COMP_ZLIB of CSGC_PGP_COMP_NONE op om dat te wijzigen. AES-256 wordt gebruikt wanneer elke ontvanger het in zijn voorkeuren vermeldt (altijd voor SEIPD v2), anders AES-128.


function sgcPGP_Encrypt(const aData: TBytes;
  const aRecipients: array of TsgcPGPKey; aSigner: TsgcPGPKey = nil;
  aArmor: Boolean = True; aCompression: Integer = CSGC_PGP_COMP_ZIP;
  const aFileName: string = ''): TBytes;

var
  oPublicRing: TsgcPGPKeyRing;
  oBob: TsgcPGPKey;
  oEncrypted: TBytes;
begin
  oPublicRing := TsgcPGPKeyRing.Create;
  try
    oPublicRing.LoadFromFile('public-keys.asc');
    oBob := oPublicRing.FindByUserID('bob@example.com');
    if oBob = nil then
      raise Exception.Create('Bob is not in the keyring');
    { oAlice is an unlocked secret key, used as signer }
    oEncrypted := sgcPGP_Encrypt(sgcPGP_UTF8('Hello Bob'), [oBob], oAlice);
    WriteLn(sgcPGP_UTF8ToString(oEncrypted));  { -----BEGIN PGP MESSAGE----- }
  finally
    oPublicRing.Free;
  end;
end;

Gebruik sgcPGP_EncryptSymmetric voor een gedeeld wachtwoord in plaats van sleutels. Met aVersion = 4 schrijft het een v4 SKESK en SEIPD v1, die gpg --decrypt leest. Met 6 schrijft het een v6 SKESK met Argon2 en SEIPD v2 met AES-256 OCB.


function sgcPGP_EncryptSymmetric(const aData: TBytes; const aPassphrase: string;
  aArmor: Boolean = True; aVersion: Integer = 4;
  aCompression: Integer = CSGC_PGP_COMP_ZIP;
  const aFileName: string = ''): TBytes;

  oEncrypted := sgcPGP_EncryptSymmetric(sgcPGP_UTF8('Hello'), 'shared passphrase');

sgcPGP_Decrypt verwerkt beide soorten, gepantserd of binair. aSecretKeys bevat de geheime sleutels die worden geprobeerd; aPassphrase ontgrendelt ze en wordt ook als berichtwachtwoord geprobeerd. Een vergrendelde sleutel wordt ontgrendeld op een privékopie, zodat de doorgegeven sleutelring nooit wordt gewijzigd. aVerifyKeys bevat de publieke sleutels waarmee een ingesloten handtekening wordt gecontroleerd en mag nil zijn. Een bericht dat niet kan worden ontsleuteld, veroorzaakt EsgcPGPError.


function sgcPGP_Decrypt(const aData: TBytes; aSecretKeys: TsgcPGPKeyRing;
  const aPassphrase: string; aVerifyKeys: TsgcPGPKeyRing;
  out aResult: TsgcPGPDecryptResult): TBytes;

var
  oSecretRing: TsgcPGPKeyRing;
  oResult: TsgcPGPDecryptResult;
  oData: TBytes;
begin
  oSecretRing := TsgcPGPKeyRing.Create;
  try
    oSecretRing.LoadFromFile('bob-secret.asc');
    try
      oData := sgcPGP_Decrypt(oEncrypted, oSecretRing, 'bob passphrase',
        oPublicRing, oResult);
      WriteLn(sgcPGP_UTF8ToString(oData));
      if oResult.Verify.Valid then
        WriteLn('signed by ' + oResult.Verify.SignerUserID)
      else if oResult.Verify.Signed then
        WriteLn('signature not valid: ' + oResult.Verify.Status);
    except
      on E: EsgcPGPError do
        WriteLn('cannot decrypt: ' + E.Message);
    end;
  finally
    oSecretRing.Free;
  end;
end;

Het record TsgcPGPDecryptResult beschrijft wat is ontsleuteld:

Veld Betekenis
FileNameBestandsnaam in het literal data-pakket, kan leeg zijn
DateDatum van de literal data, Unix-tijd
FormatFormaatbyte van de literal data: 'b' binair, 't' of 'u' tekst
EncryptedTrue wanneer de invoer versleuteld was, False voor een alleen ondertekend bericht
SymAlgoID van het symmetrische algoritme (7, 8, 9 voor AES-128, AES-192, AES-256)
AEADAlgoID van de AEAD-modus (1 EAX, 2 OCB, 3 GCM), 0 voor SEIPD v1
RecipientKeyIDSleutel-ID van de geheime sleutel die het bericht ontsleutelde, leeg bij een wachtwoordzin
VerifyEen TsgcPGPVerifyResult voor de ingesloten handtekening, zie hieronder

Ondertekenen en verifiëren

Drie soorten handtekeningen dekken de gebruikelijke gevallen:

De ondertekenaar moet ontgrendeld zijn. aTextMode ondertekent de tekst met canonieke CRLF-regeleinden, zodat de handtekening nog steeds klopt nadat het bestand tussen Windows en Linux is verplaatst.


function sgcPGP_Sign(const aData: TBytes; aSigner: TsgcPGPKey;
  aDetached: Boolean = True; aArmor: Boolean = True; aTextMode: Boolean = False;
  const aFileName: string = ''): TBytes;
function sgcPGP_ClearSign(const aText: string; aSigner: TsgcPGPKey): string;
function sgcPGP_Verify(const aSignedMessage: TBytes; aKeys: TsgcPGPKeyRing;
  out aData: TBytes; out aResult: TsgcPGPVerifyResult): Boolean;
function sgcPGP_VerifyDetached(const aData, aSignature: TBytes;
  aKeys: TsgcPGPKeyRing; out aResult: TsgcPGPVerifyResult): Boolean;

var
  oData, oSignature, oSigned, oOut: TBytes;
  oVerify: TsgcPGPVerifyResult;
  vClear: string;
begin
  oData := sgcPGP_UTF8('release 1.0');

  { detached }
  oSignature := sgcPGP_Sign(oData, oAlice);
  if sgcPGP_VerifyDetached(oData, oSignature, oPublicRing, oVerify) then
    WriteLn('valid, signed by ' + oVerify.SignerUserID);

  { inline }
  oSigned := sgcPGP_Sign(oData, oAlice, False);
  if sgcPGP_Verify(oSigned, oPublicRing, oOut, oVerify) then
    WriteLn(sgcPGP_UTF8ToString(oOut));

  { cleartext }
  vClear := sgcPGP_ClearSign('Hello', oAlice);
  if sgcPGP_Verify(sgcPGP_UTF8(vClear), oPublicRing, oOut, oVerify) then
    WriteLn('valid cleartext signature');
end;

Het record TsgcPGPVerifyResult rapporteert over de eerste geldige handtekening die is gevonden, of over de eerste handtekening wanneer er geen geldige is:

Veld Betekenis
SignedTrue wanneer het bericht minstens een handtekening bevat
ValidTrue wanneer een handtekening is geverifieerd met een geldige sleutel uit de sleutelring
SignerKeyID, SignerFingerprintHex-sleutel-ID en vingerafdruk van de sleutel (of subsleutel) die ondertekende
SignerUserIDPrimaire gebruikers-ID van de ondertekenaar
SignatureTimeAanmaaktijd van de handtekening, Unix-tijd
HashAlgoID van het hashalgoritme, sgcPGP_HashName geeft de naam
StatusLeeg wanneer geldig, anders de reden: not signed, signer key not found, bad signature, signer key not valid at signature time, signature expired, malformed signature, unexpected signature type

Een handtekening van een sleutel die niet in de sleutelring staat, is nooit geldig: Status is signer key not found en de velden van de ondertekenaar blijven leeg. Vertrouw alleen op Valid, en alleen voor sleutels die u zelf in de sleutelring hebt gezet.

Interoperabiliteit met GnuPG

Gebruik v4-sleutels om gegevens met GnuPG uit te wisselen. Gepantserde bestanden gaan ongewijzigd in beide richtingen. Typische opdrachten aan de GnuPG-kant:


gpg --import alice-public.asc
gpg --armor --export bob@example.com > bob-public.asc
gpg --armor --export-secret-keys bob@example.com > bob-secret.asc

gpg --decrypt message.asc
gpg --armor --sign --encrypt --recipient alice@example.com file.txt
gpg --armor --symmetric file.txt

gpg --verify file.txt.asc file.txt
gpg --verify clearsigned.asc
gpg --armor --detach-sign file.txt
gpg --clearsign note.txt

Door GnuPG geëxporteerde sleutels laden met TsgcPGPKeyRing.LoadFromFile, inclusief geheime sleutels die met een wachtwoordzin zijn beschermd. GnuPG 2.4 markeert zijn sleutels met de LibrePGP OCB-functie en kan er met pakket 20 (OCB-versleutelde gegevens) of v5 SKESK-pakketten naar versleutelen. Die berichten worden gelezen en ontsleuteld, maar nooit geschreven: gegevens die hier voor een v4-sleutel worden versleuteld, gebruiken altijd SEIPD v1, dat elke GnuPG-versie leest. Geheime sleutels die naar een smartcard zijn verplaatst, verschijnen in een GnuPG-export als stubs zonder geheim materiaal; ze laden, maar kunnen niet ondertekenen of ontsleutelen.

Beveiligingsopmerkingen

Beperkingen

API-referentie

Naam Unit Doel
sgcPGP_EncryptsgcCrypto_PGPVersleutelen voor publieke sleutels, optioneel ondertekend
sgcPGP_EncryptSymmetricsgcCrypto_PGPVersleutelen met een wachtwoordzin, v4 of v6
sgcPGP_DecryptsgcCrypto_PGPOntsleutelen en een ingesloten handtekening verifiëren
sgcPGP_SignsgcCrypto_PGPDetached of inline handtekening
sgcPGP_ClearSignsgcCrypto_PGPCleartext handtekening
sgcPGP_VerifysgcCrypto_PGPEen inline of cleartext ondertekend bericht verifiëren
sgcPGP_VerifyDetachedsgcCrypto_PGPEen detached handtekening verifiëren
TsgcPGPDecryptResult, TsgcPGPVerifyResultsgcCrypto_PGPResultaatrecords
sgcPGPMaxDecompressedSizesgcCrypto_PGPDecompressielimiet, 0 is onbeperkt
sgcPGP_GenerateKeysgcCrypto_PGP_CertEen v4- of v6-sleutel genereren
TsgcPGPKeysgcCrypto_PGP_CertEen sleutel met zijn gebruikers-ID's en subsleutels: LoadFromBytes, SaveToBytes, SaveToArmored, Unlock, Protect
TsgcPGPKeyRingsgcCrypto_PGP_CertLijst van sleutels waarvan de ring eigenaar is: Add, Remove, Clear, LoadFromBytes, LoadFromFile, SaveToArmored, FindByID, FindByUserID, FindKeyPacket
TsgcPGPKeyTypesgcCrypto_PGP_CertSleutelalgoritme voor generatie
sgcPGP_Armor, sgcPGP_Dearmor, sgcPGP_IsArmoredsgcCrypto_PGP_PacketsASCII armor coderen en decoderen
sgcPGP_UTF8, sgcPGP_UTF8ToStringsgcCrypto_PGP_PacketsString naar UTF-8-bytes en terug
sgcPGPMaxArgon2EncodedM, sgcPGPMaxArgon2Passes, sgcPGPMaxArgon2LanessgcCrypto_PGP_PacketsArgon2 S2K-limieten
CSGC_PGP_COMP_NONE, CSGC_PGP_COMP_ZIP, CSGC_PGP_COMP_ZLIBsgcCrypto_PGP_PacketsCompressie voor versleuteling
EsgcPGPErrorsgcCrypto_PGP_PacketsException die bij elke OpenPGP-fout wordt opgeworpen


function sgcPGP_Armor(const aData: TBytes; aType: TsgcPGPArmorType;
  aWithChecksum: Boolean = True): string;
function sgcPGP_Dearmor(const aText: string;
  out aType: TsgcPGPArmorType): TBytes;
{ TsgcPGPArmorType = (pgpaMessage, pgpaPublicKey, pgpaPrivateKey, pgpaSignature) }

Demo

De map Demos\18.Cryptography\09.OpenPGP (sgcOpenPGP.dpr) toont het gebruik van de API op deze pagina: sleutelgeneratie, versleuteling en ontsleuteling, ondertekening en verificatie.