java电子签章实现

项目源码路径:https://github.com/Syske/learning-dome-code.git

前言

最近应客户需求,需要实现电子签章功能,公章部分用的时金格科技的接口,个人人签字需要自己实现,公章部分我们就不说了,商业接口做的都比较成熟,也有示例代码,所以今天着重说的就是个人签字部分。

参照公章部分的实现方式,同时也参考了很多博客1,查了很多资料,也搞清楚了电子签章的基本流程:

因为我要实现的功能很简单就是个人签章,而且我的签名是手写的,所以创建签名部分就省略了,核心部分就是确定签名坐标和签名,确定坐标部分我根据自己查找的资料,实现了根据关键字确定坐标,因为确定坐标很麻烦,也不够灵活。

对于创建签名我有一个思路,可以将创建签名作为一个远程服务部署,然后远程调用,然后检验,返回签名;当然你也可以通过这种方式生成公章,但是因为没有经过公正机构认证,这种方式生成的公章其实是不具法律效应的,好了下面直接上代码吧。

创建签名密钥

这个密钥在pdf签名的时候会校验

package io.github.syske.common.util;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.*;
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;

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import java.security.cert.Certificate;
/**
 * @program: dpf-sign-test
 * @description: 签名工具类
 * @author: syske
 * @create: 2019-12-04 09:02
 */

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=github,O=github,C=CN,L=西安,ST=陕西";
        String subjectStr = "CN=电子签名,OU=github,O=github,C=CN,L=西安,ST=陕西";
        String certificateCRL  = "https://syske.github.io/";
        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();
    }
}
package io.github.syske.common.util;

/**
 * @program: dpf-sign-test
 * @description:
 * @author: syske
 * @create: 2019-12-04 09:00
 */
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;
    }
}

生成签名

生成的签名是png图片

package io.github.syske.common.util;

/**
 * @program: dpf-sign-test
 * @description:
 * @author: syske
 * @create: 2019-12-03 22:50
 */
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import sun.font.FontDesignMetrics;

import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;

public class SignImage {

    /**
     * @param doctorName   String 医生名字
     * @param hospitalName String 医生名称
     * @param date         String 签名日期
     *                     图片高度
     * @param jpgname      String jpg图片名
     * @return
     */
    public static boolean createSignTextImg(
            String doctorName, //
            String hospitalName, //
            String date,
            String jpgname) {
        int width = 255;
        int height = 100;
        FileOutputStream out = null;
        //背景色
        Color bgcolor = Color.WHITE;
        //字色
        Color fontcolor = Color.RED;
        Font doctorNameFont = new Font(null, Font.BOLD, 20);
        Font othorTextFont = new Font(null, Font.BOLD, 18);
        try { // 宽度 高度
            BufferedImage bimage = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_RGB);
            Graphics2D g = bimage.createGraphics();
            g.setColor(bgcolor); // 背景色
            g.fillRect(0, 0, width, height); // 画一个矩形
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON); // 去除锯齿(当设置的字体过大的时候,会出现锯齿)

            g.setColor(Color.RED);
            g.fillRect(0, 0, 8, height);
            g.fillRect(0, 0, width, 8);
            g.fillRect(0, height - 8, width, height);
            g.fillRect(width - 8, 0, width, height);

            g.setColor(fontcolor); // 字的颜色
            g.setFont(doctorNameFont); // 字体字形字号
            FontMetrics fm = FontDesignMetrics.getMetrics(doctorNameFont);
            int font1_Hight = fm.getHeight();
            int strWidth = fm.stringWidth(doctorName);
            int y = 35;
            int x = (width - strWidth) / 2;
            g.drawString(doctorName, x, y); // 在指定坐标除添加文字

            g.setFont(othorTextFont); // 字体字形字号

            fm = FontDesignMetrics.getMetrics(othorTextFont);
            int font2_Hight = fm.getHeight();
            strWidth = fm.stringWidth(hospitalName);
            x = (width - strWidth) / 2;
            g.drawString(hospitalName, x, y + font1_Hight); // 在指定坐标除添加文字

            strWidth = fm.stringWidth(date);
            x = (width - strWidth) / 2;
            g.drawString(date, x, y + font1_Hight + font2_Hight); // 在指定坐标除添加文字

            g.dispose();
            out = new FileOutputStream(jpgname); // 指定输出文件
            JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
            JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(bimage);
            param.setQuality(50f, true);
            encoder.encode(bimage, param); // 存盘
            out.flush();
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                }
            }
        }
    }

    public static void main(String[] args) {
        createSignTextImg("华佗", "在线医院", "2018.01.01", "sign.jpg");
    }
}

签名实现

这里需要注意的是,签名入参中的页码,如果是最后一页,页码传null

package io.github.syske.common.util;

import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfSignatureAppearance.RenderingMode;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import com.itextpdf.text.pdf.security.*;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;

import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.io.*;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.UUID;

/**
 * @program: SignPdf
 * @description:
 * @author: syske
 * @create: 2019-12-03 22:54
 */

public class SignPdf {
    private static Logger logger = Logger.getLogger(SignPdf.class);
    private static final String PASSWORD = "123456"; // 秘钥密码
    private static final String KEY_STORE_PATH = "d:\\keystore.p12"; // 秘钥文件路径

    private SignPdf() {
    }

    /**
     * 图片签章,指定签名坐标位置
     *
     * @param signPdfSrc
     *            签名的PDF文件
     * @param signedPdfOutFile
     *            签名后的的PDF文件
     * @param signImage
     *            签名图片完整路径
     * @param x
     *            以左下角为原点x坐标值
     * @param y
     *            以左下角为原点Y坐标值
     * @param numberOfPages
     *            签名页码,如果是最后一页则传null
     * @param pageStyle
     *            页面布局,横向或者纵向
     * @throws Exception
     */
    public static void sign(String signPdfSrc, String signedPdfOutFile,
                            String signImage, Float x, Float y, Integer numberOfPages,
                            PageStyle pageStyle) throws Exception {
        sign(signPdfSrc, signedPdfOutFile, signImage, x, y, null,
                numberOfPages, pageStyle);
    }

    /**
     * 图片签章,指定关键字
     *
     * @param signPdfSrc
     *            签名的PDF文件
     * @param signedPdfFile
     *            签名后的的PDF文件
     * @param signImage
     *            签名图片完整路径
     * @param keyWords
     *            关键字
     * @param numberOfPages
     *            签名页码,如果是最后一页则传null
     * @param pageStyle
     *            页面布局,横向或者纵向
     */
    public static void sign(String signPdfSrc, String signedPdfFile,
                            String signImage, String keyWords, Integer numberOfPages,
                            PageStyle pageStyle) throws Exception {
        sign(signPdfSrc, signedPdfFile, signImage, null, null, keyWords,
                numberOfPages, pageStyle);
    }

    /**
     * 私人签章
     *
     * @param signPdfSrc
     *            签名的PDF文件
     * @param signedPdfOutFile
     *            签名后的的PDF文件
     * @param signImage
     *            签名图片完整路径
     * @param x
     *            以左下角为原点x坐标
     * @param y
     *            以左下角为原点y坐标
     * @param keyWords
     *            关键字
     * @param numberOfPages
     *            签名页码,如果是最后一页则传null
     * @param pageStyle
     *            页面布局,横向或者纵向
     * @return
     */
    public static void sign(String signPdfSrc, String signedPdfOutFile,
                            String signImage, Float x, Float y, String keyWords,
                            Integer numberOfPages, PageStyle pageStyle) throws Exception {
        File signPdfSrcFile = new File(signPdfSrc);
        PdfReader reader = null;
        ByteArrayOutputStream signPDFData = null;
        PdfStamper stp = null;
        FileInputStream fos = null;
        FileOutputStream pdfOutputStream = null;
        try {
            BouncyCastleProvider provider = new BouncyCastleProvider();
            Security.addProvider(provider);
            KeyStore ks = KeyStore.getInstance("PKCS12",
                    new BouncyCastleProvider());
            fos = new FileInputStream(KEY_STORE_PATH);
            // 私钥密码 为Pkcs生成证书是的私钥密码 123456
            ks.load(fos, PASSWORD.toCharArray());
            String alias = ks.aliases().nextElement();
            PrivateKey key = (PrivateKey) ks.getKey(alias,
                    PASSWORD.toCharArray());
            Certificate[] chain = ks.getCertificateChain(alias);
            reader = new PdfReader(signPdfSrc); // 也可以输入流的方式构建
            signPDFData = new ByteArrayOutputStream();
            numberOfPages = numberOfPages == null ? reader.getNumberOfPages()
                    : 0;

            // 临时pdf文件
            File temp = new File(signPdfSrcFile.getParent(),
                    System.currentTimeMillis() + ".pdf");
            stp = PdfStamper.createSignature(reader, signPDFData, '\0', temp,
                    true);
            stp.setFullCompression();
            PdfSignatureAppearance sap = stp.getSignatureAppearance();
            sap.setReason("数字签名,不可改变");
            // 使用png格式透明图片
            Image image = Image.getInstance(signImage);

            sap.setImageScale(0);
            sap.setSignatureGraphic(image);
            sap.setRenderingMode(RenderingMode.GRAPHIC);
            float llx = 0f;
            float lly = 0f;

            float signImageWidth = image.getWidth();
            float signImageHeight = image.getHeight();

            float signImageHeightSocale = 85 / signImageWidth * signImageHeight;
            if (keyWords != null && !keyWords.isEmpty()) {
                KeyWordInfo keyWordInfo = getKeyWordLocation(numberOfPages,
                        keyWords, reader);
                Rectangle pageSize = reader.getPageSize(numberOfPages);
                float width = pageSize.getWidth();
                if (PageStyle.PAGE_STYLE_LANDSCAPE.equals(pageStyle)) {
                    llx = keyWordInfo.getY() + (float) keyWordInfo.getHeight();
                    lly = width - keyWordInfo.getX() - signImageHeightSocale
                            / 2;
                } else if (PageStyle.PAGE_STYLE_PORTRAIT.equals(pageStyle)) {
                    llx = keyWordInfo.getX() + (float) keyWordInfo.getWidth();
                    lly = keyWordInfo.getY() - signImageHeightSocale / 2;
                }

            } else if (x != null && y != null) {
                llx = x;
                lly = y;
            } else {
                throw new Exception("坐标和关键字不能同时为空!");
            }

            float urx = llx + 85;
            float ury = lly + signImageHeightSocale;
            // 是对应x轴和y轴坐标
            sap.setVisibleSignature(new Rectangle(llx, lly, urx, ury),
                    numberOfPages,
                    UUID.randomUUID().toString().replaceAll("-", ""));
            stp.getWriter().setCompressionLevel(5);
            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();
            pdfOutputStream = new FileOutputStream(signedPdfOutFile);
            pdfOutputStream.write(signPDFData.toByteArray());
            pdfOutputStream.close();
        } catch (KeyStoreException e) {
            logger.error("签名验证失败", e);
            throw new Exception("签名验证失败", e);
        } catch (FileNotFoundException e) {
            logger.error("文件未找到", e);
            throw new Exception("文件未找到", e);
        } catch (IOException e) {
            logger.error("IO异常", e);
            throw new Exception("IO异常", e);
        } catch (Exception e) {
            logger.error("签章失败", e);
            throw new Exception("签章失败", e);
        } finally {
            if (signPDFData != null) {
                try {
                    signPDFData.close();
                } catch (IOException e) {
                    logger.error("资源关闭失败", e);
                    throw new Exception("资源关闭失败", e);
                }
            }

            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    logger.error("资源关闭失败", e);
                    throw new Exception("资源关闭失败", e);
                }
            }
            if (pdfOutputStream != null) {
                try {
                    pdfOutputStream.close();
                } catch (IOException e) {
                    logger.error("资源关闭失败", e);
                    throw new Exception("资源关闭失败", e);
                }
            }

        }
    }

    /**
     * 查找关键字定位
     *
     * @param numberOfPages
     * @param keyWords
     *            关键字
     * @param reader
     * @return
     * @throws IOException
     */
    private static KeyWordInfo getKeyWordLocation(Integer numberOfPages,
                                                  final String keyWords, PdfReader reader) throws IOException {
        PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(
                reader);

        final KeyWordInfo keyWordInfo = new KeyWordInfo();

        pdfReaderContentParser.processContent(numberOfPages,
                new RenderListener() {
                    @Override
                    public void renderText(TextRenderInfo textRenderInfo) {
                        String text = textRenderInfo.getText(); // 整页内容

                        if (null != text && text.contains(keyWords)) {
                            Rectangle2D.Float boundingRectange = textRenderInfo
                                    .getBaseline().getBoundingRectange();
                            float leftY = (float) boundingRectange.getMinY() - 1;
                            float rightY = (float) boundingRectange.getMaxY() + 1;

                            logger.debug(boundingRectange.x + "--"
                                    + boundingRectange.y + "---");

                            keyWordInfo.setHeight(rightY - leftY);
                            keyWordInfo.setWidth((rightY - leftY)
                                    * keyWords.length());
                            keyWordInfo.setX(boundingRectange.x);
                            keyWordInfo.setY(boundingRectange.y);
                        }

                    }

                    @Override
                    public void renderImage(ImageRenderInfo arg0) {}

                    @Override
                    public void endTextBlock() {}

                    @Override
                    public void beginTextBlock() {}
                });
        return keyWordInfo;
    }

    private static class KeyWordInfo {
        private float x;
        private float y;
        private double width;
        private double height;

        public float getX() {
            return x;
        }

        public void setX(float x) {
            this.x = x;
        }

        public float getY() {
            return y;
        }

        public void setY(float y) {
            this.y = y;
        }

        public double getWidth() {
            return width;
        }

        public void setWidth(double width) {
            this.width = width;
        }

        public double getHeight() {
            return height;
        }

        public void setHeight(double height) {
            this.height = height;
        }
    }

    enum PageStyle {
        PAGE_STYLE_LANDSCAPE, // 横向
        PAGE_STYLE_PORTRAIT // 纵向
    }

    public static PageStyle getPageStyle_LANDSCAPE() {
        return PageStyle.PAGE_STYLE_LANDSCAPE;
    }

    public static PageStyle getPageStyle_PORTRAIT() {
        return PageStyle.PAGE_STYLE_PORTRAIT;
    }

    public static void main(String[] args) throws Exception {
        sign("E:\\test2_sgin.pdf",//
                "D:\\signed-35.pdf",
                "E:\\workSpeace\\pansky\\other_files\\电子签章\\png\\sign_0000_1_yxz.png",
                null, null, "负责人", null, PageStyle.PAGE_STYLE_LANDSCAPE);
        // read();

        // test();
    }

}

项目pom依赖

差点都忘记了??

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.github.syske</groupId>
    <artifactId>dpf-sign-test</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>6</source>
                    <target>6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk15on</artifactId>
            <version>1.64</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>

    </dependencies>

</project>

总结

因为本身业务不复杂,所以也没有过多的说明,但以上代码实现部分还有很多需要改进的地方,比如:

1、查找关键字的时候,只获取了一次坐标,但是同一页关键字可能存在多个,如果想在相同关键字的地方都盖章,是不能实现的,如果你正好有这样的需求,你可以动手自己改造;

2、和第一个类似,没有实现全文档签名

项目源码路径:https://github.com/Syske/learning-dome-code.git

原文地址:https://www.cnblogs.com/caoleiCoding/p/12148017.html

时间: 2024-08-30 03:35:08

java电子签章实现的相关文章

平台电子签章实现方法

有的单位,需要在文件上盖章,从而彰显正式,以及法律效益!几经研究终于找到实现的办法,下面我们详细的讲下. 主要步骤:1.公章制作:在OA个人信息-我的签名,哪里设置公章.先把公章制作成图片,然后新增签名时,就选择图片类型,调取公章.2.数据库字段设置:数据库盖章字段,编辑类型设置为"签名".字段的属性,3.外观:选择可移动,设置签名的高度.宽度. 温馨提示:电子签章目前只支持V15.0.6以上的版本.   操作方法:1.盖章的时候,窗体不能放大2.盖章后,保存文档,然后文档预览.3.觉

解决方案-电子签章:金格科技

ylbtech-解决方案-电子签章:金格科技 1.返回顶部 1. iSignature电子签章 金格iSignature®电子签章系统秉承“签章一体化”的产品理念,构建全场景化的电子签章产品体系,具有如下特性: 网络一体化       签章同时支持互联网.政务外网.内网.专网等环境的部署和应用,满足用户一套签章多种网络环境下一体化签章的应用. 印章一体化       签章面向数字企业.政务统一印章平台.国产化等领域,为政府.企业建构一体化印章中心,实现印章的可信.安全.合法.密码一体化     

shell脚本批量/单独启动、停止、重启java独立jar程序

本人最近半年使用阿里dubbo做开发,并在公司内部大力进行推广,将原来一个笨重且不易于维护的大项目切分成多个相对独立的java程序,好处是显而易见的,但是随着切分的独立运行程序包越来越多,程序的部署变成了一件非常头痛的问题,无耐之下,本人想到可否写一个shell脚本来批量/单独启动.停止.重启这些独立的java程序,之前没有写过shell脚本,研究二天后,终于将这个脚本写出来了,以后部署起来方便多了,废话不多说,直接贴上shell脚本,有需要的朋友可以根据自己项目修改前面的程序代码数组.程序名称

一种电子病历系统软件框架思想

袁永福 2016-5-9 电子病历系统到底采用B/S还是C/S架构是一个长期争论的话题.而在业界两种架构的应用范围谁也不占有显著优势. 在此笔者提出一种BS和CS混合的架构,以下是其原理图: 在该结构中主要部分有 WEB服务器 这是系统的核心.大多数的业务流程运行在WEB服务器中.采用ASP.NET开发,直连数据库. WEB服务器包含系统功能 API集合,以远程调用的方式向PC客户端软件提供功能支持. 还包含ASPX页面,向移动设备提供功能支持. PC机客户端 PC机客户端为一个轻量级的客户端软

Java 浅析,生成OFD文件

摘要:这几天遇到个需要,需要提供用户下载电子证照,文件格式为OFD,一上网查瞬间懵了,几乎没有相关的,Google上也没有(咱们国家的国标文件没有相关资料也正常),时间紧,选择最简单的方法来实现,我将需要生成的文件用word做了一份模板,利用网页工具转成OFD文件,这个网站非常良心,不干coder blackmail coder的事儿,这 是地址http://www.yozodcs.com/page/example.html(因为OFD文件没有专业软件帮助几乎很难代码实现),(用7Z工具解压of

Java学习资源

最新的科技一般都是先有英文的,所以英语有多重要可想而知.我的英文很烂,一直想学,从来都是说起来容易,想起来简单,做起来最难.只能强迫自己多看些英语方面的技术网站,技术英语两不误. 学习 Java 最好的电子书(PDF) 喜欢阅读的可以通过这些免费的 Java 电子进行自学.大多数在线的电子书都是更新的,完整的.覆盖了 Java 的大多数细节. Official Java Tutorial by Oracle (Sun) 这是 Addison-Wesley 出版社的官方 Java 指南. Java

科普:什么样的电子合同才合法有效?

世界已进入一个信息时代,以互联网络为基础信息技术正在改变着我们的生活方式和商务方式.越来越多的商业交易都通过互联网完成,如何在交易双方不用会面的情况下,顺利达成交易并确保这笔交易受法律保护?电子合同的诞生就是为了解决这个问题,但电子合同的法律有效性,成为了众多交易纠纷的焦点,合同当事人的身份认证.合同生效时间.如何撤消等问题没有得到根本解决.在实际运作中,线上交易仍然要通过线下纸质合同的传统方式解决交易合法化问题,造成资源浪费,降低运作效率,阻碍了电子商务的快速发展. 什么样的电子合同才是合法有

.NET 开发电子病历系统(EMR)

医疗行业信息化特点 随着信息技术的发展,以互联网为依托的健康教育.医疗信息查询.电子健康档案.电子处方.等多种形式的医疗健康服务悄然改变着传统医疗服务模式.病历是病人在医院诊断治疗全过程的原始记录,它包含有首页.病程记录.检查检验结果.医嘱.手术记录.护理记录等等.而随着医疗行业信息系统(HIS)的推行,电子病历系统也是整个行业最为关注的一个大的功能模块,是以电子化方式管理的有关个人终生健康状态和医疗保健行为的信息,涉及病人信息的采集.存储.传输.处理和利用的所有过程信息. 电子病历系统优势优点

浅谈java中的对象、类、与方法的重载

对象: 一切皆为对象. 对象包括两部分内容:属性(名词形容词),行为(动词). 对象和对象之间是有关系的: 派生,关联,依赖. 类: 对同一类别的众多对象的一种抽象. 类,还是用来生成对象的一种模板,对象是类的一种具体化的表现. 面向对象的三大特性:封装,继承,多态. ? 1 2 3 4 class 类名{ 访问修饰符 成员变量的定义; 访问修饰符 成员函数(方法)的定义; } 访问修改符:默认不写,private,public. private,私有.只能被当前class 类名{}中的代码访问