OpenPGP

OpenPGP é o padrão por trás do PGP e do GnuPG. Ele criptografa arquivos e mensagens para uma ou mais pessoas, assina-os para que o destinatário possa verificar quem os escreveu e que nada foi alterado, e faz as duas coisas sem uma autoridade certificadora: cada usuário possui um par de chaves e publica a metade pública. É o formato usado em e-mail criptografado, em versões de software assinadas, em repositórios de pacotes e na troca de arquivos com parceiros e bancos.

 

As units sgcCrypto implementam o OpenPGP em Object Pascal puro, sem DLL externa, sem OpenSSL e sem Indy. Elas compilam do Delphi 7 ao Delphi 13 em Win32 e Win64, e as units também compilam para Linux64. Tudo funciona em memória sobre TBytes: a saída em armor é texto ASCII retornado como TBytes, e os auxiliares sgcPGP_UTF8 e sgcPGP_UTF8ToString convertem entre strings e bytes.

 

Versão 4 ou versão 6

 

Duas versões do formato são suportadas, e ambas são lidas e escritas.

 

Versão 4 (RFC 4880 e RFC 6637) é a que o GnuPG, o Kleopatra, o Thunderbird e quase todas as ferramentas em uso hoje utilizam. Escolha-a sempre que o outro lado executar o GnuPG ou que você não controle o software dele. As mensagens são protegidas com SEIPD v1 (AES em modo CFB mais uma verificação de integridade MDC).

 

Versão 6 (RFC 9580) é a revisão atual do padrão. As chaves usam Ed25519/X25519 ou Ed448/X448 nativamente, as chaves secretas são protegidas com Argon2, e as mensagens usam SEIPD v2, uma construção AEAD de verdade (OCB por padrão). Escolha-a quando todas as partes suportarem a RFC 9580, por exemplo entre duas aplicações que você mesmo constrói. O GnuPG 2.4 não lê chaves v6.

 

Você não precisa escolher por mensagem. Ao criptografar, o SEIPD v2 só é usado se todos os destinatários o suportarem (chaves v6). Se algum destinatário tiver uma chave v4, a mensagem é escrita como SEIPD v1, de modo que uma lista de destinatários mista continua legível pelo GnuPG.

 

Recursos suportados

 

Área Suportado
Formatos OpenPGP v4 (RFC 4880, RFC 6637, compatível com o GnuPG) e v6 (RFC 9580)
Algoritmos de chave pública RSA 2048/3072/4096, Ed25519, Ed448 (somente v6), X25519, X448, ECDSA e ECDH nas curvas NIST P-256/P-384/P-521, ECDSA e ECDH nas curvas Brainpool (somente importação)
Cifras simétricas AES-128, AES-192, AES-256
Dados criptografados SEIPD v1 (CFB mais MDC), SEIPD v2 AEAD com OCB, EAX ou GCM
Chaves de sessão Chave pública (PKESK v3 e v6) e senha (SKESK v4 e v6)
Proteção de chave (S2K) S2K iterado e com salt, S2K Argon2
Compressão ZIP (DEFLATE bruto) e ZLIB
Assinaturas Assinaturas destacadas, em linha (uma passagem) e em texto claro, modo binário e modo texto
Codificação ASCII armor e binário
GnuPG 2.4 LibrePGP Lê mensagens criptografadas com OCB (pacote 20) e pacotes SKESK v5, nunca os escreve

 

Units

 

Unit Conteúdo
sgcCrypto_PGP API de alto nível: criptografar, descriptografar, assinar, verificar, records de resultado
sgcCrypto_PGP_Cert Chaves transferíveis, chaveiros e geração de chaves: TsgcPGPKey, TsgcPGPKeyRing, sgcPGP_GenerateKey
sgcCrypto_PGP_Message Pacotes de mensagem: chaves de sessão, dados criptografados, dados literais e compactados
sgcCrypto_PGP_Signature Pacotes de assinatura, criação e verificação
sgcCrypto_PGP_Keys Pacotes de chave e proteção da chave secreta
sgcCrypto_PGP_Packets Enquadramento de pacotes, ASCII armor, S2K, constantes e auxiliares
sgcCrypto_Modes AES-OCB e AES-EAX, usados pelo SEIPD v2 (veja Criptografia Simétrica)
sgcCrypto_Deflate Compressão DEFLATE e ZLIB (veja Criptografia de Arquivos WinZip AE-2)

 

A maioria das aplicações só precisa de sgcCrypto_PGP, sgcCrypto_PGP_Cert e sgcCrypto_PGP_Packets na cláusula uses. As units fazem parte do pacote Cryptography e são excluídas das builds FIPS (SGC_CRYPTO_FIPS, veja Modo FIPS).

 

Gerenciamento de chaves

 

sgcPGP_GenerateKey cria uma chave primária de assinatura mais uma subchave de criptografia, vincula o ID de usuário com uma autoassinatura e retorna um TsgcPGPKey. A senha protege a chave secreta quando ela é salva; o objeto retornado já está desbloqueado e pode assinar e descriptografar imediatamente. Uma senha vazia salva a chave secreta sem proteção. aExpirationSeconds define o tempo de vida da chave contado a partir da criação, 0 significa que ela nunca expira.

 

TsgcPGPKeyType Versão 4 Versão 6
pgpkEd25519EdDSA Ed25519 + ECDH Curve25519Ed25519 + X25519
pgpkEd448não disponívelEd448 + X448
pgpkRSA2048RSA 2048não disponível (o mínimo da RFC 9580 é 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;

 

Uma chave carregada de um arquivo fica bloqueada se foi salva com uma senha. Chame Unlock antes de usá-la para assinar; ele retorna False quando a senha está errada. Protect criptografa novamente o material secreto com uma nova senha, que é a forma de alterá-la. As chaves v4 são protegidas com AES-256 e o S2K iterado e com salt; as chaves v6 com AES-256 em modo OCB e um S2K Argon2.

 


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;

 

Um TsgcPGPKeyRing guarda qualquer número de chaves e é o dono delas: uma chave passada a Add é liberada junto com o chaveiro, portanto não a libere você mesmo. LoadFromFile e LoadFromBytes aceitam dados em armor ou binários com uma ou várias chaves (uma exportação de um chaveiro inteiro do GnuPG funciona) e acrescentam ao que já está carregado. Buscas:

 

 

Cada chave expõe Version, FingerprintHex, KeyIDHex, PrimaryUserID, HasSecret, IsValid, IsRevoked, IsExpired e ExpirationTime, além de seus IDs de usuário e subchaves. As autoassinaturas, os vínculos de subchave, a expiração e a revogação são verificados no carregamento, e uma chave revogada ou expirada nunca é selecionada para criptografar ou assinar.

 

Criptografar e descriptografar

 

sgcPGP_Encrypt criptografa para uma ou mais chaves públicas. Informar um signatário assina os dados dentro da mensagem criptografada, que é o que gpg --sign --encrypt produz. O signatário precisa estar desbloqueado. Os dados são compactados com ZIP por padrão; passe CSGC_PGP_COMP_ZLIB ou CSGC_PGP_COMP_NONE para alterar isso. O AES-256 é usado quando todos os destinatários o listam em suas preferências (sempre para SEIPD v2), caso contrário o 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;

 

Para uma senha compartilhada em vez de chaves, use sgcPGP_EncryptSymmetric. Com aVersion = 4 ele escreve um SKESK v4 e SEIPD v1, que o gpg --decrypt lê. Com 6 ele escreve um SKESK v6 com Argon2 e SEIPD v2 com 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 trata os dois tipos, em armor ou binários. aSecretKeys contém as chaves secretas a tentar; aPassphrase as desbloqueia e também é tentada como a senha da mensagem. Uma chave bloqueada é desbloqueada em uma cópia privada, portanto o chaveiro passado nunca é modificado. aVerifyKeys contém as chaves públicas usadas para verificar uma assinatura embutida e pode ser nil. Uma mensagem que não pode ser descriptografada gera 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;

 

O record TsgcPGPDecryptResult descreve o que foi descriptografado:

 

Campo Significado
FileNameNome de arquivo armazenado no pacote de dados literais, pode estar vazio
DateData dos dados literais, tempo Unix
FormatByte de formato dos dados literais: 'b' binário, 't' ou 'u' texto
EncryptedTrue quando a entrada estava criptografada, False para uma mensagem somente assinada
SymAlgoID do algoritmo simétrico (7, 8, 9 para AES-128, AES-192, AES-256)
AEADAlgoID do modo AEAD (1 EAX, 2 OCB, 3 GCM), 0 para SEIPD v1
RecipientKeyIDID da chave secreta que descriptografou a mensagem, vazio para uma senha
VerifyUm TsgcPGPVerifyResult para a assinatura embutida, veja abaixo

 

Assinar e verificar

 

Três tipos de assinatura cobrem os casos usuais:

 

 

O signatário precisa estar desbloqueado. aTextMode assina o texto com finais de linha CRLF canônicos, de modo que a assinatura continua sendo verificada depois que o arquivo passa entre Windows e Linux.

 


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;

 

O record TsgcPGPVerifyResult informa sobre a primeira assinatura válida encontrada, ou sobre a primeira assinatura quando nenhuma é válida:

 

Campo Significado
SignedTrue quando a mensagem traz pelo menos uma assinatura
ValidTrue quando uma assinatura foi verificada com uma chave válida do chaveiro
SignerKeyID, SignerFingerprintID da chave e impressão digital em hexadecimal da chave (ou subchave) que assinou
SignerUserIDID de usuário primário do signatário
SignatureTimeHora de criação da assinatura, tempo Unix
HashAlgoID do algoritmo de hash, sgcPGP_HashName fornece seu nome
StatusVazio quando válida, caso contrário o motivo: not signed, signer key not found, bad signature, signer key not valid at signature time, signature expired, malformed signature, unexpected signature type

 

Uma assinatura de uma chave que não está no chaveiro nunca é válida: Status é signer key not found e os campos do signatário permanecem vazios. Confie apenas em Valid, e somente para chaves que você mesmo colocou no chaveiro.

 

Interoperabilidade com o GnuPG

 

Use chaves v4 para trocar dados com o GnuPG. Os arquivos em armor funcionam nos dois sentidos sem alterações. Comandos típicos do lado do 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

 

As chaves exportadas pelo GnuPG são carregadas com TsgcPGPKeyRing.LoadFromFile, inclusive as chaves secretas protegidas por senha. O GnuPG 2.4 marca suas chaves com o recurso OCB do LibrePGP e pode criptografar para elas com o pacote 20 (dados criptografados com OCB) ou com pacotes SKESK v5. Essas mensagens são lidas e descriptografadas, mas nunca são escritas: os dados criptografados aqui para uma chave v4 sempre usam SEIPD v1, que toda versão do GnuPG lê. As chaves secretas movidas para um smartcard aparecem em uma exportação do GnuPG como stubs sem material secreto; elas são carregadas, mas não podem assinar nem descriptografar.

 

Notas de segurança

 

 

Limitações

 

 

Referência da API

 

Nome Unit Finalidade
sgcPGP_EncryptsgcCrypto_PGPCriptografar para chaves públicas, opcionalmente assinado
sgcPGP_EncryptSymmetricsgcCrypto_PGPCriptografar com uma senha, v4 ou v6
sgcPGP_DecryptsgcCrypto_PGPDescriptografar e verificar uma assinatura embutida
sgcPGP_SignsgcCrypto_PGPAssinatura destacada ou em linha
sgcPGP_ClearSignsgcCrypto_PGPAssinatura em texto claro
sgcPGP_VerifysgcCrypto_PGPVerificar uma mensagem assinada em linha ou em texto claro
sgcPGP_VerifyDetachedsgcCrypto_PGPVerificar uma assinatura destacada
TsgcPGPDecryptResult, TsgcPGPVerifyResultsgcCrypto_PGPRecords de resultado
sgcPGPMaxDecompressedSizesgcCrypto_PGPLimite de descompressão, 0 é ilimitado
sgcPGP_GenerateKeysgcCrypto_PGP_CertGerar uma chave v4 ou v6
TsgcPGPKeysgcCrypto_PGP_CertUma chave com seus IDs de usuário e subchaves: LoadFromBytes, SaveToBytes, SaveToArmored, Unlock, Protect
TsgcPGPKeyRingsgcCrypto_PGP_CertLista proprietária de chaves: Add, Remove, Clear, LoadFromBytes, LoadFromFile, SaveToArmored, FindByID, FindByUserID, FindKeyPacket
TsgcPGPKeyTypesgcCrypto_PGP_CertAlgoritmo de chave para geração
sgcPGP_Armor, sgcPGP_Dearmor, sgcPGP_IsArmoredsgcCrypto_PGP_PacketsCodificação e decodificação ASCII armor
sgcPGP_UTF8, sgcPGP_UTF8ToStringsgcCrypto_PGP_PacketsString para bytes UTF-8 e de volta
sgcPGPMaxArgon2EncodedM, sgcPGPMaxArgon2Passes, sgcPGPMaxArgon2LanessgcCrypto_PGP_PacketsLimites do S2K Argon2
CSGC_PGP_COMP_NONE, CSGC_PGP_COMP_ZIP, CSGC_PGP_COMP_ZLIBsgcCrypto_PGP_PacketsCompressão para criptografia
EsgcPGPErrorsgcCrypto_PGP_PacketsExceção gerada em qualquer erro do 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

 

A pasta Demos\18.Cryptography\09.OpenPGP (sgcOpenPGP.dpr) mostra a API desta página em uso: geração de chaves, criptografia e descriptografia, assinatura e verificação.