Table of Contents

Signing PDFs (Delphi)

Note

This demo is available in your FlexCel installation at <FlexCel Install Folder>\Demo\Delphi\Modules\25.Printing and Exporting\35.Signing Pdfs and also at https:​//​github.​com/​tmssoftware/​TMS-​FlexCel.​VCL-​demos/​tree/​master/​Delphi/​Modules/​25.​Printing and Exporting/35.Signing Pdfs

Overview

In this example we will show how to add a visible or invisible signature to a generated PDF file.

Concepts

  • In order to sign a PDF file you will need a certificate issued by a valid Certificate Authority, or one issued by yourself. In this example we will use a self signed certificate. This certificate will not validate by default when you open it in Acrobat, you need to add it to your trusted list.

  • As SHA-1 is deprecated, FlexCel will default to using SHA512 for the signature. You could use a different algorithm by providing an OID in the EncryptionFactory.GetSigner call.

  • In order to sign a file, FlexCel will write a requirement for Acrobat 8 or newer in the generated files. This is because only Acrobat 8 or newer support SHA512. Older versions of acrobat will still display the pages but will not validate the signature.

  • FlexCel currently only has support for signing in Windows, using CryptoAPI. You can still create your own signature engine for other platforms by using a third party cryptography library or by calling the native crypto functions in that platform, the same way we call CryptoAPI. This is explained in the section Signing PDF Files in the PDF exporting guide.

Files

USigningPdfs.pas

unit USigningPdfs;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Dialogs,
  StdCtrls,
  pngimage, ExtCtrls, ExtDlgs,
  {$if CompilerVersion >= 23.0} System.UITypes, {$IFEND}
  FlexCel.VCLSupport, FlexCel.Core, FlexCel.XlsAdapter, FlexCel.Render, FlexCel.Pdf;


type
  TFSigningPdfs = class(TForm)
    btnCreateAndSignPdf: TButton;
    cbVisibleSignature: TCheckBox;
    SignaturePicture: TImage;
    OpenPictureDialog: TOpenPictureDialog;
    OpenExcelDialog: TOpenDialog;
    SavePdfDialog: TSaveDialog;
    procedure cbVisibleSignatureClick(Sender: TObject);
    procedure SignaturePictureClick(Sender: TObject);
    procedure btnCreateAndSignPdfClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    DataPath: string;
    ImgData: ByteArray;
    procedure LoadImage(const FileName: string);

    { Private declarations }
  public
    { Public declarations }
  end;

var
  FSigningPdfs: TFSigningPdfs;

implementation
uses IOUtils, UFlexCelHDPI, ShellAPI;

{$R *.dfm}
procedure TFSigningPdfs.LoadImage(const FileName: string);
begin
  ImgData := TFile.ReadAllBytes(FileName);
  SignaturePicture.Picture.LoadFromFile(FileName);
end;

procedure TFSigningPdfs.FormCreate(Sender: TObject);
begin
  DataPath := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), '..\..\');
  LoadImage(DataPath + 'sign.png');
end;


procedure TFSigningPdfs.btnCreateAndSignPdfClick(Sender: TObject);
var
  xls: TExcelFile;
  pdf: TFlexCelPdfExport;
  Cert: TX509Certificate2;
  Signer: TCmsSigner;
  Signature: TPdfSignature;

begin
  //Load the Excel file.
  if (not OpenExcelDialog.Execute) then exit;
  xls := TXlsFile.Create;
  try
    xls.Open(OpenExcelDialog.FileName);

    //Export it to pdf.
    pdf := TFlexCelPdfExport.Create(xls, true);
    try
      pdf.FontEmbed := TFontEmbed.Embed;

      //Load the certificate and create a signer.
      Cert := EncryptionFactory.GetX509Certificate(TFile.ReadAllBytes(DataPath + 'flexcel.pfx'), 'password');
      try
        // The current implementation uses only one certificate. The algorithm by
        // default if you leave the second parameter empty is SHA512.
        Signer := EncryptionFactory.GetSigner(TArray<TX509Certificate2>.Create(Cert), '');
        try
          if (cbVisibleSignature.Checked) then
          begin
            //The -1 as "page" parameter means the last page.
            Signature := TPdfVisibleSignature.Create(TBuiltInSignerFactory.Create(Signer),
                            'Signature',
                            'I have read the document and certify it is valid.',
                            'Springfield',
                            'adrian@tmssoftware.com',
                            -1,
                            TUIRectangle.Create(50, 50, 140, 70),
                            ImgData);
            Signer := nil; //The signature now owns the Signer so we don't want to free it.



          end
          else
          begin
            Signature := TPdfSignature.Create(TBuiltInSignerFactory.Create(Signer),
                                          'Signature',
                                          'I have read the document and certify it is valid.',
                                          'Springfield',
                                          'adrian@tmssoftware.com');
            Signer := nil; //The signature now owns the Signer so we don't want to free it.
          end;
        Except
          Signer.Free;  //Only if there is an error.
          raise;
        end;

        //You must sign the document *BEFORE* starting to write it.
        pdf.Sign(Signature); //Now the pdf owns the signature. There is no need to free it.

        if (not SavePdfDialog.Execute) then exit;
        pdf.ExportAllVisibleSheets(SavePdfDialog.FileName, false, 'Signed Pdf');
      finally
        Cert.Free;
      end;
    finally
      pdf.Free;
    end;
  finally
    xls.Free;
  end;
                    
  if MessageDlg('Do you want to open the generated file?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    ShellExecute(0, 'open', PCHAR(SavePdfDialog.FileName), nil, nil, SW_SHOWNORMAL);
  end;

end;

procedure TFSigningPdfs.cbVisibleSignatureClick(Sender: TObject);
var
  delta: integer;
begin
  SignaturePicture.Visible := cbVisibleSignature.Checked;
  Delta := SignaturePicture.Height + 30;
  if (cbVisibleSignature.Checked) then Height := Height + delta else Height := Height - delta;
end;

procedure TFSigningPdfs.SignaturePictureClick(Sender: TObject);
begin
  if (not OpenPictureDialog.Execute) then exit;
  LoadImage(OpenPictureDialog.FileName);
end;

end.