Delphi의 OpenPGP: GnuPG 없이 암호화, 서명, 검증하기

· 컴포넌트
Delphi의 OpenPGP: GnuPG 없이 암호화, 서명, 검증하기

OpenPGP는 여전히 많은 조직이 파일을 주고받는 방식입니다. 은행은 결제 배치를 자신의 공개 키로 암호화해서 보내 달라고 하고, 파트너는 GnuPG로 암호화한 보고서를 보내며, 릴리스에는 분리 서명이 함께 배포되어야 합니다. Delphi에서는 보통 모든 컴퓨터에 GnuPG를 설치하고 명령줄에서 gpg.exe를 호출해야 했고, 그에 따라 경로, 키링, 종료 코드를 처리하는 일이 뒤따랐습니다.

이제 sgcCrypto는 OpenPGP를 순수 Object Pascal로 직접 구현합니다. 배포할 DLL도, 호출할 명령줄 도구도, Indy에 대한 의존성도 없습니다. 키, 암호화, 복호화, 서명, 검증은 TBytes를 다루는 몇 번의 함수 호출로 처리되며, 결과는 양방향 모두 GnuPG와 상호 운용됩니다.

버전 4와 버전 6

형식의 두 세대를 지원합니다:

여러 수신자에게 암호화할 때, 라이브러리는 모든 수신자가 읽을 수 있는 형식을 선택합니다. 모두가 지원하면 RFC 9580의 AEAD 형식을, 그렇지 않으면 무결성이 보호되는 기존 형식을 사용합니다. 오래된 키와 새로운 키가 섞인 수신자에게 보낸 메시지도 어디서나 열립니다.

키 생성

한 번의 호출로 완전한 키가 만들어집니다. 기본 서명 키, 암호화 하위 키, 사용자 ID, 그리고 이들을 묶는 자체 서명이 포함됩니다.

uses
  sgcCrypto_PGP, sgcCrypto_PGP_Cert;

var
  oAlice: TsgcPGPKey;
begin
  // v4 Ed25519 + Curve25519, the default key type of GnuPG
  oAlice := sgcPGP_GenerateKey('Alice <alice@example.com>', 'secret',
    pgpkEd25519, 4);
  try
    Memo1.Text := oAlice.SaveToArmored(False);  // public key, share it
    SaveSecretKey(oAlice.SaveToArmored(True));  // protected secret key
  finally
    oAlice.Free;
  end;
end;

지원하는 키 유형은 RSA 2048, 3072, 4096, Ed25519, Ed448(버전 6), 그리고 NIST P-256, P-384, P-521의 ECDSA와 ECDH입니다. Brainpool 키는 가져올 수 있습니다. 비밀 키는 사용자의 패스프레이즈으로 보호되며, 버전 4는 반복된 SHA-256을, 버전 6는 AES-OCB와 함께 Argon2를 사용합니다.

암호화와 복호화

하나 이상의 공개 키로 암호화하며, 필요하면 발신자가 서명할 수 있습니다. 출력은 기본적으로 ASCII 아머 형식이므로 메일에 첨부하거나 서버에 업로드하기 바로 좋습니다.

// encrypt to Bob, signed by Alice, compressed with ZIP
vEncrypted := sgcPGP_Encrypt(vData, [oBob], oAlice);

// or protect a file with a passphrase only
vEncrypted := sgcPGP_EncryptSymmetric(vData, 'shared passphrase');

복호화에는 비밀 키가 들어 있는 키링이 필요합니다. 잠긴 키는 패스프레이즈으로 비공개 복사본에서 잠금 해제되므로 키링 자체는 변경되지 않습니다. 결과에는 메시지의 수신자가 누구였는지, 어떻게 암호화되었는지, 그리고 안에 있는 서명이 유효한지가 담겨 있습니다.

var
  oRing: TsgcPGPKeyRing;
  vResult: TsgcPGPDecryptResult;
begin
  oRing := TsgcPGPKeyRing.Create;
  try
    oRing.LoadFromFile('bob-secret.asc');
    vData := sgcPGP_Decrypt(vEncrypted, oRing, 'bob passphrase', oContacts,
      vResult);
    if vResult.Verify.Valid then
      Log('Signed by ' + vResult.Verify.SignerUserID);
  finally
    oRing.Free;
  end;
end;

서명과 검증

파일과 릴리스를 위한 분리 서명, 인라인 서명 메시지, 읽을 수 있는 상태로 남아야 하는 텍스트를 위한 평문 서명을 모두 지원하며, 줄 바꿈 방식에 영향을 받지 않는 서명을 위한 텍스트 모드도 제공합니다.

// detached signature, the classic file.sig
vSignature := sgcPGP_Sign(vData, oAlice);
if sgcPGP_VerifyDetached(vData, vSignature, oContacts, vVerify) then
  Log('Valid signature from ' + vVerify.SignerUserID);

// cleartext signed text, readable without any tool
vSigned := sgcPGP_ClearSign('Payment batch 2026-10 approved', oAlice);

GnuPG와 함께 동작

모든 기능은 GnuPG 2.4.5를 상대로 양방향 테스트를 거쳤습니다. GnuPG가 만든 파일과 서명은 Delphi에서 복호화되고 검증되며, 라이브러리가 만드는 모든 것은 gpg --decrypt와 gpg --verify가 받아들입니다. 여기에는 GnuPG 2.4가 자체 생성 키에 대해 기본으로 쓰는 OCB 암호화 메시지도 포함되는데, 많은 OpenPGP 라이브러리는 이를 읽지 못합니다. 버전 6 지원은 RFC 9580의 예제 키와 메시지로 확인했습니다.

키는 아머 형식 텍스트로 두 세계를 오갑니다. Delphi에서 키를 내보낸 뒤 gpg --import를 실행하거나, gpg --export --armor의 출력을 TsgcPGPKeyRing에 불러오십시오.

신뢰할 수 없는 입력을 위한 설계

외부에서 받은 파일을 복호화하는 애플리케이션은 그중 일부가 악의적이라고 가정해야 합니다. 라이브러리는 보호되지 않는 오래된 암호화 형식을 거부하고, 모든 복호화 실패를 동일한 오류로 보고하여 오라클로 악용될 수 없게 하며, 문서에 대한 SHA-1 서명을 거부하고, 조작된 메시지가 서버를 붙잡아 두지 못하도록 패스프레이즈 작업 계수와 압축 해제 크기에 한도를 둡니다.

그 밖의 새로운 기능

OpenPGP 유닛은 Win32와 Win64용 Delphi 7부터 Delphi 13, 그리고 C++Builder에서 컴파일됩니다. DSA와 ElGamal 키는 불러올 수 있지만 사용할 수는 없으며, 오래된 CAST5, 3DES, IDEA 암호는 지원하지 않습니다.

다음 읽을거리

특정 연동, 은행 형식, 또는 가져와야 하는 키에 관한 질문이 있으신가요? 문의하기. 코드를 작성한 사람들에게서 직접 답변을 받으실 수 있습니다.