OpenPGP

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
pgpkEd25519EdDSA Ed25519 + ECDH Curve25519Ed25519 + X25519
pgpkEd448niedostępneEd448 + X448
pgpkRSA2048RSA 2048niedostępne (minimum w RFC 9580 to 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;

 

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
FileNameNazwa pliku zapisana w pakiecie danych literalnych, może być pusta
DateData danych literalnych, czas Unix
FormatBajt formatu danych literalnych: 'b' binarne, 't' lub 'u' tekstowe
EncryptedTrue, gdy dane wejściowe były zaszyfrowane, False dla wiadomości wyłącznie podpisanej
SymAlgoIdentyfikator algorytmu symetrycznego (7, 8, 9 dla AES-128, AES-192, AES-256)
AEADAlgoIdentyfikator trybu AEAD (1 EAX, 2 OCB, 3 GCM), 0 dla SEIPD v1
RecipientKeyIDIdentyfikator klucza tajnego, który odszyfrował wiadomość, pusty dla hasła
VerifyTsgcPGPVerifyResult 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
SignedTrue, gdy wiadomość zawiera co najmniej jeden podpis
ValidTrue, gdy podpis został zweryfikowany poprawnym kluczem z zestawu kluczy
SignerKeyID, SignerFingerprintSzesnastkowy identyfikator i odcisk klucza (lub podklucza), który podpisał
SignerUserIDGłówny identyfikator użytkownika podpisującego
SignatureTimeCzas utworzenia podpisu, czas Unix
HashAlgoIdentyfikator algorytmu skrótu, sgcPGP_HashName podaje jego nazwę
StatusPusty, 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_EncryptsgcCrypto_PGPSzyfrowanie dla kluczy publicznych, opcjonalnie podpisane
sgcPGP_EncryptSymmetricsgcCrypto_PGPSzyfrowanie hasłem, v4 lub v6
sgcPGP_DecryptsgcCrypto_PGPOdszyfrowanie i weryfikacja wbudowanego podpisu
sgcPGP_SignsgcCrypto_PGPPodpis odłączony lub wbudowany
sgcPGP_ClearSignsgcCrypto_PGPPodpis jawnotekstowy
sgcPGP_VerifysgcCrypto_PGPWeryfikacja wiadomości z podpisem wbudowanym lub jawnotekstowym
sgcPGP_VerifyDetachedsgcCrypto_PGPWeryfikacja podpisu odłączonego
TsgcPGPDecryptResult, TsgcPGPVerifyResultsgcCrypto_PGPRekordy wyników
sgcPGPMaxDecompressedSizesgcCrypto_PGPLimit dekompresji, 0 oznacza brak limitu
sgcPGP_GenerateKeysgcCrypto_PGP_CertGenerowanie klucza v4 lub v6
TsgcPGPKeysgcCrypto_PGP_CertJeden klucz z jego identyfikatorami użytkownika i podkluczami: LoadFromBytes, SaveToBytes, SaveToArmored, Unlock, Protect
TsgcPGPKeyRingsgcCrypto_PGP_CertLista kluczy będąca ich właścicielem: Add, Remove, Clear, LoadFromBytes, LoadFromFile, SaveToArmored, FindByID, FindByUserID, FindKeyPacket
TsgcPGPKeyTypesgcCrypto_PGP_CertAlgorytm klucza do generowania
sgcPGP_Armor, sgcPGP_Dearmor, sgcPGP_IsArmoredsgcCrypto_PGP_PacketsKodowanie i dekodowanie pancerza ASCII
sgcPGP_UTF8, sgcPGP_UTF8ToStringsgcCrypto_PGP_PacketsŁańcuch znaków na bajty UTF-8 i z powrotem
sgcPGPMaxArgon2EncodedM, sgcPGPMaxArgon2Passes, sgcPGPMaxArgon2LanessgcCrypto_PGP_PacketsLimity Argon2 S2K
CSGC_PGP_COMP_NONE, CSGC_PGP_COMP_ZIP, CSGC_PGP_COMP_ZLIBsgcCrypto_PGP_PacketsKompresja przy szyfrowaniu
EsgcPGPErrorsgcCrypto_PGP_PacketsWyją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ę.