admin 管理员组

文章数量: 1087139

I want to sign a document with using an external API. You can send an hash of your document to that service and get a signed hash back.

This is how I send the document hash to the API:

private String generateHash(byte[] content) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA256");
        byte[] hash = digest.digest(content);
        return Base64.getEncoder().encodeToString(hash);
}

This is how I handle the response with the signature hash:

byte[] signByte = Base64.getDecoder().decode(stsCallbackDocumentResponse.getSignByte());

After signing the signature is invalid because of "The document was modified or corrupted after the signature was applied".

Here is the code responsible for signing:

private byte[] signPdf(byte[] content, byte[] signByte, String reason, String location)
            throws GeneralSecurityException, IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
        PdfReader reader = new PdfReader(inputStream);

        StampingProperties stampingProperties = new StampingProperties().useAppendMode();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfSigner signer = new PdfSigner(reader, outputStream, stampingProperties);

        // .setFieldName("Signature1");
        Rectangle rect = new Rectangle(36, 648, 200, 100);
        signer.getSignerProperties().setPageRect(rect).setPageNumber(1);
        if (reason != null) {
            signer.getSignerProperties().setReason(reason);
        }
        if (location != null) {
            signer.getSignerProperties().setLocation(location);
        }

        IExternalDigest digest = new BouncyCastleDigest();
        IExternalSignature signature = new IExternalSignature() {

            @Override
            public String getDigestAlgorithmName() {
                return DigestAlgorithms.SHA256;
            }

            @Override
            public String getSignatureAlgorithmName() {
                return "RSA";
            }

            @Override
            public ISignatureMechanismParams getSignatureMechanismParameters() {
                return null;
            }

            @Override
            public byte[] sign(byte[] message) throws GeneralSecurityException {
                return signByte;
            }
        };

        StsAccessToken stsAccessToken = stsAccessTokenRepository
                .findByApplicationUserId(SecurityUtils.getCurrentUserId());

        Certificate[] chain = generateCertificateChain(stsAccessToken.getSigningCertificate().getPemCertificate());

        signer.signDetached(digest, signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);

        return outputStream.toByteArray();
    }

    private Certificate[] generateCertificateChain(String pemCertificate) {
        try {
            byte[] decodedCert = java.util.Base64.getDecoder()
                    .decode(pemCertificate.replaceAll("-----\\w+ CERTIFICATE-----", "").replaceAll("\n", ""));
            InputStream inputStream = new ByteArrayInputStream(decodedCert);

            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

            X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(inputStream);

            List<Certificate> certificateChain = new ArrayList<>();
            certificateChain.add(certificate);

            return certificateChain.toArray(new Certificate[0]);
        } catch (Exception e) {
            throw new ServiceException(e);
        }
    }

Finally I create a document from the resulting byte[].

I also tried saving directly to a document.

PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"), stampingProperties);

Dependencies:

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>sign</artifactId>
            <version>9.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>bouncy-castle-adapter</artifactId>
            <version>9.1.0</version>
        </dependency>

Any idea what I'm doing wrong?

I don't have API documentation for the signing step, I only have one link.

UPDATES:

You are right, I was not sending the hash for the message from the sign method of IExternalSignature but was sending the hash of the entire document because that's what the API documentation says.

What I didn't mention and I think it was useful to do is that my signature happens in different steps.

My poor documentation says:

 1. /api/v1/signature
Body:
 - id: unique identifier of the record.
 - hashByte: the hash value of the document, encoded (e.g. Base64) to validate data integrity.
 - algorithmName: name of the algorithm used to generate the hash (e.g. SHA256).
 - docName: name of the document or file for which the hash was calculated.
 2. /api/v1/callback
Response:
- id: unique identifier of the record.
- signByte: A digital signature in the form of an array of bytes, encoded in Base64.

I updated the code as follows after your response but the signature is still invalid.

    public void signDocument(DocumentContent documentContent) {
    /*
     * { 
     *  "id": documentContent.getId(), 
     *  "hashByte": generateHash(documentContent.getContent()), 
     *  "algorithmName": "SHA256",
     *  "docName": documentContent.getName() 
     * } 
     * send sign request
     */
}

private String generateHash(byte[] content) {
    try {
        PdfReader reader = new PdfReader(new ByteArrayInputStream(content));
        PdfSigner signer = new PdfSigner(reader, new ByteArrayOutputStream(),
                new StampingProperties().useAppendMode());
        signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);

        final HashHolder extracted = new HashHolder();
        IExternalSignature hashExtractor = new IExternalSignature() {

            @Override
            public byte[] sign(byte[] hash) throws GeneralSecurityException {
                extracted.hash = hash;
                return new byte[0];
            }

            @Override
            public String getDigestAlgorithmName() {
                return DigestAlgorithms.SHA256;
            }

            @Override
            public String getSignatureAlgorithmName() {
                return "RSA";
            }

            @Override
            public ISignatureMechanismParams getSignatureMechanismParameters() {
                return null;
            }
        };
        signer.signDetached(new BouncyCastleDigest(), hashExtractor, getCertificateChain(), null, null, null, 0,
                PdfSigner.CryptoStandard.CMS);

        MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
        byte[] hash = messageDigest.digest(extracted.hash);

        return Base64.getEncoder().encodeToString(hash);
    } catch (Exception e) {
        throw new ServiceException(e);
    }
}

public void addCallbackSignatureHash(DocumentContent documentContent) {
    try {
        byte[] signByte = Base64.getDecoder().decode(getCallbackSignByte());
        PdfReader reader = new PdfReader(new ByteArrayInputStream(documentContent.getContent()));

        PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"),
                new StampingProperties().useAppendMode());
        signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);

        IExternalSignature signature = new IExternalSignature() {

            @Override
            public String getDigestAlgorithmName() {
                return DigestAlgorithms.SHA256;
            }

            @Override
            public String getSignatureAlgorithmName() {
                return "RSA";
            }

            @Override
            public ISignatureMechanismParams getSignatureMechanismParameters() {
                return null;
            }

            @Override
            public byte[] sign(byte[] message) throws GeneralSecurityException {
                return signByte;
            }
        };
        signer.signDetached(new BouncyCastleDigest(), signature, getCertificateChain(), null, null, null, 0,
                PdfSigner.CryptoStandard.CMS);

    } catch (Exception e) {
        throw new ServiceException(e);
    }
}

What am I missing this time?

I want to sign a document with using an external API. You can send an hash of your document to that service and get a signed hash back.

This is how I send the document hash to the API:

private String generateHash(byte[] content) throws NoSuchAlgorithmException {
        MessageDigest digest = MessageDigest.getInstance("SHA256");
        byte[] hash = digest.digest(content);
        return Base64.getEncoder().encodeToString(hash);
}

This is how I handle the response with the signature hash:

byte[] signByte = Base64.getDecoder().decode(stsCallbackDocumentResponse.getSignByte());

After signing the signature is invalid because of "The document was modified or corrupted after the signature was applied".

Here is the code responsible for signing:

private byte[] signPdf(byte[] content, byte[] signByte, String reason, String location)
            throws GeneralSecurityException, IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
        PdfReader reader = new PdfReader(inputStream);

        StampingProperties stampingProperties = new StampingProperties().useAppendMode();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfSigner signer = new PdfSigner(reader, outputStream, stampingProperties);

        // .setFieldName("Signature1");
        Rectangle rect = new Rectangle(36, 648, 200, 100);
        signer.getSignerProperties().setPageRect(rect).setPageNumber(1);
        if (reason != null) {
            signer.getSignerProperties().setReason(reason);
        }
        if (location != null) {
            signer.getSignerProperties().setLocation(location);
        }

        IExternalDigest digest = new BouncyCastleDigest();
        IExternalSignature signature = new IExternalSignature() {

            @Override
            public String getDigestAlgorithmName() {
                return DigestAlgorithms.SHA256;
            }

            @Override
            public String getSignatureAlgorithmName() {
                return "RSA";
            }

            @Override
            public ISignatureMechanismParams getSignatureMechanismParameters() {
                return null;
            }

            @Override
            public byte[] sign(byte[] message) throws GeneralSecurityException {
                return signByte;
            }
        };

        StsAccessToken stsAccessToken = stsAccessTokenRepository
                .findByApplicationUserId(SecurityUtils.getCurrentUserId());

        Certificate[] chain = generateCertificateChain(stsAccessToken.getSigningCertificate().getPemCertificate());

        signer.signDetached(digest, signature, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);

        return outputStream.toByteArray();
    }

    private Certificate[] generateCertificateChain(String pemCertificate) {
        try {
            byte[] decodedCert = java.util.Base64.getDecoder()
                    .decode(pemCertificate.replaceAll("-----\\w+ CERTIFICATE-----", "").replaceAll("\n", ""));
            InputStream inputStream = new ByteArrayInputStream(decodedCert);

            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

            X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(inputStream);

            List<Certificate> certificateChain = new ArrayList<>();
            certificateChain.add(certificate);

            return certificateChain.toArray(new Certificate[0]);
        } catch (Exception e) {
            throw new ServiceException(e);
        }
    }

Finally I create a document from the resulting byte[].

I also tried saving directly to a document.

PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"), stampingProperties);

Dependencies:

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>sign</artifactId>
            <version>9.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>bouncy-castle-adapter</artifactId>
            <version>9.1.0</version>
        </dependency>

Any idea what I'm doing wrong?

I don't have API documentation for the signing step, I only have one link.

UPDATES:

You are right, I was not sending the hash for the message from the sign method of IExternalSignature but was sending the hash of the entire document because that's what the API documentation says.

What I didn't mention and I think it was useful to do is that my signature happens in different steps.

My poor documentation says:

 1. /api/v1/signature
Body:
 - id: unique identifier of the record.
 - hashByte: the hash value of the document, encoded (e.g. Base64) to validate data integrity.
 - algorithmName: name of the algorithm used to generate the hash (e.g. SHA256).
 - docName: name of the document or file for which the hash was calculated.
 2. /api/v1/callback
Response:
- id: unique identifier of the record.
- signByte: A digital signature in the form of an array of bytes, encoded in Base64.

I updated the code as follows after your response but the signature is still invalid.

    public void signDocument(DocumentContent documentContent) {
    /*
     * { 
     *  "id": documentContent.getId(), 
     *  "hashByte": generateHash(documentContent.getContent()), 
     *  "algorithmName": "SHA256",
     *  "docName": documentContent.getName() 
     * } 
     * send sign request
     */
}

private String generateHash(byte[] content) {
    try {
        PdfReader reader = new PdfReader(new ByteArrayInputStream(content));
        PdfSigner signer = new PdfSigner(reader, new ByteArrayOutputStream(),
                new StampingProperties().useAppendMode());
        signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);

        final HashHolder extracted = new HashHolder();
        IExternalSignature hashExtractor = new IExternalSignature() {

            @Override
            public byte[] sign(byte[] hash) throws GeneralSecurityException {
                extracted.hash = hash;
                return new byte[0];
            }

            @Override
            public String getDigestAlgorithmName() {
                return DigestAlgorithms.SHA256;
            }

            @Override
            public String getSignatureAlgorithmName() {
                return "RSA";
            }

            @Override
            public ISignatureMechanismParams getSignatureMechanismParameters() {
                return null;
            }
        };
        signer.signDetached(new BouncyCastleDigest(), hashExtractor, getCertificateChain(), null, null, null, 0,
                PdfSigner.CryptoStandard.CMS);

        MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
        byte[] hash = messageDigest.digest(extracted.hash);

        return Base64.getEncoder().encodeToString(hash);
    } catch (Exception e) {
        throw new ServiceException(e);
    }
}

public void addCallbackSignatureHash(DocumentContent documentContent) {
    try {
        byte[] signByte = Base64.getDecoder().decode(getCallbackSignByte());
        PdfReader reader = new PdfReader(new ByteArrayInputStream(documentContent.getContent()));

        PdfSigner signer = new PdfSigner(reader, new FileOutputStream("/home/alex/repo/alex.pdf"),
                new StampingProperties().useAppendMode());
        signer.getSignerProperties().setPageRect(new Rectangle(36, 648, 200, 100)).setPageNumber(1);

        IExternalSignature signature = new IExternalSignature() {

            @Override
            public String getDigestAlgorithmName() {
                return DigestAlgorithms.SHA256;
            }

            @Override
            public String getSignatureAlgorithmName() {
                return "RSA";
            }

            @Override
            public ISignatureMechanismParams getSignatureMechanismParameters() {
                return null;
            }

            @Override
            public byte[] sign(byte[] message) throws GeneralSecurityException {
                return signByte;
            }
        };
        signer.signDetached(new BouncyCastleDigest(), signature, getCertificateChain(), null, null, null, 0,
                PdfSigner.CryptoStandard.CMS);

    } catch (Exception e) {
        throw new ServiceException(e);
    }
}

What am I missing this time?

Share Improve this question edited Mar 28 at 12:26 Alex Dolhescu asked Mar 27 at 19:09 Alex DolhescuAlex Dolhescu 112 bronze badges 7
  • 2 What bytes do you apply generateHash to? Apparently not the message parameter bytes of the sign method of your IExternalSignature implementation as you should do. – mkl Commented Mar 28 at 6:07
  • @Alex I assume you are feeding the whole document to generate the hash, which is then signed via external service. But the PDF signing excludes some part of file for getting hash for signing. You may refer my answer to a similar question, and see if it helps your case. – Deepak Chaudhary Commented Mar 28 at 6:12
  • @mkl Before I was sending the whole document completely wrong. I updated the code, but without success. What am I missing this time? – Alex Dolhescu Commented Mar 28 at 12:02
  • @DeepakChaudhary you're right that's what i needed: sign a pdf in multiple phases. I updated the code, but without success. What am I missing this time? – Alex Dolhescu Commented Mar 28 at 12:03
  • 1 Alex, just like @Deepak said, if you start the signing process twice from the original PDF of the user, you'll usually get different files (in particular because the current time is added to the PDF as modification and signing time, and new document IDs are generated). Tweaking that to create identical files can be hard. Thus, it's much easier not to discard the result of the first pass but keep it and overwrite the dummy signature from the first pass using deferred signing. – mkl Commented Mar 28 at 14:43
 |  Show 2 more comments

1 Answer 1

Reset to default 1

As already indicated in a comment, it is unclear from your question which bytes you apply your generateHash method to and then sign. Apparently, though, you don't apply it to the byte[] message parameter of the sign method of your IExternalSignature implementation as you should.

Thus, basically you should simply change that sign method to something like this:

public byte[] sign(byte[] message) throws GeneralSecurityException {
    MessageDigest digest = MessageDigest.getInstance("SHA256");
    byte[] hash = digest.digest(message);
    String base64Hash = Base64.getEncoder().encodeToString(hash);
    var stsCallbackDocumentResponse = [... request signature response for base64Hash ...];
    byte[] signByte = Base64.getDecoder().decode(stsCallbackDocumentResponse.getSignByte());
    return signByte;
}

and get rid of the signByte parameter of signPdf.

With that change in place, signPdf should be able to properly sign your PDF in a single step.


Yes, there are situations in which people use a multistep approach to signing with iText, i.e. you first sign the PDF with a dummy signature value, calculate the hash of the signed byte ranges of that dummy-signed file, request and retrieve a signature for that hash, and overwrite the dummy signature in the prepared PDF by the actual one.

Often, though, they do this without need and only create unnecessarily complicated code this way. The only situations in which such a multistep approach makes sense for me, are either when the signature value retrieval from a server takes a long time and you don't want your thread to linger that long; or when the preparation step and the signature value retrieval occur on different computers, for example the preparation step is executed on a computer without network access to the signing server.

本文标签:

Error[2]: Invalid argument supplied for foreach(), File: /www/wwwroot/roclinux.cn/tmp/view_template_quzhiwa_htm_read.htm, Line: 58
File: /www/wwwroot/roclinux.cn/tmp/route_read.php, Line: 205, include(/www/wwwroot/roclinux.cn/tmp/view_template_quzhiwa_htm_read.htm)
File: /www/wwwroot/roclinux.cn/tmp/index.inc.php, Line: 129, include(/www/wwwroot/roclinux.cn/tmp/route_read.php)
File: /www/wwwroot/roclinux.cn/index.php, Line: 29, include(/www/wwwroot/roclinux.cn/tmp/index.inc.php)