OpenPGP

OpenPGP는 PGP와 GnuPG의 기반이 되는 표준입니다. 파일과 메시지를 한 명 이상의 사람에게 암호화하고, 수신자가 작성자를 확인하고 내용이 변경되지 않았음을 검증할 수 있도록 서명하며, 이 모든 것을 인증 기관 없이 수행합니다. 모든 사용자가 자신의 키 쌍을 소유하고 공개 키를 배포합니다. 암호화된 이메일, 서명된 소프트웨어 릴리스, 패키지 저장소, 그리고 파트너 및 은행과의 파일 교환에 사용되는 형식입니다.

 

sgcCrypto 유닛은 외부 DLL, OpenSSL, Indy 없이 순수 Object Pascal로 OpenPGP를 구현합니다. Win32와 Win64에서 Delphi 7부터 Delphi 13까지 컴파일되며, Linux64용으로도 컴파일됩니다. 모든 작업은 TBytes를 사용하여 메모리에서 수행됩니다. armor 출력은 TBytes로 반환되는 ASCII 텍스트이며, 헬퍼 함수 sgcPGP_UTF8와 sgcPGP_UTF8ToString이 문자열과 바이트 사이를 변환합니다.

 

버전 4 또는 버전 6

 

형식의 두 가지 버전이 지원되며, 둘 다 읽고 쓸 수 있습니다.

 

버전 4 (RFC 4880 및 RFC 6637)는 GnuPG, Kleopatra, Thunderbird 및 오늘날 배포된 거의 모든 도구가 사용하는 버전입니다. 상대방이 GnuPG를 실행하거나 상대방의 소프트웨어를 제어할 수 없는 경우에 선택하십시오. 메시지는 SEIPD v1(CFB 모드의 AES와 MDC 무결성 검사)으로 보호됩니다.

 

버전 6 (RFC 9580)은 표준의 최신 개정판입니다. 키는 Ed25519/X25519 또는 Ed448/X448을 기본으로 사용하고, 비밀 키는 Argon2로 보호되며, 메시지는 실제 AEAD 구성인 SEIPD v2(기본값은 OCB)를 사용합니다. 직접 만든 두 애플리케이션 사이처럼 모든 당사자가 RFC 9580을 지원할 때 선택하십시오. GnuPG 2.4는 v6 키를 읽지 못합니다.

 

메시지마다 선택할 필요는 없습니다. 암호화할 때 SEIPD v2는 모든 수신자가 지원하는 경우(v6 키)에만 사용됩니다. 수신자 중 하나라도 v4 키를 가지고 있으면 메시지는 SEIPD v1로 작성되므로, 혼합된 수신자 목록도 GnuPG에서 읽을 수 있습니다.

 

지원 기능

 

영역 지원 내용
형식 OpenPGP v4(RFC 4880, RFC 6637, GnuPG 호환) 및 v6(RFC 9580)
공개 키 알고리즘 RSA 2048/3072/4096, Ed25519, Ed448(v6 전용), X25519, X448, NIST P-256/P-384/P-521의 ECDSA 및 ECDH, Brainpool 곡선의 ECDSA 및 ECDH(가져오기만 가능)
대칭 암호 AES-128, AES-192, AES-256
암호화된 데이터 SEIPD v1(CFB와 MDC), OCB, EAX 또는 GCM을 사용하는 SEIPD v2 AEAD
세션 키 공개 키(PKESK v3 및 v6)와 패스프레이즈(SKESK v4 및 v6)
키 보호(S2K) 반복 및 솔트 S2K, Argon2 S2K
압축 ZIP(raw DEFLATE)과 ZLIB
서명 분리형, 인라인(원패스), 클리어텍스트 서명, 바이너리 및 텍스트 모드
인코딩 ASCII armor와 바이너리
GnuPG 2.4 LibrePGP OCB로 암호화된 메시지(패킷 20)와 v5 SKESK 패킷을 읽을 수 있으며, 쓰지는 않음

 

유닛

 

유닛 내용
sgcCrypto_PGP 상위 수준 API: 암호화, 복호화, 서명, 검증, 결과 레코드
sgcCrypto_PGP_Cert 전송 가능한 키, 키링, 키 생성: TsgcPGPKey, TsgcPGPKeyRing, sgcPGP_GenerateKey
sgcCrypto_PGP_Message 메시지 패킷: 세션 키, 암호화된 데이터, 리터럴 및 압축 데이터
sgcCrypto_PGP_Signature 서명 패킷, 생성 및 검증
sgcCrypto_PGP_Keys 키 패킷과 비밀 키 보호
sgcCrypto_PGP_Packets 패킷 프레이밍, ASCII armor, S2K, 상수 및 헬퍼
sgcCrypto_Modes SEIPD v2에서 사용하는 AES-OCB와 AES-EAX(대칭 암호화 참조)
sgcCrypto_Deflate DEFLATE 및 ZLIB 압축(WinZip AE-2 아카이브 암호화 참조)

 

대부분의 애플리케이션은 uses 절에 sgcCrypto_PGP, sgcCrypto_PGP_Cert, sgcCrypto_PGP_Packets만 있으면 됩니다. 이 유닛들은 암호화 팩에 포함되어 있으며 FIPS 빌드에서는 제외됩니다 (SGC_CRYPTO_FIPS, FIPS 모드 참조).

 

키 관리

 

sgcPGP_GenerateKey는 기본 서명 키와 암호화 하위 키를 생성하고, 자체 서명으로 사용자 ID를 바인딩한 다음 TsgcPGPKey를 반환합니다. 패스프레이즈는 비밀 키를 저장할 때 보호하며, 반환된 객체는 이미 잠금이 해제되어 있어 즉시 서명하고 복호화할 수 있습니다. 패스프레이즈가 비어 있으면 비밀 키는 보호되지 않은 상태로 저장됩니다. aExpirationSeconds는 생성 시점부터 계산하는 키 수명을 설정하며, 0은 만료되지 않음을 의미합니다.

 

TsgcPGPKeyType 버전 4 버전 6
pgpkEd25519EdDSA Ed25519 + ECDH Curve25519Ed25519 + X25519
pgpkEd448사용할 수 없음Ed448 + X448
pgpkRSA2048RSA 2048사용할 수 없음(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;

 

파일에서 로드한 키는 패스프레이즈로 저장된 경우 잠겨 있습니다. 서명에 사용하기 전에 Unlock을 호출하십시오. 패스프레이즈가 틀리면 False를 반환합니다. Protect는 비밀 정보를 새 패스프레이즈로 다시 암호화하며, 패스프레이즈를 변경하는 방법이 바로 이것입니다. v4 키는 AES-256과 반복 및 솔트 S2K로 보호되고, v6 키는 OCB 모드의 AES-256과 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은 임의 개수의 키를 보관하고 소유합니다. Add에 전달한 키는 키링과 함께 해제되므로 직접 해제하지 마십시오. LoadFromFile과 LoadFromBytes는 하나 또는 여러 개의 키가 들어 있는 armor 또는 바이너리 데이터를 받아들이며(GnuPG에서 키링 전체를 내보낸 파일도 동작합니다) 이미 로드된 내용에 추가합니다. 조회 함수는 다음과 같습니다.

 

 

각 키는 Version, FingerprintHex, KeyIDHex, PrimaryUserID, HasSecret, IsValid, IsRevoked, IsExpired, ExpirationTime과 함께 사용자 ID 및 하위 키를 제공합니다. 자체 서명, 하위 키 바인딩, 만료 및 폐기는 로드할 때 검사되며, 폐기되었거나 만료된 키는 암호화나 서명에 선택되지 않습니다.

 

암호화와 복호화

 

sgcPGP_Encrypt는 하나 이상의 공개 키로 암호화합니다. 서명자를 전달하면 암호화된 메시지 안에서 데이터에 서명하며, 이는 gpg --sign --encrypt가 생성하는 것과 같습니다. 서명자는 잠금이 해제되어 있어야 합니다. 데이터는 기본적으로 ZIP으로 압축되며, 변경하려면 CSGC_PGP_COMP_ZLIB 또는 CSGC_PGP_COMP_NONE을 전달하십시오. 모든 수신자가 기본 설정에 AES-256을 나열한 경우(SEIPD v2에서는 항상) AES-256이 사용되고, 그렇지 않으면 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;

 

키 대신 공유 비밀번호를 사용하려면 sgcPGP_EncryptSymmetric을 사용하십시오. aVersion = 4이면 v4 SKESK와 SEIPD v1을 작성하며 gpg --decrypt가 이를 읽습니다. 6이면 Argon2를 사용하는 v6 SKESK와 AES-256 OCB의 SEIPD v2를 작성합니다.

 


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는 armor 또는 바이너리 형태의 두 종류를 모두 처리합니다. aSecretKeys는 시도할 비밀 키를 담고, aPassphrase는 이를 잠금 해제하며 메시지 비밀번호로도 시도됩니다. 잠긴 키는 비공개 복사본에서 잠금이 해제되므로 전달한 키링은 수정되지 않습니다. aVerifyKeys는 내장된 서명을 확인하는 데 사용하는 공개 키를 담으며 nil일 수 있습니다. 복호화할 수 없는 메시지는 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;

 

TsgcPGPDecryptResult 레코드는 복호화된 내용을 설명합니다.

 

필드 의미
FileName리터럴 데이터 패킷에 저장된 파일 이름이며, 비어 있을 수 있음
Date리터럴 데이터 날짜, Unix 시간
Format리터럴 데이터 형식 바이트: 'b'는 바이너리, 't' 또는 'u'는 텍스트
Encrypted입력이 암호화되어 있으면 True, 서명만 된 메시지이면 False
SymAlgo대칭 알고리즘 ID(AES-128, AES-192, AES-256에 대해 7, 8, 9)
AEADAlgoAEAD 모드 ID(1 EAX, 2 OCB, 3 GCM), SEIPD v1은 0
RecipientKeyID메시지를 복호화한 비밀 키의 키 ID, 패스프레이즈인 경우 비어 있음
Verify내장된 서명에 대한 TsgcPGPVerifyResult, 아래 참조

 

서명과 검증

 

세 가지 종류의 서명이 일반적인 경우를 다룹니다.

 

 

서명자는 잠금이 해제되어 있어야 합니다. aTextMode는 텍스트에 표준 CRLF 줄 끝으로 서명하므로, 파일이 Windows와 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;

 

TsgcPGPVerifyResult 레코드는 처음 발견된 유효한 서명을 보고하며, 유효한 서명이 없으면 처음 서명을 보고합니다.

 

필드 의미
Signed메시지에 서명이 하나 이상 있으면 True
Valid키링의 유효한 키로 서명이 검증되면 True
SignerKeyID, SignerFingerprint서명한 키(또는 하위 키)의 16진수 키 ID와 지문
SignerUserID서명자의 기본 사용자 ID
SignatureTime서명 생성 시간, Unix 시간
HashAlgo해시 알고리즘 ID, sgcPGP_HashName이 이름을 제공
Status유효하면 비어 있고, 그렇지 않으면 사유: not signed, signer key not found, bad signature, signer key not valid at signature time, signature expired, malformed signature, unexpected signature type

 

키링에 없는 키의 서명은 절대 유효하지 않습니다. Status는 signer key not found이고 서명자 필드는 비어 있습니다. Valid만 신뢰하고, 직접 키링에 넣은 키에 대해서만 신뢰하십시오.

 

GnuPG와의 상호 운용성

 

GnuPG와 데이터를 교환하려면 v4 키를 사용하십시오. armor 파일은 변경 없이 양방향으로 전달됩니다. 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

 

GnuPG가 내보낸 키는 TsgcPGPKeyRing.LoadFromFile로 로드되며, 패스프레이즈로 보호된 비밀 키도 포함됩니다. GnuPG 2.4는 자신의 키에 LibrePGP OCB 기능을 표시하며, 해당 키로 패킷 20(OCB 암호화 데이터) 또는 v5 SKESK 패킷을 사용해 암호화할 수 있습니다. 이러한 메시지는 읽고 복호화할 수 있지만 절대 쓰지는 않습니다. v4 키를 위해 여기서 암호화한 데이터는 항상 모든 GnuPG 버전이 읽을 수 있는 SEIPD v1을 사용합니다. 스마트카드로 옮겨진 비밀 키는 GnuPG 내보내기에서 비밀 정보가 없는 스텁으로 나타나며, 로드는 되지만 서명하거나 복호화할 수 없습니다.

 

보안 참고 사항

 

 

제한 사항

 

 

API 참조

 

이름 유닛 용도
sgcPGP_EncryptsgcCrypto_PGP공개 키로 암호화, 선택적으로 서명
sgcPGP_EncryptSymmetricsgcCrypto_PGP패스프레이즈로 암호화, v4 또는 v6
sgcPGP_DecryptsgcCrypto_PGP복호화하고 내장된 서명을 검증
sgcPGP_SignsgcCrypto_PGP분리형 또는 인라인 서명
sgcPGP_ClearSignsgcCrypto_PGP클리어텍스트 서명
sgcPGP_VerifysgcCrypto_PGP인라인 또는 클리어텍스트로 서명된 메시지 검증
sgcPGP_VerifyDetachedsgcCrypto_PGP분리형 서명 검증
TsgcPGPDecryptResult, TsgcPGPVerifyResultsgcCrypto_PGP결과 레코드
sgcPGPMaxDecompressedSizesgcCrypto_PGP압축 해제 제한, 0은 무제한
sgcPGP_GenerateKeysgcCrypto_PGP_Certv4 또는 v6 키 생성
TsgcPGPKeysgcCrypto_PGP_Cert사용자 ID와 하위 키를 가진 키 하나: LoadFromBytes, SaveToBytes, SaveToArmored, Unlock, Protect
TsgcPGPKeyRingsgcCrypto_PGP_Cert키를 소유하는 목록: Add, Remove, Clear, LoadFromBytes, LoadFromFile, SaveToArmored, FindByID, FindByUserID, FindKeyPacket
TsgcPGPKeyTypesgcCrypto_PGP_Cert생성용 키 알고리즘
sgcPGP_Armor, sgcPGP_Dearmor, sgcPGP_IsArmoredsgcCrypto_PGP_PacketsASCII armor 인코딩 및 디코딩
sgcPGP_UTF8, sgcPGP_UTF8ToStringsgcCrypto_PGP_Packets문자열을 UTF-8 바이트로, 또는 그 반대로 변환
sgcPGPMaxArgon2EncodedM, sgcPGPMaxArgon2Passes, sgcPGPMaxArgon2LanessgcCrypto_PGP_PacketsArgon2 S2K 제한
CSGC_PGP_COMP_NONE, CSGC_PGP_COMP_ZIP, CSGC_PGP_COMP_ZLIBsgcCrypto_PGP_Packets암호화용 압축
EsgcPGPErrorsgcCrypto_PGP_PacketsOpenPGP 오류 시 발생하는 예외

 


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) }

 

데모

 

Demos\18.Cryptography\09.OpenPGP 폴더(sgcOpenPGP.dpr)는 이 페이지의 API를 사용하는 방법을 보여줍니다: 키 생성, 암호화와 복호화, 서명과 검증.