У меня есть приложение, которое создает PDF-файл, и его нужно подписать.
У нас нет сертификатов для подписи документа, потому что они находятся в HSM, и единственный способ использовать сертификаты — использовать веб-сервис.
PdfReader reader = new PdfReader(src);
reader.setAppendable(true);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileOutputStream fout = new FileOutputStream(dest);
PdfStamper stamper = PdfStamper.createSignature(reader, fout, '\0');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setReason("Test");
appearance.setLocation("footer");
appearance.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
appearance.setCertificate(certChain[0]);
PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
dic.setReason(appearance.getReason());
dic.setLocation(appearance.getLocation());
dic.setContact(appearance.getContact());
dic.setDate(new PdfDate(appearance.getSignDate()));
appearance.setCryptoDictionary(dic);
HashMap<PdfName, Integer> exc = new HashMap<PdfName, Integer>();
exc.put(PdfName.CONTENTS, new Integer(8192 * 2 + 2));
appearance.preClose(exc);
ExternalDigest externalDigest = new ExternalDigest()
{
public MessageDigest getMessageDigest(String hashAlgorithm) throws GeneralSecurityException
{
return DigestAlgorithms.getMessageDigest(hashAlgorithm, null);
}
};
PdfPKCS7 sgn = new PdfPKCS7(null, certChain, "SHA256", null, externalDigest, false);
InputStream data = appearance.getRangeStream();
byte[] hash = DigestAlgorithms.digest(data, externalDigest.getMessageDigest("SHA256"));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);
String hashPdf = new String(Base64.encodeBytes(sh));
String hashSignat = hashPdf;
Это наш код, сначала получаем внешний вид подписи, и вычисляем хэш
На данный момент у нас есть хэш-код документа. Затем мы отправляем хэш в веб-сервис и получаем подписанный хэш-код.
Наконец, мы помещаем подписанный хэш в PDF:
sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA");
byte[] encodedSign = sgn.getEncodedPKCS7(hash, cal, null, null,
null, CryptoStandard.CMS);
byte[] paddedSig = new byte[8192];
System.arraycopy(encodedSign, 0, paddedSig, 0,
encodedSign.length);
PdfDictionary dic2 = new PdfDictionary();
dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
appearance.close(dic2);
В этот момент мы получаем подписанный PDF-файл, но с недействительной подписью. Adobe говорит, что «Документ был изменен или поврежден с момента его подписания».
Я прошел Подпишите PDF с помощью внешнего сервиса и iText, Подпись в формате PDF itext pkcs7 с несколькими знаками и Можно ли подписать PDF-документ хешем и подписанным хешем?, но не повезло.
@Dhanraj «iText имеет много ограничений, я бы посоветовал попробовать использовать PDFlib» - Как именно это поможет кому-либо подписать хэш PDF с помощью java и iText, о котором этот вопрос? Совсем нет, я бы сказал.
Приянка, поделитесь, пожалуйста, образцом подписанного PDF для анализа.
@mkl здесь можно получить один drive.google.com/file/d/13wpirorpEnj2dKi7ZTo76pYhx9Hfiz5v/…
Хммм, на самом деле подписанный хэш — это 1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B, который должен быть хешем аутентифицированных атрибутов, но их хэш — E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949. Хэш диапазонов байтов со знаком — это D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28, так что это тоже не этот хэш. Так что я действительно не знаю, что это за 11.... Возможно, вы захотите поделиться дополнительным кодом того, что происходит между String hashSignat = hashPdf и sgn.setExternalDigest(Base64.decode(hashSignat), null, "RSA")...
Большое спасибо @mkl за помощь. На самом деле утилита кодирования в веб-сервисе была другой, из-за чего декодированное хеш-значение было неправильным, и MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);) не нужен. эта строка давала неправильный хеш.
@mkl, но здесь, когда я подписываю уже подписанный файл, он получает знак, но с недопустимой подписью. показывает, что «диапазон байтов подписи недействителен». Пожалуйста, помогите мне с этим. И я действительно благодарен за ваш комментарий. это действительно помогло мне.
Для подписания уже подписанных PDF-файлов используйте режим добавления. Для этого вам нужна другая перегрузка PdfStamper.createSignature с еще одним параметром, boolean, для которого вы установили значение true.
Должен ли я создать реальный ответ из всех комментариев здесь, чтобы вы его приняли?
да. это будет здорово. @mkl Я просто запутался, почему значение байта «sh» каждый раз меняется для одного и того же файла? на самом деле я хочу сохранить хеш-значение файла. это из-за "кал"?
cal со временем подписания — это только одна часть, PDF-файл также содержит дату модификации и уникальный идентификатор редакции в хешированных данных.
поэтому @mkl могу ли я получить хэш за раз и сохранить его в БД, а позже подписать хеш и добавить в pdf?
Вы либо должны держать stamper и appearance открытыми на время, либо вам нужно сохранить подготовленный файл, либо вам нужно исправить iText, чтобы вы могли установить фиксированные значения для времени подписи, времени манипуляции и идентификатора.
@mkl 1-й вариант для меня невозможен. Я не получил 2-й 3-й вариант. Можете ли вы уточнить оба или дать какую-либо ссылку, на которую я могу сослаться?
У меня складывается впечатление, что вы забыли упомянуть некоторые граничные условия вашей задачи... :) Постараюсь посмотреть, скорее всего, где-то в понедельник или во вторник.
да. Мне жаль. Будет здорово, если вы поможете мне с этим. На самом деле, у меня будет веб-сервис, который предоставит хэш, который сохранится в БД, а затем отправит хэш в другой сервис, который подпишет хэш, а затем сохранит его в БД. а затем извлечет подписанный хэш из БД и добавит в pdf.




Вы поделились пример файла, подписанным вашим кодом, в комментарии к вашему вопросу.
Анализ этого файла (выполненный с использованием теста Анализ подписейtestPriyankaSignatureSampleinformedconsent_Signed) показывает, что фактически подписанный хеш
1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B
который должен быть хешем аутентифицированных атрибутов, но их хеш
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.
Хэш диапазонов байтов со знаком
D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,
так что это тоже не хэш. Таким образом, было непонятно, откуда взялся этот подписанный хэш.
В итоге оказалось, что
the encoding utility on web service was different due to which the decoded hash value was wrong and
MessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)is not needed. this line was giving the wrong hash.
После этого появилась новая проблема:
when I am signing file which is already signed is getting sign but with an invalid signature. its showing "the signature byte range is invalid".
Для подписи уже подписанных PDF-файлов вы должны использовать файл режим добавления. Для этого вам нужна другая перегрузка PdfStamper.createSignature с еще одним параметром, boolean, для которого вы установили значение true.
Причина в том, что обычно (т. е. без активации режим добавления) iText реорганизует внутренние структуры PDF и удаляет неиспользуемые объекты. В уже подписанных PDF-файлах это обычно перемещает положение (уже существующей) подписи, что делает структуру подписи недействительной. Однако, используя режим добавления, iText сохраняет исходные байты PDF такими, какими они были, и добавляет только новые данные.
I m just confused that why the value of byte "sh" is getting change for the same file every time? actually, I want to store the hash value of the file. is it because of "cal"?
Действительно, каждый раз, когда вы начинаете манипулировать PDF-файлом, результат получает новый уникальный идентификатор. Кроме того, сохраняется время модификации. И в случае варианта использования подписи также отличается время подписи.
can I get the hash at a time and store it in db and sign the hash later and append in pdf?
Вы либо должны
1st option is not possible for me. I didn't get the 2nd 3rd option. can you elaborate both or give any reference which I can refer?
Хорошо, во-первых, третий вариант, исправление iText, как правило, это то, что вы не хотите делать, потому что это затрудняет включение более поздних обновлений iText.
OpenPdf (старый форк iText) содержит патч, добавляющий свойства EnforcedModificationDate, OverrideFileId и IncludeFileID к PdfStamper. (PdfSignatureAppearance уже имеет свойство SignDate.) Это исправление было применено, чтобы позволить eSignature DSS использовать OpenPdf в процессе подписания (который также включает создание подписанного PDF дважды и поэтому требует фиксированных значений хэш-функции).
Возможно, вы не захотите переключаться на эту старую вилку iText, потому что в ней отсутствуют многие исправления и новые параметры более новых версий iText.
Таким образом, вам, вероятно, следует вместо этого сохранить исходный файл с пустой подписью, например. во временной папке и примените отложенное подписание, как только получите окончательную подпись.
По сути, это то, о чем идет речь в примере iText C4_09_Отложенная подпись, он сначала создает промежуточный PDF-файл со всем, что в нем есть, только байты подписи отсутствуют:
public void 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');
PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
appearance.setVisibleSignature(new Rectangle(36, 748, 144, 780), 1, fieldname);
appearance.setCertificate(chain[0]);
ExternalSignatureContainer external = new ExternalBlankSignatureContainer(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
MakeSignature.signExternalContainer(appearance, external, 8192);
}
Только на втором этапе вводится фактическая подпись:
public void createSignature(String src, String dest, String fieldname, PrivateKey pk, Certificate[] chain) throws IOException, DocumentException, GeneralSecurityException {
PdfReader reader = new PdfReader(src);
FileOutputStream os = new FileOutputStream(dest);
ExternalSignatureContainer external = new MyExternalSignatureContainer(pk, chain);
MakeSignature.signDeferred(reader, fieldname, os, external);
}
с использованием
class MyExternalSignatureContainer implements ExternalSignatureContainer {
protected PrivateKey pk;
protected Certificate[] chain;
public MyExternalSignatureContainer(PrivateKey pk, Certificate[] chain) {
this.pk = pk;
this.chain = chain;
}
public byte[] sign(InputStream is) throws GeneralSecurityException {
try {
PrivateKeySignature signature = new PrivateKeySignature(pk, "SHA256", "BC");
String hashAlgorithm = signature.getHashAlgorithm();
BouncyCastleDigest digest = new BouncyCastleDigest();
PdfPKCS7 sgn = new PdfPKCS7(null, chain, hashAlgorithm, null, digest, false);
byte hash[] = DigestAlgorithms.digest(is, digest.getMessageDigest(hashAlgorithm));
Calendar cal = Calendar.getInstance();
byte[] sh = sgn.getAuthenticatedAttributeBytes(hash, cal, null, null, CryptoStandard.CMS);
byte[] extSignature = signature.sign(sh);
sgn.setExternalDigest(extSignature, null, signature.getEncryptionAlgorithm());
return sgn.getEncodedPKCS7(hash, cal, null, null, null, CryptoStandard.CMS);
}
catch (IOException ioe) {
throw new ExceptionConverter(ioe);
}
}
public void modifySigningDictionary(PdfDictionary signDic) {
}
}
Здесь has рассчитывается на втором шаге, но вам не нужно этого делать, вы можете
ExternalBlankSignatureContainer, как в emptySignature() выше, а вместо этого расширив его для вычисления хэша, как это делает MyExternalSignatureContainer,MyExternalSignatureContainer, который не вычисляет хэш, а вместо этого вводит точно возвращенную подпись.Привет @mkl, я немного запутался со следующими шагами: - уже вычислил его на первом шаге, не используя исходный ExternalBlankSignatureContainer, как в emptySignature() выше, а вместо этого расширив его для вычисления хэша, как это делает MyExternalSignatureContainer, сохранив это значение в вашем базу данных и (как только вы получите подпись) внедрить эту подпись, используя вариант MyExternalSignatureContainer, который не вычисляет хэш, а вместо этого вводит точно возвращенную подпись. В отложенной подписи нам нужен закрытый ключ, а у меня не будет доступа к закрытому ключу.
Спасибо @mkl. Большое спасибо. мне потребовалось некоторое время, чтобы понять, но этот код мне очень помог. Спасибо большое. :) ... Я сослался на следующую ссылку, которая помогла мне реализовать мое требование вместе с вашим решением: - stackoverflow.com/questions/47505696/…
Этот пример (C4_09_DeferredSigning) всегда приводится для внешней подписи хешей, но на самом деле он не имеет смысла. Вы не можете использовать хэш, полученный на первом шаге. В реальном случае хэш должен быть получен, пока пустая подпись и пользователь подписывает этот хэш на своем рабочем столе, а подписанный хеш должен использоваться на втором этапе. Но к сожалению это невозможно без использования одного и того же объекта. Патч iText для меня более интересен, но как использовать эти поля?
@EbruYener «Этот пример ( C4_09_DeferredSigning ) всегда приводится для внешней подписи хешей, но на самом деле он бессмыслен». - нет, это не бессмысленно. «Вы не можете использовать хэш, полученный на первом шаге» - о да, можно.... просто сказал, я не понимаю, что вы хотите обсудить в комментарии. Лучше сделайте это актуальным вопросом и более подробно опишите проблему в вашем случае использования.
iText имеет много ограничений, я бы посоветовал попробовать использовать PDFlib