Почему мой идентификатор ключа не совпадает?

Я пытаюсь расшифровать электронное письмо S/MIME (первоначально отправленное через Outlook), и для этого я использую API bouncycastle. Однако я попадаю в ловушку.

В хранилище сертификатов Windows у меня есть сертификат получателя. Ранее я использовал его для отправки подписанного и зашифрованного электронного письма другой стороне, а они, в свою очередь, использовали его для отправки мне зашифрованного ответа. Затем я экспортировал сертификат (с закрытым ключом) в виде файла .pfx и загрузил этот файл pfx в хранилище ключей Java. Однако это не работает, и я подозреваю, что это потому, что идентификаторы ключей субъекта не совпадают.

Вот код, который я использую для получения идентификатора ключа темы из KeyStore:

KeyStore ks = KeyStore.getInstance("PKCS12");
char[]   pw = "password".toCharArray();

ks.load(new FileInputStream("d:\\cert_priv_key.pfx"), pw);

Enumeration en = ks.aliases();

while( en.hasMoreElements() )
{
    String alias = (String)en.nextElement();
    System.out.println(alias);

    if( ks.isKeyEntry(alias) )
    {
        Certificate[]   chain = ks.getCertificateChain(alias);
        X509Certificate cert  = (X509Certificate)chain[0];

        byte[] id = cert.getExtensionValue("2.5.29.14");

        System.out.println("  " + toHex(id));
    }
}

Это распечатывает следующий идентификатор ключа:

04 16 04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

Однако, когда я проверяю хранилище сертификатов Windows, идентификатор ключа отличается:

88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

KeyStore возвращает дополнительные 4 байта впереди (идентификатор ключа субъекта должен быть 160-битным хэшем SHA1 ключа, и, следовательно, длиной 20 байтов, верно?).

Еще больше сбивает с толку тот факт, что когда я анализирую электронное письмо S/MIME с помощью API bouncycastle и просматриваю получателей (SMIMEEnveloped.getRecipientInfos().getRecipients()), единственный возвращенный получатель (должен быть только один) имеет этот идентификатор ключа темы:

04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

... в нем всего два дополнительных байта, а не четыре, и я предполагаю, что именно поэтому я не могу расшифровать электронное письмо с сертификатом.

Почему ни один из этих идентификаторов ключей субъекта не совпадает? Что я делаю не так?


person Mark    schedule 29.06.2011    source источник


Ответы (3)


Все эти ответы согласуются, если вы понимаете все спецификации, но, конечно, это означает, что они сбивают с толку, если вы этого не сделаете. В первую очередь нужно искать в RFC 5280, раздел 4.2.1.2. В этом случае используется метод (1). Затем просмотрите раздел A.2 определение KeyIdentifier. Он определяется как СТРОКА ОКТЕТОВ. Теперь посмотрите, как должна быть закодирована СТРИНА ОКТЕТОВ ASN.1. Он должен начинаться с шестнадцатеричного числа 04, за которым следует длина в байтах (20 байт или 14 шестнадцатеричных), за которой следует фактическая строка октетов (хэш SHA1). Таким образом, содержимое расширения должно быть

04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

Наконец, взгляните на определение Extension в ASN.1. В нем говорится, что значение расширения должно быть закодировано как ОКТЕТНАЯ СТРОКА. В случае с этим конкретным расширением чистый эффект заключается в том, что кодируется два раза подряд как СТРОКА ОКТЕТОВ. На этом уровне СТРОКА ОКТЕТОВ — это предыдущая СТРОКА ОКТЕТОВ, которая включает два байта заголовка 04 14, поэтому длина равна 16 шестнадцатеричным, а кодировка

04 16 04 14 88 ed bb 7c 64 7b 41 63 48 0a 24 40 2b 3c d0 78 72 3c 30 b3

Это значение, возвращаемое X509Extension.getExtensionValue(). Теперь интересная часть идентификатора ключа — это просто хэш SHA1, который начинается с 88, так что это то, что отображает утилита Windows. Очевидно, что метод bouncycastle, который вы используете, отображает расширение без дополнительного декодирования.

person President James K. Polk    schedule 30.06.2011
comment
Мне удалось расшифровать электронное письмо, используя закрытый ключ напрямую, но я все еще не могу заставить его работать, используя нерекомендуемые методы. Я предполагаю, что X509Extension.getExtensionValue() должен удалять один слой кодирования из значения, когда оно возвращает его. Это бы совпало. - person Mark; 30.06.2011
comment
Также я предполагаю, что вы имели в виду ... дополнительное декодирование? - person Mark; 30.06.2011
comment
@Mark: повторное удаление слоя. Первоначально я так и думал, но после небольшого размышления я теперь думаю, что getExtensionValue() делает правильную вещь. - person President James K. Polk; 01.07.2011
comment
@GregS: Итак, вы считаете, что либо код, соответствующий идентификаторам ключей темы, неверен (должен выполнять 2-кратное декодирование на стороне сертификата и 1-кратное декодирование на стороне SMIME), либо сообщение SMIME закодировано неправильно? - person Mark; 01.07.2011
comment
@Mark: Вот как я бы это сказал. Метод getExtensionValue() не знает, что означает расширение, которое вы запрашиваете. Источник, наверное, очень простой. Код SMIME больше ориентирован на типы. Он знает, что это не просто расширение, а расширение SubjectKeyIdentifier, и, таким образом, эта СТРОКА ОКТЕТОВ фактически кодирует другую СТРОКУ ОКТЕТОВ. Признаюсь, я не слишком знаком с bcmail api, я вроде как догадываюсь. - person President James K. Polk; 01.07.2011


Принятый ответ от GregS мне очень помог.

Код, который в итоге сработал для меня:

X509Certificate certificate = ...
byte[] encExtensionSubjectKeyIdentifier = certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId());

// Unwrap first 'layer'
ASN1Primitive skiPrimitive = JcaX509ExtensionUtils.parseExtensionValue(encExtensionSubjectKeyIdentifier);

// Unwrap second 'layer'
byte[] keyIdentifier = ASN1OctetString.getInstance(skiPrimitive.getEncoded()).getOctets();

// Use keyIdentifier in e.g. CMS SignerInfo
SignerInfoGenerator signerInfoGenerator = jcaSignerInfoGeneratorBuilder.build(sha1Signer, keyIdentifier);
person Marcus Wallin    schedule 29.04.2015