Delphi로 Linux에서 Windows EXE 서명하기

· 컴포넌트
Delphi로 Linux에서 Windows EXE 서명하기

Windows 프로그램에 서명하려면 지금까지는 항상 Windows 머신이 필요했습니다. signtool은 Windows에서만 실행되므로, Linux에서 컴파일하는 빌드 파이프라인이라도 마지막에 파일에 서명을 넣기 위해서는 어딘가에 Windows 호스트가 필요했습니다.

sgcSign 2026.10부터는 이 호스트가 필요하지 않습니다. sgcSign의 Authenticode 형식, 즉 PE 파일을 해싱하고 서명을 구성하는 부분은 Windows 서명 API를 호출하는 대신 Delphi 코드로 구현되어 있습니다. Windows에서는 키 연산이 여전히 CNG를 거치지만, 그 외 모든 플랫폼에서는 sgcSign 자체 암호화 코드로 실행되므로, Windows에서 EXE에 서명하는 컴포넌트가 Linux 64비트에서도 빌드되고 실행되며, Windows는 그렇게 만들어진 파일을 다른 서명된 파일과 동일하게 받아들입니다. 이 글에서는 이를 수행하는 짧은 콘솔 프로그램과 Linux용 빌드 방법, 결과를 확인하는 방법을 보여줍니다.

Ubuntu에서 EXE에 서명하는 Delphi 프로그램과, 그 파일을 Windows에서 확인하는 모습입니다. YouTube에서도 볼 수 있습니다.

왜 Linux에서 서명하는가

프로그램

서명할 파일, 출력 파일, 비밀번호가 있는 PFX, 그리고 선택적으로 타임스탬프 기관을 인자로 받는 콘솔 프로그램입니다. Windows와 Linux에서 동일한 코드입니다.

program SignExe;

{$APPTYPE CONSOLE}

uses
  SysUtils, sgcSign_Authenticode, sgcSign_KeyProvider_PFX, sgcSign_TSA;

var
  PFX: TsgcPFXKeyProvider;
  TSA: TsgcTSAClient;
  Signer: TsgcAuthenticodeSigner;
begin
  if ParamCount < 4 then
  begin
    Writeln('usage: SignExe <input.exe> <output.exe> <file.pfx> <password> [tsa-url]');
    Halt(1);
  end;
  PFX := TsgcPFXKeyProvider.Create(nil);
  TSA := TsgcTSAClient.Create(nil);
  Signer := TsgcAuthenticodeSigner.Create(nil);
  try
    try
      PFX.LoadFromFile(ParamStr(3), ParamStr(4));
      Signer.KeyProvider := PFX;
      Signer.Hash := ahSHA256;
      Signer.Level := alBES;
      Signer.Description := 'sgcSign demo';
      if ParamCount >= 5 then
      begin
        TSA.URL := ParamStr(5);
        Signer.TSAClient := TSA;
        Signer.Level := alT;
      end;
      Signer.SignFile(ParamStr(1), ParamStr(2));
      Writeln('Signer: ', PFX.Certificate.Subject);
      Writeln('Signed: ', ParamStr(2));
    except
      on E: Exception do
      begin
        Writeln('Error: ', E.Message);
        ExitCode := 1;
      end;
    end;
  finally
    Signer.Free;
    TSA.Free;
    PFX.Free;
  end;
end.

TsgcPFXKeyProvider는 인증서와 개인 키를 보관합니다. TsgcAuthenticodeSigner는 Windows와 같은 방식으로 PE 파일을 해싱하고, PKCS#7 서명을 구성하여 파일의 인증서 테이블에 기록합니다. TsgcTSAClient를 연결하면 RFC 3161 타임스탬프도 함께 가져오므로, 인증서가 만료된 후에도 서명이 유효하게 유지됩니다.

Linux 64비트용으로 빌드하기

Projects 창에서 SDK Manager에 Linux SDK를 설정한 뒤 대상 플랫폼으로 Linux 64-bit를 추가하고 빌드합니다. sgcSign 런타임 패키지는 Delphi 10.3부터 Linux64용으로 빌드됩니다. Linux에서는 가져올 Windows 인증서 저장소가 없으므로 PFX를 순수 Pascal 코드로 파싱하며, RSA 또는 ECDSA 연산은 sgcSign 자체 암호화 코드로 실행됩니다. 그 주변의 서명 형식은 Windows에서 실행되는 것과 동일한 코드로 구성됩니다.

실행하고 결과 확인하기

$ file Hello.exe
PE32+ executable (console) x86-64, for MS Windows

$ ./SignExe Hello.exe Hello-signed-linux.exe ../certs/demo.pfx demo http://timestamp.digicert.com
Signer: O=eSeGeCe Demo, CN=sgcSign Demo Code Signing
Signed: Hello-signed-linux.exe

Windows 없이 확인하려면 osslsigncode verify가 파일의 다이제스트를 다시 계산하여 서명을 검사합니다: Signature verification: ok.

그런 다음 파일을 Windows로 복사합니다. PowerShell과 signtool verify /pa /v는 동일한 서명을 읽어 들입니다. Windows가 계산한 파일 해시는 서명 당시의 해시와 일치하고, 서명자는 데모 인증서이며, DigiCert 타임스탬프도 검증됩니다. 파일은 여전히 정상적으로 실행됩니다.

PS> Get-AuthenticodeSignature .\Hello-signed-linux.exe | Format-List SignatureType,
      @{n='Signer';e={$_.SignerCertificate.Subject}},
      @{n='TimeStamper';e={$_.TimeStamperCertificate.Subject}}

SignatureType : Authenticode
Signer        : CN=sgcSign Demo Code Signing, O=eSeGeCe Demo
TimeStamper   : CN=DigiCert SHA256 RSA4096 Timestamp Responder 2026 1, O="DigiCert, Inc.", C=US

Windows가 유일하게 문제 삼는 부분은 서명이 아니라 데모 인증서입니다. 데모 체인은 이 머신에 설치되어 있지 않은 자체 서명 테스트 루트로 끝나기 때문에, Windows는 이를 신뢰할 수 있는 루트와 연결하지 못합니다. 다이제스트, 서명, 타임스탬프는 모두 정상적으로 검증됩니다.

Linux와 Windows에서 동일한 바이트

동일한 Hello.exe를 동일한 PFX로, 타임스탬프 없이 위 프로그램의 Linux 빌드와 Windows 빌드로 각각 한 번씩 서명한 뒤 두 파일을 비교해 보겠습니다.

Linux    82344 bytes  SHA-256 6FD63BB01A8E911AB099431BC76F3BFA4EF557082BFB8BC18CCBFA97A9E8079D
Windows  82344 bytes  SHA-256 6FD63BB01A8E911AB099431BC76F3BFA4EF557082BFB8BC18CCBFA97A9E8079D

두 파일은 바이트 단위까지 완전히 동일합니다. 이런 방식의 RSA 서명은 결정적(deterministic)이며, 요청하지 않는 한 서명자는 서명 시각을 포함시키지 않으므로, 파일은 서명한 머신이 아니라 오직 키와 입력에만 의존합니다. 타임스탬프는 실행할 때마다 달라지는 유일한 부분인데, 타임스탬프 기관이 요청받은 순간을 찍기 때문입니다.

키의 출처

Linux에서는 PFX나 PEM 파일, PKCS#11 토큰이나 HSM, 또는 Azure Trusted Signing, AWS KMS, Google Cloud KMS, HashiCorp Vault, CSC 원격 서명 서비스와 같은 클라우드 서명 서비스에서 키를 가져올 수 있습니다. Windows 인증서 저장소 제공자만은 당연한 이유로 Windows 전용으로 남아 있습니다.

EXE뿐만이 아닙니다

이는 sgcSign의 모든 코드 서명 컴포넌트에 동일하게 적용됩니다. EXE와 DLL을 위한 TsgcAuthenticodeSigner, .cat 카탈로그를 위한 TsgcCatalogSigner, Windows Installer .msi.msp 파일을 위한 TsgcMSISigner, .msix.appx 패키지를 위한 TsgcAppxSigner, PowerShell 스크립트를 위한 TsgcPowerShellSigner, 그리고 이들을 검사하는 TsgcAuthenticodeVerifier까지 모두 Linux, macOS, iOS, Android에서 빌드되고 실행됩니다.

서버와 명령줄에서도

같은 내용이 라이브러리를 넘어서도 적용됩니다. Linux용으로 빌드된 sgcSign Server는 Windows에서와 정확히 동일하게 Authenticode, PowerShell, 카탈로그, MSI, MSIX 요청에 서명하고 검증하며, sgcsign 명령줄 도구는 어떤 플랫폼에서든 이를 전송합니다. Delphi 명령줄 도구에서는 EXE나 DLL을 로컬에서 해싱한 뒤 해시만 전송하는 경로가 여전히 Windows 전용입니다. .NET 명령줄 도구는 모든 플랫폼에서 이 경로를 사용합니다.

제공 현황

Linux, macOS, iOS, Android에서의 코드 서명 기능은 Delphi용 sgcSign 2026.10에 포함되어 제공됩니다. Windows 프로그램에서는 달라지는 것이 없습니다. 동일한 컴포넌트, 동일한 속성, 동일한 결과입니다. 모든 속성은 sgcSign 온라인 도움말에 문서화되어 있습니다.

질문이 있으신가요? 아니면 Windows에서 벗어나고 싶은 파이프라인이 있으신가요? 문의하기를 이용해 주시면, 실제로 코드를 작성한 사람에게서 답변을 받으실 수 있습니다.