Ручная проверка подписи XML

Я могу успешно выполнить ручную проверку ссылок (канонизировать каждый ссылочный элемент -> SHA1 -> Base64 -> проверить, совпадает ли это с содержимым DigestValue), но мне не удается проверить SignatureValue. Вот SignedInfo для канонизации и хэширования:

<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
 <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>
 <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
 <ds:Reference URI="#element-1-1291739860070-11803898">
  <ds:Transforms>
   <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
  </ds:Transforms>
  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
  <ds:DigestValue>d2cIarD4atw3HFADamfO9YTKkKs=</ds:DigestValue>
 </ds:Reference>
 <ds:Reference URI="#timestamp">
  <ds:Transforms>
   <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
  </ds:Transforms>
  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
  <ds:DigestValue>YR/fZlwJdw+KbyP24UYiyDv8/Dc=</ds:DigestValue>
 </ds:Reference>
</ds:SignedInfo>

После удаления всех пробелов между тегами (и, таким образом, получая весь элемент в одной строке), я получаю этот дайджест sha1 (в Base64):

6l26iBH7il / yrCQW6eEfv / VqAVo =

Теперь я ожидаю найти тот же дайджест после расшифровки контента SignatureValue, но получаю другое и более длинное значение:

MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH + u6R4N8Ig =

Вот некоторый Java-код для расшифровки:

      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();    
  DocumentBuilder builder = dbf.newDocumentBuilder();  
  Document doc = builder.parse(new File(inputFilePath));
  NodeList nl = doc.getElementsByTagName("ds:SignatureValue");
  if (nl.getLength() == 0) {
     throw new Exception("Cannot find SignatureValue element");
   }
  String signature = "OZg96GMrGh0cEwbpHwv3KDhFtFcnzPxbwp9Xv0pgw8Mr9+NIjRlg/G1OyIZ3SdcOYqqzF4/TVLDi5VclwnjBAFl3SEdkyUbbjXVAGkSsxPQcC4un9UYcecESETlAgV8UrHV3zTrjAWQvDg/YBKveoH90FIhfAthslqeFu3h9U20=";
  X509Certificate cert = X509Certificate.getInstance(new FileInputStream(<a file path>));
  PublicKey pubkey = cert.getPublicKey();
  Cipher cipher = Cipher.getInstance("RSA","SunJCE");
  cipher.init(Cipher.DECRYPT_MODE, pubkey);
  byte[] decodedSignature = Base64Coder.decode(signature);
  cipher.update(decodedSignature);
  byte[] sha1 = cipher.doFinal();


  System.out.println(Base64Coder.encode(sha1));

Меня сильно смущает то, что два дайджеста имеют разный размер, но, конечно, мне также нужно получить одно и то же значение из двух вычислений. Какие-либо предложения? Спасибо.

2 ответа

Решение

MCEwCQYFKw4DAhoFAAQU3M24VwKG02yUu6jlEH+u6R4N8Ig= кодирование Base64 для структуры ASN.1 в кодировке DER: SEQUENCE содержащий сначала AlgorithmIdentifier (в котором говорится, что это SHA-1, без параметров, поскольку SHA-1 не принимает ни одного), затем OCTET STRING который содержит фактическое 20-байтовое значение. В шестнадцатеричном формате это значение: dccdb8570286d36c94bba8e5107faee91e0df088,

Эта структура ASN.1 является частью стандартного механизма подписи RSA. Вы используете расшифровку RSA для доступа к этой структуре, которая является нестандартной. На самом деле вам повезло что-либо получить, поскольку шифрование RSA и подпись RSA - это два разных алгоритма. Бывает так, что они оба используют одинаковые пары ключей, и что схемы подписи и шифрования старого стиля (PKCS#1 v1.5) используют аналогичные методы заполнения (похожие, но не идентичные; уже немного удивительно, что реализация RSA на Java не подавляет заполнение подписи при использовании в режиме дешифрования).

Тем не мение, 6l26iBH7il/yrCQW6eEfv/VqAVo= это кодировка Base64 для 20-байтового значения, которое в шестнадцатеричном виде: ea5dba8811fb8a5ff2ac2416e9e11fbff56a015a, Это то, что вы получаете, хэшируя структуру XML, показанную выше, после удаления всех пробелов между тегами. Удаление всех пробелов не является правильной каноникой. На самом деле, насколько я знаю, пробелы затрагиваются только между атрибутами внутри тегов, но внешний пробел должен оставаться неизменным (за исключением нормализации конца строки [вещь LF / CR+LF]).

Значение, которое использовалось для генерации подписи (dccdb85...) можно получить, используя показанный вами объект XML и удалив начальные пробелы. Для ясности: вы копируете + вставляете XML в файл, затем удаляете начальные пробелы (от 0 до 3 пробелов) в каждой строке. Вы убедитесь, что все конец строки используют один LF (0x0A байт), и вы удаляете последний LF (тот, что сразу после </ds:SignedInfo>). Полученный файл должен иметь длину 930 байт, и его хэш SHA-1 является ожидаемым dccdb85... значение.

Глядя на ваш конкретный XML-токен, я могу рассказать вам несколько вещей.

  • Вы используете метод канонизации Исключительная XML Канонизация Версия 1.0. Это очень важный фактор, гарантирующий, что вы создадите правильные значения дайджеста и подпись.

  • Вы используете один и тот же метод канонизации как для вычисления эталонных дайджестов, так и для канонизации SignedInfo перед созданием подписи.

Спецификация для эксклюзивного XML Canonicalizaiton версии 1.0 производится W3C и может быть найдена в соответствующей Рекомендации W3C. Если вы вычисляете свои значения вручную, убедитесь, что вы точно соответствуете спецификации, потому что канонизацию трудно сделать правильно, и очень важно сделать это правильно, иначе ваши значения будут неправильными.

Я только что написал обширную статью, описывающую процесс проверки подписи XML. Статья находится в моем блоге. Он описывает процесс гораздо более подробно, чем мой ответ, поскольку в XML Signature много сложностей. Он также содержит ссылки на распространенные спецификации и RFC.

Другие вопросы по тегам