V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
matenshi
V2EX  ›  Java

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

  •  1
     
  •   matenshi · 2021-02-24 16:15:03 +08:00 · 4098 次点击
    这是一个创建于 1403 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前提是公司要弄一个电子签章的系统,目的是对 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 时,阅读器(测试时用的福昕阅读器),还是显示的 “签名有效性未知,签名者身份未知,因为它和父证书都不包含在信任列表中”。

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

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

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

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

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

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

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

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

    谢谢各位!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2130 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 16:13 · PVG 00:13 · LAX 08:13 · JFK 11:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.