OpenPGP to standard stojący za PGP i GnuPG. Szyfruje pliki i wiadomości dla jednej lub wielu osób oraz podpisuje je tak, aby odbiorca mógł sprawdzić, kto je napisał i że nic nie zostało zmienione, a robi to bez urzędu certyfikacji: każdy użytkownik posiada parę kluczy i publikuje jej publiczną połowę. Jest to format używany w szyfrowanej poczcie, podpisanych wydaniach oprogramowania, repozytoriach pakietów oraz przy wymianie plików z partnerami i bankami.
Jednostki sgcCrypto implementują OpenPGP w czystym Object Pascalu, bez zewnętrznej biblioteki DLL, bez OpenSSL i bez Indy. Kompilują się od Delphi 7 do Delphi 13 na Win32 i Win64, a jednostki kompilują się również dla Linux64. Wszystko działa w pamięci na TBytes: wynik w pancerzu ASCII to tekst ASCII zwracany jako TBytes, a funkcje pomocnicze sgcPGP_UTF8 i sgcPGP_UTF8ToString konwertują między łańcuchami znaków a bajtami.
Wersja 4 lub wersja 6
Obsługiwane są dwie wersje formatu, obie do odczytu i zapisu.
Wersja 4 (RFC 4880 i RFC 6637) jest używana przez GnuPG, Kleopatrę, Thunderbirda i niemal każde wdrożone dziś narzędzie. Wybierz ją zawsze, gdy druga strona używa GnuPG albo nie masz wpływu na jej oprogramowanie. Wiadomości są chronione przy użyciu SEIPD v1 (AES w trybie CFB oraz kontrola integralności MDC).
Wersja 6 (RFC 9580) jest aktualną wersją standardu. Klucze natywnie używają Ed25519/X25519 lub Ed448/X448, klucze tajne są chronione przy użyciu Argon2, a wiadomości używają SEIPD v2, czyli prawdziwej konstrukcji AEAD (domyślnie OCB). Wybierz ją, gdy każda strona obsługuje RFC 9580, na przykład między dwiema aplikacjami, które budujesz samodzielnie. GnuPG 2.4 nie odczytuje kluczy v6.
Nie trzeba wybierać dla każdej wiadomości osobno. Podczas szyfrowania SEIPD v2 jest używane tylko wtedy, gdy obsługuje je każdy odbiorca (klucze v6). Jeśli którykolwiek odbiorca ma klucz v4, wiadomość jest zapisywana jako SEIPD v1, dzięki czemu mieszana lista odbiorców pozostaje czytelna dla GnuPG.
Obsługiwane funkcje
| Obszar | Obsługiwane |
| Formaty | OpenPGP v4 (RFC 4880, RFC 6637, zgodny z GnuPG) oraz v6 (RFC 9580) |
| Algorytmy klucza publicznego | RSA 2048/3072/4096, Ed25519, Ed448 (tylko v6), X25519, X448, ECDSA i ECDH na NIST P-256/P-384/P-521, ECDSA i ECDH na krzywych Brainpool (tylko import) |
| Szyfry symetryczne | AES-128, AES-192, AES-256 |
| Zaszyfrowane dane | SEIPD v1 (CFB plus MDC), SEIPD v2 AEAD z OCB, EAX lub GCM |
| Klucze sesji | Klucz publiczny (PKESK v3 i v6) oraz hasło (SKESK v4 i v6) |
| Ochrona klucza (S2K) | Iterowany i solony S2K, Argon2 S2K |
| Kompresja | ZIP (surowy DEFLATE) i ZLIB |
| Podpisy | Odłączone, wbudowane (jednoprzebiegowe) i jawnotekstowe, tryb binarny i tekstowy |
| Kodowanie | Pancerz ASCII i binarne |
| GnuPG 2.4 LibrePGP | Odczytuje wiadomości zaszyfrowane OCB (pakiet 20) i pakiety SKESK v5, nigdy ich nie zapisuje |
Jednostki
| Jednostka | Zawartość |
| sgcCrypto_PGP | API wysokiego poziomu: szyfrowanie, odszyfrowywanie, podpisywanie, weryfikacja, rekordy wyników |
| sgcCrypto_PGP_Cert | Przenaszalne klucze, zestawy kluczy i generowanie kluczy: TsgcPGPKey, TsgcPGPKeyRing, sgcPGP_GenerateKey |
| sgcCrypto_PGP_Message | Pakiety wiadomości: klucze sesji, zaszyfrowane dane, dane literalne i skompresowane |
| sgcCrypto_PGP_Signature | Pakiety podpisów, tworzenie i weryfikacja |
| sgcCrypto_PGP_Keys | Pakiety kluczy i ochrona klucza tajnego |
| sgcCrypto_PGP_Packets | Ramkowanie pakietów, pancerz ASCII, S2K, stałe i funkcje pomocnicze |
| sgcCrypto_Modes | AES-OCB i AES-EAX, używane przez SEIPD v2 (patrz Szyfrowanie symetryczne) |
| sgcCrypto_Deflate | Kompresja DEFLATE i ZLIB (patrz Szyfrowanie archiwów WinZip AE-2) |
Większość aplikacji potrzebuje tylko sgcCrypto_PGP, sgcCrypto_PGP_Cert i sgcCrypto_PGP_Packets w klauzuli uses. Jednostki wchodzą w skład pakietu Cryptography i są wyłączone z kompilacji FIPS (SGC_CRYPTO_FIPS, patrz Tryb FIPS).
Zarządzanie kluczami
sgcPGP_GenerateKey tworzy główny klucz podpisujący oraz podklucz szyfrujący, wiąże identyfikator użytkownika za pomocą samopodpisu i zwraca TsgcPGPKey. Hasło chroni klucz tajny podczas zapisu; zwrócony obiekt jest już odblokowany i może od razu podpisywać oraz odszyfrowywać. Puste hasło zapisuje klucz tajny bez ochrony. aExpirationSeconds ustawia okres ważności klucza liczony od utworzenia, 0 oznacza, że klucz nigdy nie wygasa.
| TsgcPGPKeyType | Wersja 4 | Wersja 6 |
| pgpkEd25519 | EdDSA Ed25519 + ECDH Curve25519 | Ed25519 + X25519 |
| pgpkEd448 | niedostępne | Ed448 + X448 |
| pgpkRSA2048 | RSA 2048 | niedostępne (minimum w RFC 9580 to 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;
Klucz wczytany z pliku jest zablokowany, jeśli został zapisany z hasłem. Wywołaj Unlock przed użyciem go do podpisywania; zwraca False, gdy hasło jest błędne. Protect ponownie szyfruje materiał tajny nowym hasłem, i w ten sposób zmienia się hasło. Klucze v4 są chronione przy użyciu AES-256 i iterowanego, solonego S2K; klucze v6 przy użyciu AES-256 w trybie OCB i 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;
TsgcPGPKeyRing przechowuje dowolną liczbę kluczy i jest ich właścicielem: klucz przekazany do Add jest zwalniany razem z zestawem, więc nie zwalniaj go samodzielnie. LoadFromFile i LoadFromBytes przyjmują dane w pancerzu ASCII lub binarne, zawierające jeden lub wiele kluczy (działa eksport całego zestawu kluczy z GnuPG), i dołączają je do tego, co jest już wczytane. Wyszukiwanie:
Każdy klucz udostępnia Version, FingerprintHex, KeyIDHex, PrimaryUserID, HasSecret, IsValid, IsRevoked, IsExpired oraz ExpirationTime, a także swoje identyfikatory użytkownika i podklucze. Samopodpisy, powiązania podkluczy, wygaśnięcie i unieważnienie są sprawdzane podczas wczytywania, a unieważniony lub wygasły klucz nigdy nie jest wybierany do szyfrowania ani podpisywania.
Szyfrowanie i odszyfrowywanie
sgcPGP_Encrypt szyfruje dla jednego lub wielu kluczy publicznych. Przekazanie podpisującego podpisuje dane wewnątrz zaszyfrowanej wiadomości, co daje polecenie gpg --sign --encrypt. Podpisujący musi być odblokowany. Dane są domyślnie kompresowane metodą ZIP; przekaż CSGC_PGP_COMP_ZLIB lub CSGC_PGP_COMP_NONE, aby to zmienić. AES-256 jest używany, gdy każdy odbiorca wymienia go w swoich preferencjach (zawsze dla SEIPD v2), w przeciwnym razie 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;
Aby użyć wspólnego hasła zamiast kluczy, użyj sgcPGP_EncryptSymmetric. Z aVersion = 4 zapisuje SKESK v4 i SEIPD v1, które odczytuje gpg --decrypt. Z 6 zapisuje SKESK v6 z Argon2 oraz SEIPD v2 z 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 obsługuje oba rodzaje, w pancerzu ASCII lub binarne. aSecretKeys zawiera klucze tajne do wypróbowania; aPassphrase odblokowuje je i jest również wypróbowywane jako hasło wiadomości. Zablokowany klucz jest odblokowywany na prywatnej kopii, więc przekazany zestaw kluczy nigdy nie jest modyfikowany. aVerifyKeys zawiera klucze publiczne używane do sprawdzenia wbudowanego podpisu i może mieć wartość nil. Wiadomość, której nie można odszyfrować, zgłasza 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;
Rekord TsgcPGPDecryptResult opisuje, co zostało odszyfrowane:
| Pole | Znaczenie |
| FileName | Nazwa pliku zapisana w pakiecie danych literalnych, może być pusta |
| Date | Data danych literalnych, czas Unix |
| Format | Bajt formatu danych literalnych: 'b' binarne, 't' lub 'u' tekstowe |
| Encrypted | True, gdy dane wejściowe były zaszyfrowane, False dla wiadomości wyłącznie podpisanej |
| SymAlgo | Identyfikator algorytmu symetrycznego (7, 8, 9 dla AES-128, AES-192, AES-256) |
| AEADAlgo | Identyfikator trybu AEAD (1 EAX, 2 OCB, 3 GCM), 0 dla SEIPD v1 |
| RecipientKeyID | Identyfikator klucza tajnego, który odszyfrował wiadomość, pusty dla hasła |
| Verify | TsgcPGPVerifyResult dla wbudowanego podpisu, patrz niżej |
Podpisywanie i weryfikacja
Trzy rodzaje podpisu obejmują typowe przypadki:
Podpisujący musi być odblokowany. aTextMode podpisuje tekst z kanonicznymi zakończeniami wierszy CRLF, więc podpis nadal się weryfikuje po przeniesieniu pliku między Windows a Linuksem.
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;
Rekord TsgcPGPVerifyResult opisuje pierwszy znaleziony poprawny podpis albo pierwszy podpis, gdy żaden nie jest poprawny:
| Pole | Znaczenie |
| Signed | True, gdy wiadomość zawiera co najmniej jeden podpis |
| Valid | True, gdy podpis został zweryfikowany poprawnym kluczem z zestawu kluczy |
| SignerKeyID, SignerFingerprint | Szesnastkowy identyfikator i odcisk klucza (lub podklucza), który podpisał |
| SignerUserID | Główny identyfikator użytkownika podpisującego |
| SignatureTime | Czas utworzenia podpisu, czas Unix |
| HashAlgo | Identyfikator algorytmu skrótu, sgcPGP_HashName podaje jego nazwę |
| Status | Pusty, gdy podpis jest poprawny, w przeciwnym razie przyczyna: not signed, signer key not found, bad signature, signer key not valid at signature time, signature expired, malformed signature, unexpected signature type |
Podpis od klucza, którego nie ma w zestawie kluczy, nigdy nie jest poprawny: Status ma wartość signer key not found a pola podpisującego pozostają puste. Ufaj wyłącznie Valid, i tylko dla kluczy, które sam umieściłeś w zestawie kluczy.
Współdziałanie z GnuPG
Do wymiany danych z GnuPG używaj kluczy v4. Pliki w pancerzu ASCII przechodzą w obie strony bez zmian. Typowe polecenia po stronie GnuPG:
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
Klucze wyeksportowane przez GnuPG wczytują się przez TsgcPGPKeyRing.LoadFromFile, w tym klucze tajne chronione hasłem. GnuPG 2.4 oznacza swoje klucze funkcją LibrePGP OCB i może szyfrować dla nich pakietem 20 (zaszyfrowane dane OCB) lub pakietami SKESK v5. Takie wiadomości są odczytywane i odszyfrowywane, ale nigdy nie są zapisywane: dane zaszyfrowane tutaj dla klucza v4 zawsze używają SEIPD v1, który odczytuje każda wersja GnuPG. Klucze tajne przeniesione na kartę inteligentną występują w eksporcie GnuPG jako zaślepki bez materiału tajnego; wczytują się, ale nie mogą podpisywać ani odszyfrowywać.
Uwagi dotyczące bezpieczeństwa
Ograniczenia
Dokumentacja API
| Nazwa | Jednostka | Przeznaczenie |
| sgcPGP_Encrypt | sgcCrypto_PGP | Szyfrowanie dla kluczy publicznych, opcjonalnie podpisane |
| sgcPGP_EncryptSymmetric | sgcCrypto_PGP | Szyfrowanie hasłem, v4 lub v6 |
| sgcPGP_Decrypt | sgcCrypto_PGP | Odszyfrowanie i weryfikacja wbudowanego podpisu |
| sgcPGP_Sign | sgcCrypto_PGP | Podpis odłączony lub wbudowany |
| sgcPGP_ClearSign | sgcCrypto_PGP | Podpis jawnotekstowy |
| sgcPGP_Verify | sgcCrypto_PGP | Weryfikacja wiadomości z podpisem wbudowanym lub jawnotekstowym |
| sgcPGP_VerifyDetached | sgcCrypto_PGP | Weryfikacja podpisu odłączonego |
| TsgcPGPDecryptResult, TsgcPGPVerifyResult | sgcCrypto_PGP | Rekordy wyników |
| sgcPGPMaxDecompressedSize | sgcCrypto_PGP | Limit dekompresji, 0 oznacza brak limitu |
| sgcPGP_GenerateKey | sgcCrypto_PGP_Cert | Generowanie klucza v4 lub v6 |
| TsgcPGPKey | sgcCrypto_PGP_Cert | Jeden klucz z jego identyfikatorami użytkownika i podkluczami: LoadFromBytes, SaveToBytes, SaveToArmored, Unlock, Protect |
| TsgcPGPKeyRing | sgcCrypto_PGP_Cert | Lista kluczy będąca ich właścicielem: Add, Remove, Clear, LoadFromBytes, LoadFromFile, SaveToArmored, FindByID, FindByUserID, FindKeyPacket |
| TsgcPGPKeyType | sgcCrypto_PGP_Cert | Algorytm klucza do generowania |
| sgcPGP_Armor, sgcPGP_Dearmor, sgcPGP_IsArmored | sgcCrypto_PGP_Packets | Kodowanie i dekodowanie pancerza ASCII |
| sgcPGP_UTF8, sgcPGP_UTF8ToString | sgcCrypto_PGP_Packets | Łańcuch znaków na bajty UTF-8 i z powrotem |
| sgcPGPMaxArgon2EncodedM, sgcPGPMaxArgon2Passes, sgcPGPMaxArgon2Lanes | sgcCrypto_PGP_Packets | Limity Argon2 S2K |
| CSGC_PGP_COMP_NONE, CSGC_PGP_COMP_ZIP, CSGC_PGP_COMP_ZLIB | sgcCrypto_PGP_Packets | Kompresja przy szyfrowaniu |
| EsgcPGPError | sgcCrypto_PGP_Packets | Wyjątek zgłaszany przy każdym błędzie OpenPGP |
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
Folder Demos\18.Cryptography\09.OpenPGP (sgcOpenPGP.dpr) pokazuje w użyciu API opisane na tej stronie: generowanie kluczy, szyfrowanie i odszyfrowywanie, podpisywanie i weryfikację.