Description
J’ai dû récemment rechercher une solution permettant de signer numériquement un document PDF. Pensant que je ne suis pas le seul à avoir ce genre de problématique, je mets à la disposition du public le résultat de mes tests.
Cela permet de générer automatiquement ce genre d’image dans le document PDF, surtout de le signer numériquement et donc d’interdire toute modification en précisant de façon certaine « l’émetteur » (le signataire du document). Il apparait sur cette image le texte Validité inconnue la cause de cela est l’utilisation d’un « certificat auto-signé » pour signer ce PDF.
Ou « Signature Valable » si vous utilisez un certificat avec une autorité racine valide (Verisign, ..)
Outils utilisés
- Acrobat reader 7.x ou sup Pour visualiser le document généré
- iText 2.x Pour générer et signer le document
- bouncycastle.org pour itext (bcprov)
- Un certificat PKCS12 (acheté ou généré) attention si vous générez vous même votre clef il n’est pas possible de valider la chaine des certificat (ce qui est normal)
Exemple (code source)
Comme vous pouvez le voir le code java pour réaliser cela est très simple il est divisé en deux parties :
- la méthode buildPDF : qui permet de construire un document PDF , (c’est une option)
- la méthode signPdf : qui signe le document PDF
package com.berthou.test.pdf ;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import com.lowagie.text.*;
import com.lowagie.text.pdf.*;
public class sign_pdf {
/**
* Nom du document PDF généré non signé
*/
static String fname = "D:\\HelloWorld.pdf" ;
/**
* Nom du document PDF généré signé
*/
static String fnameS = "D:\\HelloWorld_sign.pdf" ;
public static void main(String[] args) {
try {
sign_pdf.buildPDF() ;
sign_pdf.signPdf() ;
}
catch(Exception e) { }
}
/**
* Création d'un simple document PDF "Hello World"
*/
public static void buildPDF() {
// Creation du document
Document document = new Document();
try {
// Creation du "writer" vers le doc
// directement vers un fichier
PdfWriter.getInstance(document,
new FileOutputStream(fname));
// Ouverture du document
document.open();
// Ecriture des datas
document.add(new Paragraph("Hello World"));
} catch (DocumentException de) {
System.err.println(de.getMessage());
} catch (IOException ioe) {
System.err.println(ioe.getMessage());
}
// Fermeture du document
document.close();
}
/**
* Signature du document
*/
public static final boolean signPdf()
throws IOException, DocumentException, Exception
{
// Vous devez preciser ici le chemin d'acces a votre clef pkcs12
String fileKey = "C:\\MonRep\\MaClef.p12" ;
// et ici sa "passPhrase"
String fileKeyPassword = "MonPassword" ;
try {
// Creation d'un KeyStore
KeyStore ks = KeyStore.getInstance("pkcs12");
// Chargement du certificat p12 dans el magasin
ks.load(new FileInputStream(fileKey), fileKeyPassword.toCharArray());
String alias = (String)ks.aliases().nextElement();
// Recupération de la clef privée
PrivateKey key = (PrivateKey)ks.getKey(alias, fileKeyPassword.toCharArray());
// et de la chaine de certificats
Certificate[] chain = ks.getCertificateChain(alias);
// Lecture du document source
PdfReader pdfReader = new PdfReader((new File(fname)).getAbsolutePath());
File outputFile = new File(fnameS);
// Creation du tampon de signature
PdfStamper pdfStamper;
pdfStamper = PdfStamper.createSignature(pdfReader, null, '\0', outputFile);
PdfSignatureAppearance sap = pdfStamper.getSignatureAppearance();
sap.setCrypto(key, chain, null, PdfSignatureAppearance.SELF_SIGNED);
sap.setReason("Test SignPDF berthou.mc");
sap.setLocation("");
// Position du tampon sur la page (ici en bas a gauche page 1)
sap.setVisibleSignature(new Rectangle(10, 10, 50, 30), 1, "sign_rbl");
pdfStamper.setFormFlattening(true);
pdfStamper.close();
return true;
}
catch (Exception key) {
throw new Exception(key);
}
}
} |
Exemple de document généré
HelloWorld_sign.pdf
Un complément à cette article est proposé dans MS CAPI et Java (JCE SunMSCAPI).
Merci Raymond !
Salut…
Bon, je ne connais pas du tout, mais comme je me suis penché sur ton source :-).
Merci pour tout d’abord..
Par contre, je rencontre l’erreur suivante (sous XP avec Java edition 6) :
Exception in thread « main » java.lang.NoClassDefFoundError:
Peut etre rien, mais un peu handicapant :-).
Si quelqu’un peut m’aider…
Stephane
Salut,
je progresse, et j’utilise javac :-)
(si le modo peut annuler le message précedent…).
Stephane
Stephane, Normalement tu as uniquement un problème de classpath et je pense que tu as déjà résolue ce problème. Par contre je te conseille d’utiliser un IDE pour le développement Java et surtout la plateforme Eclipse.
salut,
en cherchons j’ai effectivement compris ce probleme.
Petite question rberthou : est il nécessaire de rajouter des packages a ton source ? si oui, ou est ce que je peux les downloader ?
En ce qui concerne Eclipse, je vais essayer de voir comment ca marche.
Merci pour les conseils :-)
Steph
Oui bien sur, les packages sont précisés dans l’article (itext et bouncycastle)
Bonjour,
Merci pour avoir poster ce code, je l’ai fait marche.
Cependant, pour certains PDF existant que j’essaie de signer avec ce code, j’obtiens l’erreur suivante :
java.lang.IllegalArgumentException: PdfReader not opened with owner password
at com.lowagie.text.pdf.PdfStamperImp.(Unknown Source)
at com.lowagie.text.pdf.PdfStamper.(Unknown Source)
at com.lowagie.text.pdf.PdfStamper.createSignature(Unknown Source)
at com.lowagie.text.pdf.PdfStamper.createSignature(Unknown Source)
As-tu déjà rencontré ce problème ?
Merci, @+
Tony.
Non, mais le message est je pense clair, le document est protégé et nécessite le mot de passe. cela semble être une nouvelle sécurité ajoutée à partir de iText 2.03 ( From iText version 2.0.3 the password restrictions are enforced by the library instead of passing that responsability to the developer. Using PdfStamper or importing pages in PdfStamper, PdfCopy and PdfWriter will throw an exception if the PDF was not opened with the owner password.)
Ok merci, j’aurais dû m’en douter… ;)
bonjour et merci à R. Berthou,
L’ajout d’une signature supplémentaire en utilisant le flag « append » à true (ci-après) ne fonctionne pas chez moi.. quelqu’un a-t-il une piste ?
pdfStamper = PdfStamper.createSignature(pdfReader, null, », outputFile, true);
bon, en fait ça marche bien, j’avais juste fait une boulette sur eclipse ;-( j’arrive bien à ajouter une signature à un document déjà signé
Moi, j’ai fait un tampon avec PowerPoint que ‘jinsère systématiquement sur les documents par la fonction OUTILS/COMMENTAIRES ET ANNOTATIONS/TAMPONS puis je choisi mon tampon perso que je doi mettre systématiquement au même endroit sur le document.
Y a t’il possibilité par le script ci-dessus de faire cela en le modifiant ?
Emmanuel
Il est possible d’associer une image à cette signature numérique (cette image peux représenter ce que l’on souhaite).
Le grand plus de cette signature numérique par rapporta une simple insertion d’image est dans la certification de la source du document.
Bonjour et merci pour le partage de l’information et du code source.
Je fais des recherches depuis pas mal de temps sur la signature numérique d’un document pdf.
Je suis pas un pro de java mais j’espère pouvoir y arriver. Moi je suis plutot Wisual Basic & Windev (oui oui je sais c’est minable … ;-) ). Bon je vais essayer de télécharger Eclipse et vogue la galère ! ;-)
Encore merci, et je referais si tu veux bien appel à toi en cas de problème !
Bonnes fêtes de fin d’année, ciao
Une autre question, saurais-tu s’il y a possibilité d’insérer la signature numérique dans le document pdf (avec le certificat et tout et tout) mais sans java ?
Tony , oui cela est faisable sans problème dans le cas de VisualBasic je pense qu’il doit exister sous forme d’un composant.
Tu peux déjà regarder du coté de iText version .net
http://itextsharp.sourceforge.net/
Je peux également créer une version autonome (ligne de commande) de ce petit exemple pour signer un document (il prendrais alors une liste de valeurs en paramètes – certificat, fichier a signer, destination, …).
acrobat reader permet egalement de signer un document via son interface.
Waouh t’es vraiment un chef…
Merci de cette réponse, je me sens moins seul pour résoudre mon problème.
Si tu pouvais me faire un outil en ligne de commande je suis preneur oui. Tu peux me contacter directement sur l’adresse mail que je viens de te joindre.
Comment faire via Adobe Reader pour signer un document ? Sérieux après moultes manipulations, je ne vois pas ! :-(
Merci et n’hésite pas à me contacter par mail
ReBonjour,
J’ai vu qu’il était possible de signer un document en allant dans Document/Signer via Acrobat Reader. Mais cette zone est grisée de chez moi. Saurais-tu pourquoi ? Merci
Quels sont les formats de certificats supportés par cette signature? .pfx ? .pcs ?
Merci de ton aide
salut les gens tous sa c’est super pratique mais j’aurais aimé pouvoir visualisé mon pdf signé sur iPhone , le problème c’est qu’il n’y a pas adobe reader sur iPhone et que l’outil de visualisation par défaut (jme souvient plus son nom) n’affiche pas la signature.
Y a t-il une solution pour rendre le pdf généré plus « generique » lol ? avec un peu de chance c’est juste quelques petites modif au niveau de la méthode signPdf().
Bonjour,
Juste où quelqu’un se demanderait comment enlever une signature… pour par exemple imprimer le PDF dans une imprimante professionnelle qui n’aime pas du tout les signatures… voici un code Itext!
Par contre, est-ce bien légale d’enlever une signature numérique sur un document officiel ?
Oui pour des raisons d’interopérabilité entre mon PDF et mon imprimante… je prends le droit! :)
–
import java.io.FileOutputStream;
import java.io.IOException;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfImportedPage;
import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.PdfWriter;
/**
* Class that modify some properties… (like Unsign pdf…)
*
* @author nexus6@altern.org
* @version 1.0 11/12/2006
*/
public class PDFmodify
{
/**
* Generates a PDF file
*
* @param args
*/
public static void main(String[] args)
{
System.out.println(« Usage: PDFmodify file_in file_out »);
try
{
// we create a reader for a certain document
PdfReader reader = new PdfReader(args[0]);
// we retrieve the total number of pages
int n = reader.getNumberOfPages();
System.out.println(« There are » + n + » pages in the document. »);
// we retrieve the size of the first page
Rectangle psize = reader.getPageSize(1);
// float width = psize.width();
// float height = psize.height();
// step 1: creation of a document-object
Document document = new Document(psize);
try
{
// PdfContentByte pdfc = reader.getPageContent(1);
// step 2:
// we create a writer that listens to the document
// and directs a PDF-stream to a file
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(args[1]));
// step 3: we open the document
document.open();
// step 4: we add content
PdfContentByte cb = writer.getDirectContent();
int i = 0;
while (i < n)
{
i++;
//if (i==5) break;
document.newPage();
PdfImportedPage page = writer.getImportedPage(reader, i);
cb.addTemplate(page, 0, 0);
System.err.println(« processed page » + i);
}
System.out.println(« done! »);
}
catch (DocumentException de)
{
System.err.println(de.getMessage());
}
catch (IOException ioe)
{
System.err.println(ioe.getMessage());
}
// step 5: we close the document
document.close();
}
catch (Exception de)
{
de.printStackTrace();
}
}
}
Bonjour,
Je tiens à vous remercier pour votre code.
Je me demande comment vous avez générer les clés et les certificats pour le test?
J’ai essayé avec keytool mais le keystore est de type JKS … et sur l’api de iText ils travaille avec pkcs12
Merci d’avance
@Nouara : Le plus simple consiste a passer par openssl (il y a pas mal de bonne doc sur le net). Tu dois, également, pouvoir faire cela avec putty outils puttygen.exe .
Tout d’abord un grand merci pour ce code. Je voudrais juste savoir s’il est possible de rendre les informations écrites par la signature en français (Si oui comment ?). Ensuite comment disposer une image sur le coté et non en background. Enfin le message signature valable ou bien validité inconnue, pourrait -on l’enlever définitivement. Merci
salut merci pour ton code mais j’arrive pas a l’exécuté ,il m’affiche :
Exception in thread « main » java.lang.NoClassDefFoundError: DocumentException
at java.lang.Class.getDeclaredMethods
je pense c’est un probleme de classe itest ex … mais j’arrive pas a importer ce packge
Merci
Désolé pour le retard dans mes réponses, une longue absence trop de travail ….
@laro : c’est un simple problème de classpath tu dois ajouter a ton environnement d’exécution les libs requisent par ce projet (iText, bouncycastle, … ) normalement en passant par eclipse cela est relativement facile.
Alors je sais pas si c’est moi qui suis nul ou quoi mais impossible de faire marcher ton bout de code pendant plusieurs heures…. Finalement j’ai trouvé LE jar de bouncycastle qui ne faisait pas planter le programme et je vous file le lien parce que j’ai quand même pas mal galéré pour le trouver…
http://www.jarfinder.com/index.php/jars/versionInfo/42780
En tout cas merci pour ton bout de code :)
En fait je rectifie le tir… après avoir parcouru quelques sites, il y a des versions de bouncy castle qui ne sont compatibles qu’avec certaines versions d’itext… donc il faut souvent en tester plusieurs avant de trouver la bonne… :)
Pas compris tous les remarques à propos de bouncycastle. Où est-il fait appel à bouncycastle ?
Où trouver les .jar compatibles, iText et bouncycastle, les versions de ces .jar précisément
Merci
Ajouter une réponse