当前位置: 首页 > 工具软件 > SignPDF > 使用案例 >

pdf手写签名

华知
2023-12-01

完美解决pdf手写签名(纯手写水印图片)

首先你需要生成证书公私钥,后面pdf盖章时需要用到。

public class Extension {

    private String oid;

    private boolean critical;

    private byte[] value;

    public String getOid() {
        return oid;
    }

    public byte[] getValue() {
        return value;
    }
    public boolean isCritical() {
        return critical;
    }
}

需要的一个实体类

import java.security.KeyPair;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
import org.bouncycastle.asn1.x509.DistributionPoint;
import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

public class Pkcs {

private static KeyPair getKey() throws NoSuchAlgorithmException {
    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA",
            new BouncyCastleProvider());
    generator.initialize(1024);
    // 证书中的密钥 公钥和私钥
    KeyPair keyPair = generator.generateKeyPair();
    return keyPair;
}

/**
 * @param password
 *            密码
 * @param issuerStr 颁发机构信息
 * 
 * @param subjectStr 使用者信息
 * 
* @param certificateCRL 颁发地址
 * 
 * @return
 */
public static Map<String, byte[]> createCert(String password,
        String issuerStr, String subjectStr, String certificateCRL) {
    Map<String, byte[]> result = new HashMap<String, byte[]>();
    ByteArrayOutputStream out = null;
    try {
        // 生成JKS证书
        // KeyStore keyStore = KeyStore.getInstance("JKS");
        // 标志生成PKCS12证书
        KeyStore keyStore = KeyStore.getInstance("PKCS12",
                new BouncyCastleProvider());
        keyStore.load(null, null);
        KeyPair keyPair = getKey();
        // issuer与 subject相同的证书就是CA证书
        Certificate cert = generateCertificateV3(issuerStr, subjectStr,
                keyPair, result, certificateCRL, null);
        // cretkey随便写,标识别名
        keyStore.setKeyEntry("cretkey", keyPair.getPrivate(),
                password.toCharArray(), new Certificate[] { cert });
        out = new ByteArrayOutputStream();
        cert.verify(keyPair.getPublic());
        keyStore.store(out, password.toCharArray());
        byte[] keyStoreData = out.toByteArray();
        result.put("keyStoreData", keyStoreData);
        return result;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
            }
        }
    }
    return result;
}

/**
 * @param issuerStr
 * @param subjectStr
 * @param keyPair
 * @param result
 * @param certificateCRL
 * @param extensions
 * @return
 */
public static Certificate generateCertificateV3(String issuerStr,
        String subjectStr, KeyPair keyPair, Map<String, byte[]> result,
        String certificateCRL, List<Extension> extensions) {
    ByteArrayInputStream bout = null;
    X509Certificate cert = null;
    try {
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        Date notBefore = new Date();
        Calendar rightNow = Calendar.getInstance();
        rightNow.setTime(notBefore);
        // 日期加1年
        rightNow.add(Calendar.YEAR, 1);
        Date notAfter = rightNow.getTime();
        // 证书序列号
        BigInteger serial = BigInteger.probablePrime(256, new Random());
        X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
                new X500Name(issuerStr), serial, notBefore, notAfter,
                new X500Name(subjectStr), publicKey);
        JcaContentSignerBuilder jBuilder = new JcaContentSignerBuilder(
                "SHA1withRSA");
        SecureRandom secureRandom = new SecureRandom();
        jBuilder.setSecureRandom(secureRandom);
        ContentSigner singer = jBuilder.setProvider(
                new BouncyCastleProvider()).build(privateKey);
        // 分发点
        ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier(
                "2.5.29.31");
        GeneralName generalName = new GeneralName(
                GeneralName.uniformResourceIdentifier, certificateCRL);
        GeneralNames seneralNames = new GeneralNames(generalName);
        DistributionPointName distributionPoint = new DistributionPointName(
                seneralNames);
        DistributionPoint[] points = new DistributionPoint[1];
        points[0] = new DistributionPoint(distributionPoint, null, null);
        CRLDistPoint cRLDistPoint = new CRLDistPoint(points);
        builder.addExtension(cRLDistributionPoints, true, cRLDistPoint);
        // 用途
        ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier(
                "2.5.29.15");
        // | KeyUsage.nonRepudiation | KeyUsage.keyCertSign
        builder.addExtension(keyUsage, true, new KeyUsage(
                KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
        // 基本限制 X509Extension.java
        ASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier(
                "2.5.29.19");
        builder.addExtension(basicConstraints, true, new BasicConstraints(
                true));
        // privKey:使用自己的私钥进行签名,CA证书
        if (extensions != null)
            for (Extension ext : extensions) {
                builder.addExtension(
                        new ASN1ObjectIdentifier(ext.getOid()),
                        ext.isCritical(),
                        ASN1Primitive.fromByteArray(ext.getValue()));
            }
        X509CertificateHolder holder = builder.build(singer);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        bout = new ByteArrayInputStream(holder.toASN1Structure()
                .getEncoded());
        cert = (X509Certificate) cf.generateCertificate(bout);
        byte[] certBuf = holder.getEncoded();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        // 证书数据
        result.put("certificateData", certBuf);
        //公钥
        result.put("publicKey", publicKey.getEncoded());
        //私钥
        result.put("privateKey", privateKey.getEncoded());
        //证书有效开始时间
        result.put("notBefore", format.format(notBefore).getBytes("utf-8"));
        //证书有效结束时间
        result.put("notAfter", format.format(notAfter).getBytes("utf-8"));
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (bout != null) {
            try {
                bout.close();
            } catch (IOException e) {
            }
        }
    }
    return cert;
}

public static void main(String[] args) throws Exception{
    // CN: 名字与姓氏    OU : 组织单位名称
    // O :组织名称  L : 城市或区域名称  E : 电子邮件
    // ST: 州或省份名称  C: 单位的两字母国家代码 
    String issuerStr = "CN=在线医院,OU=gitbook研发部,O=gitbook有限公司,C=CN,E=gitbook@sina.com,L=北京,ST=北京";
    String subjectStr = "CN=huangjinjin,OU=gitbook研发部,O=gitbook有限公司,C=CN,E=huangjinjin@sina.com,L=北京,ST=北京";
    String certificateCRL  = "https://gitbook.cn";
    Map<String, byte[]> result = createCert("123456", issuerStr, subjectStr, certificateCRL);

    FileOutputStream outPutStream = new FileOutputStream("d:/keystore.p12"); // ca.jks
    outPutStream.write(result.get("keyStoreData"));
    outPutStream.close();
    FileOutputStream fos = new FileOutputStream(new File("d:/keystore.cer"));
    fos.write(result.get("certificateData"));
    fos.flush();
    fos.close();
}
}

运行上面的代码生成两个文件,主要是.p12结尾的文件后面主要会用到

以下是一个死的图片水印

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import com.itextpdf.awt.AsianFontMapper;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfStream;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.PrivateKeySignature;


public class SignHighPdf {

/**
 * @param password
 *            秘钥密码
 * @param keyStorePath
 *            秘钥文件路径
 * @param signPdfSrc
 *            签名的PDF文件
 * @param x
 * 
 * @param y
 * @return
 */
public static byte[] sign(String password, String keyStorePath, String signPdfSrc, 
        float x, float y,
        String signText) {
    File signPdfSrcFile = new File(signPdfSrc);
    PdfReader reader = null;
    ByteArrayOutputStream signPDFData = null;
    PdfStamper stp = null;
    FileInputStream fos = null;
    try {
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        KeyStore ks = KeyStore.getInstance("PKCS12", new BouncyCastleProvider());
        fos = new FileInputStream(keyStorePath);
        ks.load(fos, password.toCharArray()); // 私钥密码
        String alias = (String) ks.aliases().nextElement();
        PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
        Certificate[] chain = ks.getCertificateChain(alias);
        reader = new PdfReader(signPdfSrc);
        signPDFData = new ByteArrayOutputStream();
        // 临时pdf文件
        File temp = new File(signPdfSrcFile.getParent(), System.currentTimeMillis() + ".pdf");
        stp = PdfStamper.createSignature(reader, signPDFData, '\0', temp, true);
        PdfSignatureAppearance sap = stp.getSignatureAppearance();
        sap.setReason("数字签名,不可改变");
        // 是对应x轴和y轴坐标
        sap.setVisibleSignature(new Rectangle(x, y, x + 150, y + 65), 1,
                "sr"+String.valueOf(System.nanoTime()));
        /layer 0 Creating the appearance for layer 0
        PdfTemplate n0 = sap.getLayer(0);
        n0.reset();
        float lx = n0.getBoundingBox().getLeft();
        float by = n0.getBoundingBox().getBottom();
        float width = n0.getBoundingBox().getWidth();
        float height = n0.getBoundingBox().getHeight();
        n0.setRGBColorFill(255, 0, 0);
        n0.rectangle(lx, by, 5, height);
        n0.rectangle(lx, by, width, 5);
        n0.rectangle(lx, by+height-5, width, 5);
        n0.rectangle(lx+width-5, by, 5, height);
        n0.fill();
        ///layer 2
        PdfTemplate n2 = sap.getLayer(2);
        n2.setCharacterSpacing(0.0f);
        ColumnText ct = new ColumnText(n2);
        ct.setSimpleColumn(n2.getBoundingBox());
        n2.setRGBColorFill(255, 0, 0);
        //做一个占位的动作
        Paragraph p1 = new Paragraph(" ");
        BaseFont bf = BaseFont.createFont(AsianFontMapper.ChineseSimplifiedFont, AsianFontMapper.ChineseSimplifiedEncoding_H, 
                BaseFont.NOT_EMBEDDED);
        Font font1 = new Font(bf, 5, Font.BOLD, BaseColor.RED);
        Font font2 = new Font(bf, 13, Font.BOLD, BaseColor.RED);
        p1.setFont(font1);
        ct.addElement(p1);
        Paragraph p = new Paragraph(signText);
        p.setAlignment(Element.ALIGN_CENTER);
        p.setFont(font2);
        ct.addElement(p);
        ct.go();
        stp.getWriter().setCompressionLevel(PdfStream.BEST_COMPRESSION);
        ExternalDigest digest = new BouncyCastleDigest();
        ExternalSignature signature = new PrivateKeySignature(key, DigestAlgorithms.SHA512, provider.getName());
        MakeSignature.signDetached(sap, digest, signature, chain, null, null, null, 0, CryptoStandard.CADES);
        stp.close();
        reader.close();
        return signPDFData.toByteArray();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (signPDFData != null) {
            try {
                signPDFData.close();
            } catch (IOException e) {
            }
        }
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
            }
        }
    }
    return null;
}

public static void main(String[] args) throws Exception {
    //高清签章
    byte[] fileData = sign("123456", "d:/keystore.p12",
            "d:/xxx.pdf", 350, 290, "华佗\n2017-12-20");
    FileOutputStream f = new FileOutputStream(new File("d:/new.pdf"));
    f.write(fileData);
    System.out.println("签名成功");
    f.close();
}

}

需要更完美的实现代码,请到我的主页资源中查看(纯手写签名哦)
利用图片签名技术,保存并水印到pdf指定位置

下载链接:https://download.csdn.net/download/weixin_42786641/11214592
 类似资料: