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 |
| pgpkEd25519 | EdDSA Ed25519 + ECDH Curve25519 | Ed25519 + X25519 |
| pgpkEd448 | niet beschikbaar | Ed448 + X448 |
| pgpkRSA2048 | RSA 2048 | niet beschikbaar (het minimum van RFC 9580 is 3072) |
| pgpkRSA3072 | RSA 3072 | RSA 3072 |
| pgpkRSA4096 | RSA 4096 | RSA 4096 |
| pgpkNISTP256 | ECDSA + ECDH P-256 | ECDSA + ECDH P-256 |
| pgpkNISTP384 | ECDSA + ECDH P-384 | ECDSA + ECDH P-384 |
| pgpkNISTP521 | ECDSA + ECDH P-521 | ECDSA + 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 |
| FileName | Bestandsnaam in het literal data-pakket, kan leeg zijn |
| Date | Datum van de literal data, Unix-tijd |
| Format | Formaatbyte van de literal data: 'b' binair, 't' of 'u' tekst |
| Encrypted | True wanneer de invoer versleuteld was, False voor een alleen ondertekend bericht |
| SymAlgo | ID van het symmetrische algoritme (7, 8, 9 voor AES-128, AES-192, AES-256) |
| AEADAlgo | ID van de AEAD-modus (1 EAX, 2 OCB, 3 GCM), 0 voor SEIPD v1 |
| RecipientKeyID | Sleutel-ID van de geheime sleutel die het bericht ontsleutelde, leeg bij een wachtwoordzin |
| Verify | Een 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 |
| Signed | True wanneer het bericht minstens een handtekening bevat |
| Valid | True wanneer een handtekening is geverifieerd met een geldige sleutel uit de sleutelring |
| SignerKeyID, SignerFingerprint | Hex-sleutel-ID en vingerafdruk van de sleutel (of subsleutel) die ondertekende |
| SignerUserID | Primaire gebruikers-ID van de ondertekenaar |
| SignatureTime | Aanmaaktijd van de handtekening, Unix-tijd |
| HashAlgo | ID van het hashalgoritme, sgcPGP_HashName geeft de naam |
| Status | Leeg 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_Encrypt | sgcCrypto_PGP | Versleutelen voor publieke sleutels, optioneel ondertekend |
| sgcPGP_EncryptSymmetric | sgcCrypto_PGP | Versleutelen met een wachtwoordzin, v4 of v6 |
| sgcPGP_Decrypt | sgcCrypto_PGP | Ontsleutelen en een ingesloten handtekening verifiëren |
| sgcPGP_Sign | sgcCrypto_PGP | Detached of inline handtekening |
| sgcPGP_ClearSign | sgcCrypto_PGP | Cleartext handtekening |
| sgcPGP_Verify | sgcCrypto_PGP | Een inline of cleartext ondertekend bericht verifiëren |
| sgcPGP_VerifyDetached | sgcCrypto_PGP | Een detached handtekening verifiëren |
| TsgcPGPDecryptResult, TsgcPGPVerifyResult | sgcCrypto_PGP | Resultaatrecords |
| sgcPGPMaxDecompressedSize | sgcCrypto_PGP | Decompressielimiet, 0 is onbeperkt |
| sgcPGP_GenerateKey | sgcCrypto_PGP_Cert | Een v4- of v6-sleutel genereren |
| TsgcPGPKey | sgcCrypto_PGP_Cert | Een sleutel met zijn gebruikers-ID's en subsleutels: LoadFromBytes, SaveToBytes, SaveToArmored, Unlock, Protect |
| TsgcPGPKeyRing | sgcCrypto_PGP_Cert | Lijst van sleutels waarvan de ring eigenaar is: Add, Remove, Clear, LoadFromBytes, LoadFromFile, SaveToArmored, FindByID, FindByUserID, FindKeyPacket |
| TsgcPGPKeyType | sgcCrypto_PGP_Cert | Sleutelalgoritme voor generatie |
| sgcPGP_Armor, sgcPGP_Dearmor, sgcPGP_IsArmored | sgcCrypto_PGP_Packets | ASCII armor coderen en decoderen |
| sgcPGP_UTF8, sgcPGP_UTF8ToString | sgcCrypto_PGP_Packets | String naar UTF-8-bytes en terug |
| sgcPGPMaxArgon2EncodedM, sgcPGPMaxArgon2Passes, sgcPGPMaxArgon2Lanes | sgcCrypto_PGP_Packets | Argon2 S2K-limieten |
| CSGC_PGP_COMP_NONE, CSGC_PGP_COMP_ZIP, CSGC_PGP_COMP_ZLIB | sgcCrypto_PGP_Packets | Compressie voor versleuteling |
| EsgcPGPError | sgcCrypto_PGP_Packets | Exception 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.