通过 MakeSignature.signDeferred 进行外部签名时,Java iText 5 签名无效

问题描述 投票:0回答:2

我正在使用 iText 5 Java 进行外部签名。首先,我创建签名外观,计算签名属性的哈希值并为签名留出空白位置。稍后,当我从客户端获得签名的哈希值时,我通过

MakeSignature.signDeferred
将其插入 PDF。

但是 PDF 阅读器显示签名无效。投诉PDF已被修改。 Adobe Image

这是用于签名的代码。我删除了很多功能代码,以保持代码的基本要素。

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import java.io.*;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class MklTest {
    static String thisHash;

    static class MyExternalSignatureContainer implements ExternalSignatureContainer {
        protected byte[] sig;
        public MyExternalSignatureContainer(byte[] sig) {
            this.sig = sig;
        }
        public byte[] sign(InputStream is) {
            return sig;
        }

        @Override
        public void modifySigningDictionary(PdfDictionary signDic) {
        }
    }

    static class EmptyContainer implements ExternalSignatureContainer {
        public EmptyContainer() {
        }
        public byte[] sign(InputStream is) {
            ExternalDigest digest = hashAlgorithm1 -> DigestAlgorithms.getMessageDigest(hashAlgorithm1, null);
            try {
                byte[] hash = DigestAlgorithms.digest(is, digest.getMessageDigest("SHA256"));

                thisHash = Hex.encodeHexString(hash);

                return new byte[0];
            } catch (IOException | GeneralSecurityException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary pdfDictionary) {
            pdfDictionary.put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
            pdfDictionary.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
        }
    }

    public static String emptySignature(String src, String dest, String fieldname, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        FileOutputStream os = new FileOutputStream(dest);
        PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');

        Calendar cal = GregorianCalendar.getInstance();
        cal.add(Calendar.MINUTE, 10);

        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
        appearance.setCertificate(chain[0]);
        appearance.setReason("Nice");
        appearance.setLocation("Delhi");
        appearance.setSignDate(cal);

        ExternalSignatureContainer external = new EmptyContainer();
        MakeSignature.signExternalContainer(appearance, external, 8192);

        os.close();
        reader.close();

        return thisHash;
    }

    public static Certificate getCert() throws CertificateException {
        String cert = ""; // the cert we get from client
        ByteArrayInputStream userCertificate = new ByteArrayInputStream(Base64.decodeBase64(cert));

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

        return cf.generateCertificate(userCertificate);
    }

    private static ExternalDigest getDigest() {
       return new ExternalDigest() {
            public MessageDigest getMessageDigest(String hashAlgorithm)
                    throws GeneralSecurityException {
                return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
            }
        };
    }

    public static void createSignature(String src, String dest, String fieldname, byte[] signature) throws IOException, DocumentException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        FileOutputStream os = new FileOutputStream(dest);
        ExternalSignatureContainer external = new MyExternalSignatureContainer(signature);
        MakeSignature.signDeferred(reader, fieldname, os, external);

        reader.close();
        os.close();
    }

    public static void main(String[] args) throws Exception {
        Certificate cert = getCert();
        Certificate[] chain = {cert};

        String src = "/home/spooderman/Downloads/sample.pdf";
        String between = "/tmp/sample_out_between.pdf";
        String dest = "/tmp/sample_out.pdf";
        String fieldName = "sign";

        String hash = emptySignature(src, between, fieldName, chain);

        String signature = "";  // signed hash signature we get from client
        byte[] signatureBytes = Hex.decodeHex(signature.toCharArray());

        PdfPKCS7 sgn = new PdfPKCS7(null, chain, "SHA256", null, getDigest(), false);
        sgn.setExternalDigest(signatureBytes, null, "RSA");

       byte[] data = sgn.getEncodedPKCS7(Hex.decodeHex(hash.toCharArray()),null, null, null, MakeSignature.CryptoStandard.CMS);

        createSignature(between, dest, fieldName, data);
    }
}

这是原始 PDF

这是带有空签名的PDF

这是带有最终签名的PDF

计算出的 PDF 哈希值。

954927c9286320e904920b0bf12f7cad387c1a9afd5a92314960a1083593f7dc

这是我从客户端收到的签名哈希签名。

6c14b965c7e90c3134653a9261b0666dce7a7e28cb605fc3152ad111fa7915a77396799357daf1d37c52163ce6d34bfd96ee743e721b45e929f6d8aced144f094d03dce00f25c6c1fc5aa63c92322780f7de675c194ef17303a643055dbbedfec9d5200994fcdfc3ad9488d568ad3f6cd2d262e360a79ad90b5ffb188723de559f3696dcb223930f842172e4838f7d5e6a44494ced54bca54ed12133ea189d616a10039a222ce61885ad98b8ba0bd83d63b887e2c188ca10bd2f53f92f08c5585b9826553280c19976a0ba29f7789ad6a80010b4a6431d3b6bb8f27999b23d3739de03db6db8ab46acaf38b33bd37a74465744c3f95a093deff26cb44b45e27e

我已经尝试了很多在 stackoverflow 上找到的东西,但问题仍然是一样的。任何正确方向的帮助将不胜感激。

对于客户端部分,我使用Fortify,它使本地 USB 令牌和 HSM 模块可供 JS 客户端使用。

java pdf itext digital-signature signature
2个回答
4
投票

问题是

EmptyContainer.sign
方法只给出 PDF 字节而不是经过验证的属性。经过身份验证的属性实际上是需要签名的。感谢 mkl 指出正确的方向。

我修改了

EmptyContainer.sign
方法来创建
PdfPKCS7
对象并使用 PDF 哈希作为参数之一调用
PdfPKCS7.getAuthenticatedAttributeBytes

getAuthenticatedAttributeBytes()
方法返回的字节进行签名并创建签名字节和原始哈希值的 CMS 容器后,我能够成功对 PDF 进行签名。

Success Message

如果有人需要的话,这是代码。内容很杂乱,但你会得到本质。

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.security.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import javax.annotation.Nullable;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class MklTest {
    static Certificate[] chain;
    static byte[] toSign;
    static byte[] hash;

    static class MyExternalSignatureContainer implements ExternalSignatureContainer {
        protected byte[] sig;
        public MyExternalSignatureContainer(byte[] sig) {
            this.sig = sig;
        }
        public byte[] sign(InputStream is) {
            return sig;
        }

        @Override
        public void modifySigningDictionary(PdfDictionary signDic) {
        }
    }

    static class EmptyContainer implements ExternalSignatureContainer {
        public EmptyContainer() {
        }
        public byte[] sign(InputStream is) {
            try {
                ExternalDigest digest = getDigest();
                String hashAlgorithm = getHashAlgorithm();

                Certificate[] certs = {null};

                hash = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
                    PdfPKCS7 sgn = getPkcs(certs);

                toSign = sgn.getAuthenticatedAttributeBytes(hash, getOscp(), null,
                        MakeSignature.CryptoStandard.CMS);

                return new byte[0];
            } catch (IOException | GeneralSecurityException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void modifySigningDictionary(PdfDictionary pdfDictionary) {
            pdfDictionary.put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
            pdfDictionary.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
        }
    }

    public static String getHashAlgorithm() {
        return "SHA256";
    }

    public static byte[] getOscp() {
        byte[] ocsp = null;
        OcspClient ocspClient = new OcspClientBouncyCastle(new OCSPVerifier(null, null));

        if (chain.length >= 2) {
            ocsp = ocspClient.getEncoded((X509Certificate)chain[0], (X509Certificate)chain[1], null);
        }

        return ocsp;
    }

    public static PdfPKCS7 getPkcs() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
        return new PdfPKCS7(null, chain, getHashAlgorithm(), null, getDigest(), false);
    }

    public static PdfPKCS7 getPkcs(@Nullable  Certificate[] certChain) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
        //noinspection ConstantConditions
        return new PdfPKCS7(null, certChain, getHashAlgorithm(), null, getDigest(), false);
    }

    public static void emptySignature(String src, String dest, String fieldname) throws IOException, DocumentException, GeneralSecurityException {
        PdfReader reader = new PdfReader(src);
        FileOutputStream os = new FileOutputStream(dest);
        PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');

        Calendar cal = GregorianCalendar.getInstance();
        cal.add(Calendar.MINUTE, 10);

        PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
        appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
        appearance.setReason("Nice");
        appearance.setLocation("Delhi");
        appearance.setSignDate(cal);

        ExternalSignatureContainer external = new EmptyContainer();
        MakeSignature.signExternalContainer(appearance, external, 8192);

        os.close();
        reader.close();
    }

    public static void setChain() throws CertificateException {
        String cert = ""; // the cert we get from client
        ByteArrayInputStream userCertificate = new ByteArrayInputStream(Base64.decodeBase64(cert));

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

        chain = new Certificate[]{cf.generateCertificate(userCertificate)};
    }

    private static ExternalDigest getDigest() {
       return new ExternalDigest() {
            public MessageDigest getMessageDigest(String hashAlgorithm)
                    throws GeneralSecurityException {
                return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
            }
        };
    }

    public static TSAClient getTsa() {
        return new TSAClientBouncyCastle("http://timestamp.digicert.com", null, null, 4096, "SHA-512");
    }

    public static void createSignature(String src, String dest, String fieldname, byte[] hash, byte[] signature) throws IOException, DocumentException, GeneralSecurityException {
        PdfPKCS7 sgn = getPkcs();
        sgn.setExternalDigest(signature, null, "RSA");

        byte[] encodedSig = sgn.getEncodedPKCS7(hash, getTsa(), getOscp(), null,
                MakeSignature.CryptoStandard.CMS);

        PdfReader reader = new PdfReader(src);
        FileOutputStream os = new FileOutputStream(dest);
        ExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
        MakeSignature.signDeferred(reader, fieldname, os, external);

        reader.close();
        os.close();
    }

    public static void main(String[] args) throws Exception {
        setChain();

        String src = "/home/spooderman/Downloads/sample.pdf";
        String between = "/tmp/sample_out_between.pdf";
        String dest = "/tmp/sample_out.pdf";
        String fieldName = "sign";

        emptySignature(src, between, fieldName);
        System.out.println(Hex.encodeHexString(toSign));

        String signature = "";  // signed hash signature we get from client
        byte[] signatureBytes = Hex.decodeHex(signature.toCharArray());

        createSignature(between, dest, fieldName, hash, signatureBytes);
    }
}

0
投票

我有同样的问题,但修改我的代码后(受正确版本的启发)结果不一样,我不明白为什么......先生[标签:https://stackoverflow.com/users/1729265/mkl ] 你能帮我吗?

public class AndreaTest {

static byte[] hash;

static byte[] toSign;

static Certificate[] chain;

String fieldName = "sign";  

public static void main(String[] args) throws Exception {
            
    String ocspUrl = props.getProperty("auditdata.ocspUrl");
    String ocspUser = props.getProperty("auditdata.ocspUser");
    String ocspPass = props.getProperty("auditdata.ocspPass");
        
    File initialFile = new File("src/main/resources/certificate.pem");
        
    InputStream pKstream  = new FileInputStream(initialFile);

    TSAClientBouncyCastle tsClient = new TSAClientBouncyCastle(ocspUrl, ocspUser, ocspPass, 4096, "SHA256");
    
    String originalPdf = "src/main/resources/originalPdf.PDF";
    String tempPdf = "src/main/resources/tempPdf.PDF";
                  
    setChain(pKstream);
    
    emptySignature(originalPdf, tempPdf, fieldName);
    
    String signature = "";  // signed hash signature we get from client
    byte[] signedHash = Hex.decodeHex(signature.toCharArray());
    
    createSignature(tempPdf, outPdf, fieldName, hash, signedHash, tsClient);
}

public static void setChain(InputStream userCertificate) throws CertificateException {
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    chain = new Certificate[]{cf.generateCertificate(userCertificate)};
}

public static void emptySignature(String src, String dest, String fieldname) throws IOException, DocumentException, GeneralSecurityException {
    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    PdfStamper stamper = PdfStamper.createSignature(reader, os, '\0');

    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    
    appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
    
    ExternalSignatureContainer external = new EmptyContainer();
    MakeSignature.signExternalContainer(appearance, external, 8192);

    os.close();
    reader.close();
}

public static void createSignature(String src, String dest, String fieldname, byte[] hash, byte[] signature, TSAClientBouncyCastle tsClient) throws IOException, DocumentException, GeneralSecurityException {
    PdfPKCS7 sgn = getPkcs();
    sgn.setExternalDigest(signature, null, "RSA");

    byte[] encodedSig = sgn.getEncodedPKCS7(hash, tsClient, getOscp(), null,
    MakeSignature.CryptoStandard.CMS);

    PdfReader reader = new PdfReader(src);
    FileOutputStream os = new FileOutputStream(dest);
    ExternalSignatureContainer external = new MyExternalSignatureContainer(encodedSig);
    MakeSignature.signDeferred(reader, fieldname, os, external);

    reader.close();
    os.close();
}

static class MyExternalSignatureContainer implements ExternalSignatureContainer {
    protected byte[] sign;
    public MyExternalSignatureContainer(byte[] sign) {
    this.sign = sign;
    }
    public byte[] sign(InputStream is) {
    return sign;
    }

    @Override
    public void modifySigningDictionary(PdfDictionary signDic) {
    }
}

static class EmptyContainer implements ExternalSignatureContainer {
    public EmptyContainer() {
    }
    public byte[] sign(InputStream is) {
        try {
            ExternalDigest digest = getDigest();

            Certificate[] certs = {null};

            hash = DigestAlgorithms.digest(is, digest.getMessageDigest("SHA256"));

            String temp = DigestUtils.sha256Hex(is);//.getBytes(StandardCharsets.UTF_8);

            PdfPKCS7 sgn = getPkcs(certs);

            toSign = sgn.getAuthenticatedAttributeBytes(hash, getOscp(), null,
            MakeSignature.CryptoStandard.CMS);

            return new byte[0];
        } catch (IOException | GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }
    
    @Override
    public void modifySigningDictionary(PdfDictionary pdfDictionary) {
        pdfDictionary.put(PdfName.FILTER, PdfName.ADOBE_PPKMS);
        pdfDictionary.put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
    }
}

private static ExternalDigest getDigest() {
    return new ExternalDigest() {
        public MessageDigest getMessageDigest(String hashAlgorithm)
                throws GeneralSecurityException {
            return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
        }
    };
}

public static PdfPKCS7 getPkcs() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
    return new PdfPKCS7(null, chain, "SHA256", null, getDigest(), false);
}

public static PdfPKCS7 getPkcs(@Nullable  Certificate[] certChain) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
    //noinspection ConstantConditions
    return new PdfPKCS7(null, certChain, "SHA256", null, getDigest(), false);
}

public static byte[] getOscp() {
    byte[] ocsp = null;
    OcspClient ocspClient = new OcspClientBouncyCastle(new OCSPVerifier(null, null));

    if (chain.length >= 2) {
        ocsp = ocspClient.getEncoded((X509Certificate)chain[0], (X509Certificate)chain[1], null);
    }

    return ocsp;
}   

}

© www.soinside.com 2019 - 2024. All rights reserved.