如何实现一个电子签章的系统呢

2021-02-24 16:15:03 +08:00
 matenshi

前提是公司要弄一个电子签章的系统,目的是对 pdf 文档签名,然后验签。用户用阅读器查看 pdf 时,也要显示证书的有效性,验证时从场景来看,也不能简单把每个人的证书都加到信任列表中,希望能有个根证书一样的东西来验证。

我以前对证书加解密这块不是很懂,https 也是用的 Let's Encrypt 。后面补了相关知识,大致上是 CA 机构用自己的私钥加密我的公钥得到证书,然后客户信任并拥有这个 CA 的公钥,这样客户使用公钥解密加密后的证书,并从证书中得到我的公钥。不知道我理解的对不对?

然后回到 pdf,我想就是私钥签名,公钥验签。

然后查询了相关资料后,我使用 keytool 来生成证书,用 itext7 来对 pdf 文档签名,以下是步骤:

  1. 创建一个自签名的证书作为根证书(类似 CA?);
  2. 生成一个人员的证书;
  3. 生成该人员证书对应的证书请求文件;
  4. 然后通过步骤 1 生成的根证书并对其签名,得到一个证书文件(发布者就变了);
  5. 通过 itext 来对 pdf 文件进行签名。
  6. 导出第一步的证书(根证书),添加到 pdf 阅读器受信任的证书列表中(也安装在了本地),然后查看 pdf 。

下面是签名的方法,修改的 itext7 的 demo:

public class C4_07_ClientServerSigning {
    public static final String DEST = "/Users/xxx/Downloads/";
    public static final String SRC = "/Users/xxx/Downloads/ApplicationForm.pdf";
    public static final String CERT = "/Users/xxx/Documents/csr/pdf/aaa.cer";
    public static final String KEYSTORE = "/Users/xxx/Documents/csr/pdf/aaa.keystore";
    public static final char[] PASSWORD = "123456".toCharArray();
    public static final String[] RESULT_FILES = new String[] {
            "hello_server.pdf"
 };
    public static void main(String[] args) throws GeneralSecurityException, IOException {
        File file = new File(DEST);
        file.mkdirs();
        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        Certificate[] chain = new Certificate[1];
        chain[0] = factory.generateCertificate(new FileInputStream(CERT));
        new C4_07_ClientServerSigning2().sign(SRC, DEST + RESULT_FILES[0],chain,PdfSigner.CryptoStandard.CMS,
                "Test", "Ghent");
    }
    public void sign(String src, String dest, Certificate[] chain, PdfSigner.CryptoStandard subfilter,
                     String reason, String location) throws GeneralSecurityException, IOException {
        PdfReader reader = new PdfReader(src);
        PdfSigner signer = new PdfSigner(reader, new FileOutputStream(dest), new StampingProperties());
        // Create the signature appearance
        Rectangle rect = new Rectangle(36, 648, 200, 100);
        PdfSignatureAppearance appearance = signer.getSignatureAppearance();
        appearance
 .setReason(reason)
                .setLocation(location)
                .setPageRect(rect)
                .setPageNumber(1);
        signer.setFieldName("sig");
        IExternalDigest digest = new BouncyCastleDigest();
        BouncyCastleProvider provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        ks.load(new FileInputStream(KEYSTORE), PASSWORD);
        String alias = ks.aliases().nextElement();
        PrivateKey pk = (PrivateKey) ks.getKey(alias, PASSWORD);
        IExternalSignature pks = new PrivateKeySignature(pk, DigestAlgorithms.SHA256, provider.getName());
        // Sign the document using the detached mode, CMS or CAdES equivalent.
 signer.signDetached(digest, pks, chain, null, null, null,
                0, subfilter);
    }
}

现在的问题是,查看 pdf 时,阅读器(测试时用的福昕阅读器),还是显示的 “签名有效性未知,签名者身份未知,因为它和父证书都不包含在信任列表中”。

现在就没有头绪了,我觉得是我想错了,请各位有空帮忙看看,谢谢

4099 次点击
所在节点    Java
18 条回复
chixinzei
2021-02-24 17:25:49 +08:00
https 原理再看一遍,CA 证书(含公钥)是直接被各大主流浏览器所信任的,所以客户不需要再次信任,你找 CA 买的证书会通过浏览器自行与 CA 交互认证成功。 你自己签的证书(公钥)客户端并没有对应的 CA 可以验证,所以除非客户手动信任该证书,否则证书都是未知的。
chixinzei
2021-02-24 17:27:52 +08:00
补充,浏览器不会自行跟 CA 交互认证,而是根据内置 CA 证书进行算法验证匹配是否合法。
chinvo
2021-02-24 17:30:19 +08:00
PDF 签名需要 Adobe 认证的 CA 签的证书,否则提示不可信
matenshi
2021-02-24 17:35:05 +08:00
@chixinzei 是这样,可是我第 6 步把我这个自签的根证书导出来,并手动添加到阅读器的信任列表和安装到本机的受信任证书里了,这样不算吗。。。
matenshi
2021-02-24 17:36:58 +08:00
@chinvo 嗯,现在用的不是 adobe reader 看的,不过如果我把通过 ca 的签名过后的用户证书先手动添加到受信任列表里的话,也是可以的,但是业务场景不允许。
chinvo
2021-02-24 17:44:03 +08:00
如果不能添加自签名证书(通过安装包安装)的话,一般来说只能找受信任的 CA 合作或者买他们的中间证书。
matenshi
2021-02-24 18:05:40 +08:00
@chinvo 不说能不能通过安装包安装,目前设想是可以添加自签名证书的(就是让用户电脑都手动添加这个证书)。 以前是买过安政通的电子签章软件,那个是有个专门的客户端来查看 pdf,现在新项目领导说自己弄个功能简单的。
chinvo
2021-02-24 18:12:19 +08:00
@matenshi #7 那你们也可以自己做个 PDF 阅读器,内置上自己的 CA 就行
joesonw
2021-02-24 18:18:37 +08:00
matenshi
2021-02-24 18:19:41 +08:00
@chinvo 现在的问题就是这个,我通过 ```keytool -exportcert ```命令把我的自签证书导出为 cer 文件,然后在阅读器上把该证书添加为受信任的根证书,某种意义上也算内置了自己的 CA 了吧。然后还是提示上面那个问题,我查看了经过签名的证书,证书链上的顶层确实是我导出的那个已经加入了受信任的根证书,结果还是这样。
KuroNekoFan
2021-02-24 20:22:31 +08:00
好像已经有第三方的系统了
esign.cn
matenshi
2021-02-24 22:12:54 +08:00
@KuroNekoFan 谢谢,不过第三方的目前先不做考虑,因为要实现的功能比较简单。
hysys32
2021-02-25 07:45:58 +08:00
Adobe 官方有个 echosign……我们公司各种签名都用它
teawithlife
2021-02-25 08:54:07 +08:00
@matenshi #5 从 5 楼的描述来说,是不是这样的情况:
1. 你把 CA 签名过的用户证书加入信任列表,阅读器认证通过
2. 你把 CA 证书加入信任列表,删去用户证书,阅读器认证失败

如果是这样的话,问题应该出在第 4 步后少了一步,你要把用户证书和 CA 证书的公钥合并成一个证书文件,这样才能形成一个证书链,阅读器信任这个合并的证书文件后,才能从 CA 开始进行认证。如果有多级 CA,比如 CA1 签名 CA2,CA2 签名 CA3,CA3 签名用户证书,那这四张证书都要合并成一个文件才行

原理应该就是这样,但是具体的操作你得自己找找
kikikiabc
2021-02-25 09:01:49 +08:00
既然 CA 都自己建了,PDF 阅读器也自己搞一个不就行了。操作系统里面的信任关系,其他软件可以自行选择是否使用。比如 Firefox 浏览器或 AdobePdf 这些都自己搞了一套证书信任体系的。

就像手机上的静音键,理论上是手机操作系统设置了一个应该静音的标记,但 App 是否遵守得靠自觉。
a1274598858
2021-02-25 09:24:33 +08:00
@chinvo adobe 阅读器信不信任根都不影响..只要 CA 证书是国内有资质的公司颁发的,并且 pdf 验签没有被篡改,这份文件都是有效的
type
2021-02-25 09:36:51 +08:00
楼主目前的方案适合公司内部自用,如果涉及与外部相对方的签名,这个是不具备法律效力的
matenshi
2021-02-25 16:30:35 +08:00
解决了,问题有点像#14 楼说的,证书链的问题,不过问题是出在用 itext7 签名的时候。

看代码我构造的数组长度只为 1,并且就导入了导出的二级证书,应该把根证书也导入进来,顺序的话是根证书在后头,这样就可以了。

itext 还有另一个方法,就是通过 KeyStore 对象的 getCertificateChain 方法获取证书链,不过这样就需要把签名(正常的请看就是 CA 认证)后的证书(上面第 4 步生成的)导入到密钥库中才行。

至于电子签章应该有的基本功能如 制章(在我看来就是弄个图片?)、盖章、撤章、验证之类的 itext7 都有提供相关的方法。
最后这就像#17 楼说的,只能内部玩玩,并且 itext7 是 APGL 协议的。

谢谢各位!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/755866

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX