| Digital Signature
 
 Printed From: Debenu Quick PDF Library - PDF SDK Community Forum
 Category:  For Users of the Library
 Forum Name:  I need help - I can help
 Forum Description:  Problems and solutions while programming with the Debenu Quick PDF Library and Debenu PDF Viewer SDK
 URL: http://www.quickpdf.org/forum/forum_posts.asp?TID=3995
 Printed Date: 31 Oct 25 at 4:22PM
 Software Version: Web Wiz Forums 11.01 - http://www.webwizforums.com
 
 
 Topic: Digital Signature
 Posted By: MauricioPt
 Subject: Digital Signature
 Date Posted: 31 Oct 22 at 2:50PM
 
 
        
          | Hi, 
 i have translated the sample source code from C# and VB from here :
 https://www.debenu.com/kb/advanced-options-signing-pdf-files/
 into Delphi language.
 
 uses
 Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, DebenuPDFLibrary,
 cyStrUtils, cyDebenuPDFLibrary, idGlobal, IdCoderMIME,
 
 ssl_const, ssl_types, ssl_init, ssl_err, ssl_evp, ssl_bio, ssl_pem, ssl_engine, ssl_x509, ssl_pkcs7, ssl_pkcs12, ssl_rsa;
 
 procedure TFrmPrinDemoPdf.SignDataWithPfxFile(const AFileName, Password: String; const DataToSign: AnsiString; var Result: ansistring);
 var
 LM: TMemoryStream;
 AnsiPassword: AnsiString;
 PAnsiPassword: PAnsiChar;
 
 B: PBIO;
 P12: PPKCS12;
 
 // Pfx extaction :
 pCertificate: PX509;
 pkey: PEVP_PKEY;
 ca: PSTACK_OF_X509;
 md: PEVP_MD;
 
 // Hash :
 mdctx: EVP_MD_CTX;
 AnsiDataToHash: AnsiString;
 LengthAnsiDataToHash: integer;
 HashBuffer: Array of AnsiChar;    //  Array [0..4095] of AnsiChar;
 HashBufferF: Array [0..4095] of AnsiChar;
 c, HashSize: integer;
 
 // Sign :
 AnsiDataToSign: AnsiString;
 LengthAnsiDataToSign: integer;
 ppKeyCtx: PPEVP_PKEY_CTX;
 pEngin: PENGINE;
 SignBuffer: Array of AnsiChar;    //  Array [0..4095] of AnsiChar;
 SignSize: cardinal;
 
 // Test
 bp, b64, mbp: PBIO;
 signFile: string;
 _RSA: PRSA;
 
 // PKCS7 :
 _pPKCS7: PPKCS7;
 _flags: integer;
 _certs: PSTACK_OF;
 InputBioFile, OutputBioFile: PBIO;
 
 aIdBytes: TIdBytes;
 pmdctx: PEVP_MD_CTX;
 // f: file;
 begin
 Result := '';
 
 B := nil;
 P12 := nil;
 pkey := Nil;
 pCertificate := Nil;
 ca := Nil;
 
 LM := nil;
 AnsiPassword := Copy(Password, 1, Length(Password));
 PAnsiPassword := PAnsichar(AnsiPassword);
 //ShowMessage( intToStr(Length(AnsiPassword)) );
 
 // raise exception SSL_InitLib;
 SSL_InitPEM;
 SSL_InitERR;
 SSL_InitBIO;
 SSL_InitEVP;
 
 // Ajouté !
 //SSL_InitRSA;
 SSL_InitPKCS7;
 //SSL_InitX509;
 //SSL_InitPKCS12;
 
 // Exception SSL_InitENGINE;
 SSL_InitX509;
 SSL_InitPKCS12;
 
 // Exception ENGINE_load_builtin_engines;
 OpenSSL_add_all_ciphers;
 OpenSSL_add_all_digests;
 
 //  OpenSSL_add_all_algorithms;
 ERR_load_crypto_strings;
 
 try
 LM := TMemoryStream.Create;
 LM.LoadFromFile(AFileName);
 except
 LM.Free;
 Exit;
 end;
 
 try
 B := BIO_new_mem_buf(LM.Memory, LM.Size);
 if not Assigned(B) then
 begin
 Exit;
 end;
 
 // * Créer le PPKCS12 * //
 try
 P12 := ssl_pkcs12.d2i_PKCS12_bio(B, nil);
 if Assigned(P12) then
 begin
 try
 // * Vérifier le mot de passe (!!! Ne fonctionne que si nous avons appelé tous les SSL_Init + OpenSSL_add !!!) * //
 if PKCS12_verify_mac(P12, PAnsiChar(AnsiPassword), Length(AnsiPassword)) = 1 then   // 0 s'il y a un pb ...      PAnsiChar(AnsiPassword) ou PAnsiPassword ...
 begin
 // * Récupérer le certificat X509 * //
 PKCS12_parse(p12, PAnsiChar(AnsiPassword), @pkey, @pCertificate, @ca);       // PAnsiChar(AnsiPassword) ou PAnsiPassword ...
 
 if Assigned(pCertificate) then
 begin
 // INFO :              ShowMessage(pCertificate^.name);
 end;
 
 // * Sign * //
 if Assigned(pkey) and CBSignProcessPassthroughSHA1.Checked then
 try
 // Create an SHA-1 hash of the file data //
 md := EVP_get_digestbyname('SHA1');         //  EVP_sha256;
 // Pas besoin ... New(mdctx);
 
 if EVP_SignInit(@mdctx, md) = 1 then      // Ou directement EVP_SignInit(@md_ctx, EVP_SHA1);
 try
 // SSL_CheckError;
 AnsiDataToHash := DataToSign;
 LengthAnsiDataToHash := Length(AnsiDataToHash);
 
 // Tableau dynamique :
 if EVP_SignUpdate(@mdctx, PAnsiChar(AnsiDataToHash), LengthAnsiDataToHash) = 1 then
 begin
 SetLength(HashBuffer, EVP_PKEY_size(pKey));
 
 if EVP_SignFinal(@mdctx, PAnsiChar(HashBuffer), HashSize, pkey) = 1 then     // HashSize = 256 cars
 begin
 Result := '';
 c := 0;
 while c < HashSize do
 begin
 Result := Result + HashBuffer[c];
 inc(c);
 end;
 
 MemLogs.Lines.Add('Data to SHA1 : "' + Copy(AnsiDataToHash, 1, 20) + '..."');
 MemLogs.Lines.Add('HASH result : "' + Result + '"');
 string_Savetofile(Result, 'D:\_DELPHI XE10.2\_TEST SIGN\_DEBUG\QuickPDF\Certificat\SetSignProcessPassthrough 04 hash result.txt', TEncoding.ANSI);
 end;
 end;
 finally
 end;
 finally
 end;
 
 // PKCS 7 //
 if Assigned(pkey) and CBSignProcessPassthroughPkcs7.Checked then
 try
 MemLogs.Lines.Add('HASH to sign (PKCS7) : "' + string_LoadFromfile('D:\_DELPHI XE10.2\_TEST SIGN\_DEBUG\QuickPDF\Certificat\SetSignProcessPassthrough 04 hash result.txt', TEncoding.ANSI) + '"');
 
 // Range du pdf ! InputBioFile := BIO_new_file('D:\_DELPHI XE10.2\_TEST SIGN\_DEBUG\QuickPDF\Certificat\SetSignProcessPassthrough.data_to_sign.txt','rb');
 InputBioFile := BIO_new_file('D:\_DELPHI XE10.2\_TEST SIGN\_DEBUG\QuickPDF\Certificat\SetSignProcessPassthrough 04 hash result.txt','rb');      // TODO: charger via la variable Result //
 OutputBioFile := BIO_new_file('D:\_DELPHI XE10.2\_TEST SIGN\_DEBUG\QuickPDF\Certificat\SetSignProcessPassthrough 05 pkcs7 signature.p7m','wb');
 
 _certs := nil; // := ca fait augmenter la taille du pdf
 _flags := ssl_const.PKCS7_NOATTR;// or ssl_const.PKCS7_STREAM;     // _flags info: If "PKCS7_NOCERTS" is set, the signer's certificate will not be included in the PKCS7 structure
 _pPKCS7 := ssl_pkcs7.PKCS7_sign(pCertificate, pkey, _certs, InputBioFile, _flags);     // il faut appeler SSL_InitPKCS7 avant !
 ssl_pkcs7.i2d_PKCS7_bio_stream(OutputBioFile, _pPKCS7, InputBioFile, _flags);      //  SMIME_write_PKCS7( OutputBioFile, p7, InputBioFile, flags );
 
 PKCS7_free(_pPKCS7);
 BIO_Free(InputBioFile);
 BIO_Free(OutputBioFile);   // Le fichier est écrit une fois le free effectué !
 
 Result := string_LoadFromfile('D:\_DELPHI XE10.2\_TEST SIGN\_DEBUG\QuickPDF\Certificat\SetSignProcessPassthrough 05 pkcs7 signature.p7m', TEncoding.ANSI);    // TODO: Sauvegarder dans la variable Result //
 MemLogs.Lines.Add('Sign (PKCS7) result : "' + Result + '"');
 finally
 end;
 
 end
 else begin
 // ERR_get_error;
 // ERR_print_errors(B);
 end;
 finally
 PKCS12_free(P12);
 end;
 end;
 finally
 BIO_free(B);
 end;
 finally
 FreeAndNil(LM);
 end;
 end;
 
 procedure TFrmPrinDemoPdf.BtnSetSignProcessPassthroughClick(Sender: TObject);
 var
 FichierEntreePdf, FichierSortiePdf: string;
 DataToSign, ResultatSignature, LoadSignature: AnsiString;
 FichierImage, FichierCert, SignatureFieldName, Reason, Location, ContactInfo: string;
 PfxPassword: AnsiString;
 
 PDFLibrary: TDebenuPDFLibrary;
 UnlockResult, LoadResult, SignProcessID, TestByteSignature, SignatureByteLength, X, Rslt: Integer;
 hashRange: array[0..7] of integer;
 LM: TMemoryStream;
 filedata: TBytes;
 filedataLength: Integer;
 BytesRead: Integer;
 Hex: string;
 begin
 MemLogs.Lines.Clear;
 FichierCert := 'c:\certificate.pdf';
 PfxPassword := '';
 
 FichierEntreePdf := 'c:\input.pdf';
 FichierSortiePdf := 'c:\signed.pdf';
 
 // Estimate how much space we need for the signature by first signing some random data
 DataToSign := 'calc need bytes';
 SignDataWithPfxFile(FichierCert, PfxPassword, DataToSign, ResultatSignature);
 
 TestByteSignature := Length(ResultatSignature);
 MemLogs.Lines.Add('Bytes de la signature : ' + intToStr(TestByteSignature) );
 
 PDFLibrary := TDebenuPDFLibrary.Create;
 UnlockResult := PDFLibrary.UnlockKey('my licence key');
 
 if UnlockResult = 0 then
 begin
 PDFLibrary.Free;
 Exit;
 end;
 
 try
 Rslt := 0;   // Signé ?
 SignatureFieldName := 'FIELD_SIGN';
 FichierImage := 'c:\image.png';
 Reason := 'DEBUGGING ...';
 Location := 'PORTUGAL';
 ContactInfo := 'Mauricio';
 
 // SignatureByteLength := TestByteSignature * 2 + 512;
 // Number of bytes to reserve for the signature placeholder. We need double the bytes because they will will be stored in hex format, and we add 512 bytes extra margin :
 // Non car le fonction SetSignProcessPassthrough duplique déjà les bytes comme indiqué ici:
 SignatureByteLength := TestByteSignature + 256;    // SetSignProcessPassthrough already duplicates bytes !
 
 SignProcessID := PDFLibrary.NewSignProcessFromFile(FichierEntreePdf, '');
 
 if SignProcessID <> 0 then
 try
 // In this mode, the PDF is prepared using a placeholder for the signature data. The user can then replace this placeholder with the signature data of their choice
 // using the GetSignProcessByteRange function to determine the byte range that the signature hashing should be calculated over.
 // The length in bytes of the raw binary signature data. This value will be doubled when allocating the space in the PDF as the signature data should be written using hex encoding (two characters per raw byte).
 Rslt := PDFLibrary.SetSignProcessPassthrough(SignProcessID, SignatureByteLength);
 
 if Rslt = 1 then
 begin
 PDFLibrary.SetSignProcessInfo(SignProcessID, Reason, Location, ContactInfo);
 PDFLibrary.SetSignProcessField(SignProcessID, SignatureFieldName);
 PDFLibrary.SetSignProcessFieldBounds(SignProcessID, 100, 600, 200, 100);
 
 if FichierImage <> '' then
 PDFLibrary.SetSignProcessFieldImageFromFile(SignProcessID, FichierImage, 0);
 
 PDFLibrary.EndSignProcessToFile(SignProcessID, FichierSortiePdf);     // Sauvegarde le fichier sur le disque
 
 Rslt := PDFLibrary.GetSignProcessResult(SignProcessID);
 
 if Rslt = 1 then
 if FileExists(FichierSortiePdf) then
 begin
 // Byte ranges //
 for X := 0 to 7 do
 begin
 hashRange[X] := PDFLibrary.GetSignProcessByteRange(SignProcessID, X + 1);
 MemLogs.Lines.Add('hashRange[' + intToStr(X) + '] : ' + intToStr(hashRange[X]) );
 // hashRange[4] hashRange[5] hashRange[6] et hashRange[7] à 0
 end;
 
 // Read the file data into a byte array, missing out the placeholder for the signature data //
 filedataLength := hashRange[1] + (hashRange[3]);  // Visual Basic sample do -1 but not C# sample ... := hashRange[1] + (hashRange[3] - 1);
 SetLength(filedata, filedataLength);
 
 try
 LM := TMemoryStream.Create;
 LM.LoadFromFile(FichierSortiePdf);
 
 LM.Seek(hashRange[0], TSeekOrigin.soBeginning);   // Seek() Déplace la position en cours dans le flux de Offset octets par rapport à la position de départ spécifiée par Origin.
 BytesRead := LM.Read(filedata, 0, hashRange[1]);  // La méthode Read permet de lire le contenu du flux mémoire dans un tampon en partant de la position en cours
 LM.Seek(hashRange[2], TSeekOrigin.soBeginning);
 BytesRead := LM.Read(filedata, hashRange[1], hashRange[3]);
 
 // Sign the file data (generates an SHA-1 hash and signs that hash) /
 SetString(DataToSign, PAnsiChar(filedata), filedataLength);
 
 SignDataWithPfxFile(FichierCert, PfxPassword, DataToSign, ResultatSignature);
 
 // Convert the signature to hex format //
 Hex := AnsistringToHex(ResultatSignature);       // Length(Hex) = 2 x Length(ResultatSignature) = 512 ...
 
 // Write the signature into the placeholder //
 LM.Seek(hashRange[1] + 1, TSeekOrigin.soBeginning);
 filedata := BytesOf(Hex);
 LM.Write(filedata, length(filedata));
 
 LM.SaveToFile(FichierSortiePdf);
 finally
 LM.Free;
 end;
 end;
 end;
 finally
 PDFLibrary.ReleaseSignProcess(SignProcessID); // Releases a signature process from memory
 end;
 
 if Rslt = 1 then
 ShowMessage('Signé');
 finally
 PDFLibrary.Free;
 end;
 
 MemLogs.SelStart := 0;
 MemLogs.SelLength := 0;
 end;
 
 I have written SignDataWithPfxFile() function with open_ssl library.
 
 I can see the certificate but not Acrobat says that the document has been modified.
 Any help will be appreciate.
 
 |  
 
 Replies:
 Posted By: MauricioPt
 Date Posted: 31 Oct 22 at 2:52PM
 
 
        
          | I can see the certificate but Acrobat says that the document has been modified. Any help will be appreciate.
 |  
 Posted By: samukinha
 Date Posted: 23 Nov 22 at 6:08PM
 
 
        
          | Hi Mauricio What is your Debenu version? I have 13.12 version. I don't have binaries for Delphi 11 to test, if you can send me... Regards |  
 Posted By: samukinha
 Date Posted: 25 Nov 22 at 2:22PM
 
 
        
          | Hi Mauricio What is your Debenu version? I have 13.12 version. I don't have binaries for Delphi 11 to test, if you can send me... Regards |  
 Posted By: MauricioPt
 Date Posted: 28 Nov 22 at 11:49AM
 
 
        
          | I have QuickPdf v15.11 licensed. I can't send Delphi binaries.
 
 |  
 
 |