Файл примера
Вы предоставили файл примера , подписанный вашим кодом в комментарии к вашему вопросу.
Анализ этого файла (выполняется с использованием AnalyzeSignatures test testPriyankaSignatureSampleinformedconsent_Signed
) показывает, что фактически подписанный хэш равен
1134ED96F42AC7352E546BE0E906C0BF5D44229AEAFAC39145DB40A0BB7E817B
, который должен быть хешем аутентифицированных атрибутов, но их хеш равен
E7101D9770ABF5E58D43670AAC6E9418265AE80F74B3BDFB67911C0CC5D5D949.
.хэш диапазонов байтов со знаком равен
D7917CF3BA9FE5D5B626ED825965D699F7C54DBBF9F18DECE18EF8DD36DC4C28,
, так что это тоже не этот хеш.Таким образом, было неясно, откуда взялся этот подписанный хеш.
В итоге оказалось, что
утилита кодирования в веб-сервисе отличалась из-за того, что декодированное хеш-значение было неправильным иMessageDigest(sh = MessageDigest.getInstance("SHA256", "BC").digest(sh);)
не нужно.эта строка давала неверный хеш.
Подписание ранее подписанных файлов
После этого возникла новая проблема:
, когда я подписываю файл, которыйуже подписанный получает знак, но с недействительной подписью.он показывает «диапазон байтов подписи недействителен».
Для подписания уже подписанных PDF-файлов вы должны использовать режим добавления .Для этого вам понадобится другая перегрузка PdfStamper.createSignature
с еще одним параметром, boolean
, для которого вы установите true
.
Причина в том, что обычно (т.е. без активации режима добавления )1044 *) iText реорганизует внутренние структуры PDF и удаляет неиспользуемые объекты.В уже подписанных PDF-файлах это обычно перемещает позицию (уже существующей) подписи, которая делает недействительной структуру подписи.Используя режим добавления , iText сохраняет исходные байты PDF такими, какими они были, и только добавляет новые данные.
Изменение хешей
Я просто запутался, почемузначение байта "sh" меняется каждый раз для одного и того же файла?на самом деле, я хочу сохранить хеш-значение файла.это из-за "cal"?
Действительно, каждый раз, когда вы начинаете манипулировать PDF, результат получает новый уникальный идентификатор.Кроме того, время модификации сохраняется.И в случае использования подписи, время подписи также отличается.
Могу ли я получить хэш за раз, сохранить его в db, подписать хэш позже и добавить в pdf?
Вы должны либо
- оставить штамп и внешний вид открытым какое-то время, либо вам нужно
- сохранить подготовленный файл или
- патч iText, позволяющий вам установить фиксированные значения времени подписи, времени манипуляции и идентификатора.
1-й вариант для меня невозможен.Я не получил 2-й 3-й вариант.Можете ли вы уточнить оба или дать какую-либо ссылку, на которую я могу сослаться?
Исправление iText для принудительного применения фиксированных хэшей для каждого файла
Хорошо, сначала отключим третий вариант, исправление iText, обычно эточто-то, чего вы не хотите делать, потому что это затрудняет включение более поздних обновлений iText.
OpenPdf (более старая ветка iText) содержит патч, добавляющий свойства EnforcedModificationDate
, OverrideFileId
и IncludeFileID
к PdfStamper
.(PdfSignatureAppearance
уже имеет свойство SignDate
.) Этот патч был применен, чтобы позволить eSignature DSS использовать OpenPdf в процессе подписи (который также включает создание подписанного PDF дважды и поэтому требует фиксированных значений хеша).
Возможно, вы не захотите переключаться на эту старую вилку iText, поскольку она пропускает множество исправлений и новых опций в новых версиях iText.
Сохранение уже подготовленного файла
Таким образом, вы, вероятно, должны вместо этого сохранитьпервоначально созданный файл с пустой подписью, например, в какой-то временной папке, и применить отложенную подпись , как только вы получите окончательную подпись.
По сути, это пример iText C4_09_DeferredSigning - это все, сначала создается промежуточный 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
, который не вычисляет хеш, а вместо этого вводит точно возвращенную подпись.