【问题标题】:Signing PDF using network HSM and PDFBox使用网络 HSM 和 PDFBox 签署 PDF
【发布时间】:2018-12-02 10:48:05
【问题描述】:

我正在尝试以 CreateSignature 示例为起点,并对其进行更改以使其与我们基于外部网络的 HSM 系统一起使用。

生成的 PDF 文档总是抱怨“文档已被更改”。我对应该使用什么签名缺乏洞察力。

下面是我的CreateSignatureBase.java中sign()的实现:

@Override
    public byte[] sign(InputStream content) throws IOException {

        // cannot be done private (interface)
        try {

            // Certificate chain is acquired at initialization
            List<Certificate> certList = new ArrayList<>();
            certList.addAll(Arrays.asList(certificateChain));
            Store<?> certs = new JcaCertStore(certList);
            org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(certificateChain[0].getEncoded());

            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

            //HSMSigner is the class that interacts with the network HSM to get the signature.
            HSMSigner signer = new HSMSigner();
            byte[] input = IOUtil.toByteArray(content);

            //SignedHash is a base64-encoded PKCS1 block. see HSMSigner.getSignature() below
            final String signedHash = signer.getSignature(input);

            ContentSigner sha1Signer = new ContentSigner() {

                @Override
                public byte[] getSignature() {
                    return Base64.getDecoder().decode(signedHash);
                }

                @Override
                public OutputStream getOutputStream() {
                    return new ByteArrayOutputStream();
                }

                @Override
                public AlgorithmIdentifier getAlgorithmIdentifier() {
                    return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WITHRSAENCRYPTION");
                }
            };

            gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(sha1Signer, new X509CertificateHolder(cert)));
            gen.addCertificates(certs);
            CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
            CMSSignedData cmsSignedData = gen.generate(msg, true);

            byte[] result = cmsSignedData.getEncoded();

            return result;

        } catch (GeneralSecurityException | CMSException | OperatorCreationException e) {
            throw new IOException(e);
        }

    }

以下是HSMSigner().getSignature()的实现

public String getSignature(byte [] bytes) {


        String host = "hsmvip.corp.com";
        int port = 9000;
        SignClient signClient;
        try {
            //initialize the sign client 
            signClient = ///..;
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(bytes);
            byte[] outputDigest = messageDigest.digest();

            // signature returned by the sign method is a base64-encoded PKCS1 block.
            String signature = signClient.sign(Base16.encodeAsString(outputDigest));
            signClient.close();
            return signature;
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }

    }

非常感谢任何帮助找出我做错了什么。

可在https://file.io/2tVvYO找到已签名的 pdf 副本

提前致谢!

【问题讨论】:

  • 以上文件链接失效。请使用gofile.io/?c=XvridR
  • 在您的代码中,您使用InputStream content,创建其字节的 PKCS#1 签名,然后尝试基于此签名构建 CMS 签名容器。这是错误的做法,InputStream content 中的数据不能直接签名,而是必须进行散列,那么它们的散列只是一种键值对映射中的一个值,而这个映射(称为“签名属性") 将由您的 HSM 签名。我无法判断至少您的HSMSigner().getSignature() 是否正确,这取决于signClient 的行为。
  • 嗨@mlk,感谢您的指导,我能够根据您的建议更新我的代码并使其正常工作。我已经更新了下面的代码。

标签: java pdf pdfbox bouncycastle


【解决方案1】:

感谢您的详细回复,我能够按如下方式更新 CreateSignatureBase.sign() 方法以获得所需的结果

CreateSignautreBase.java:sign()

    @Override
    public byte[] sign(InputStream content) throws IOException {

        // cannot be done private (interface)
        try {

            // Certificate chain is acquired at initialization
            List<Certificate> certList = new ArrayList<>();
            certList.addAll(Arrays.asList(certificateChain));
            Store<?> certs = new JcaCertStore(certList);
            org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(certificateChain[0].getEncoded());

            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

            //HSMSigner is the class that interacts with the network HSM to get the signature.
            HSMSigner signer = new HSMSigner();
            byte[] input = IOUtil.toByteArray(content);

            //This is the update over previous code.
            //Create a hash of the content and add it to the attribute map
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(input);
            Attribute attr = new Attribute(CMSAttributes.messageDigest, new DERSet(new DEROctetString(messageDigest.digest())));
            ASN1EncodableVector v = new ASN1EncodableVector();
            v.add(attr);

            ContentSigner sha1Signer = new ContentSigner() {
                //This is to ensure that signature is created using the right data.
                ByteArrayOutputStream stream = new ByteArrayOutputStream();

                @Override
                public byte[] getSignature() {
                    //Calling HSM here instead, the stream is the AttributeMap
                    return Base64.getDecoder().decode(signer.getSignature(stream.toByteArray()));
                }
                //Perhaps called by BouncyCastle library to provide the content
                @Override
                public OutputStream getOutputStream() {
                    return stream;
                }

                @Override
                public AlgorithmIdentifier getAlgorithmIdentifier() {
                    return new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256WITHRSAENCRYPTION");
                }
            };

            //As per mkl's comment, using the AttributeTable as an input where the table already has a Hashed value of the content.
            SignerInfoGeneratorBuilder builder = new SignerInfoGeneratorBuilder(new BcDigestCalculatorProvider())
                    .setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(new AttributeTable(v)));

            gen.addSignerInfoGenerator(builder.build(sha1Signer, new X509CertificateHolder(cert)));

            gen.addCertificates(certs);
            CMSProcessableInputStream msg = new CMSProcessableInputStream(content);
            CMSSignedData cmsSignedData = gen.generate(msg, true);

            byte[] result = cmsSignedData.getEncoded();

            return result;

        } catch (GeneralSecurityException | CMSException | OperatorCreationException e) {
            throw new IOException(e);
        }

    }

HSMSigner.getSignature() 保持不变。

【讨论】:

  • 嗨,你能告诉我 HSM 是如何工作的吗?当我们发送摘要时它会返回什么。您在 HSM 中使用哪个库来签署摘要。我想在不调用服务的情况下实现相同的逻辑。
猜你喜欢
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-27
  • 2019-12-17
  • 1970-01-01
  • 2015-02-11
相关资源
最近更新 更多