Расширения файлов «.key» и «.cer» никоим образом не являются однозначной спецификацией того, как кодируются ключи. Однако вполне вероятно, что файл ".cer" является сертификатом X.509, который содержит (среди прочего) открытый ключ; следовательно, вы можете использовать классы X509Certificate
и X509Certificate2
(в пространстве имен System.Security.Cryptography.X509Certificates
) для декодирования сертификата и извлечения открытого ключа.
Однако вам нужен закрытый ключ, а не открытый ключ. документация MSDN по X509Certificate2 достаточно сбивает с толку, потому что он использует термин «сертификат» для обозначения либо общедоступной части (то, что у вас есть в вашем файле «.cer»), либо объединения общедоступной и частной частей в виде единого файла (в формате, который MSDN описывает как « PKCS7 (аутентикод)").
Закодированный закрытый ключ RSA обычно соответствует формату, описанному в PKCS#1, который не сложен, но по-прежнему зависит от ASN.1, использование которого требует осторожности. Иногда такие ключи RSA заключаются в более крупную структуру, в которой также указывается тип ключа (т. е. ключ предназначен для RSA); подробности см. в PKCS#8. Кроме того, оба типа кодировок ключей обычно представлены в формате PEM: это Base64 с заголовком (-----BEGIN RSA PRIVATE KEY-----
) и нижний колонтитул. Конечно, ваш закрытый ключ может быть в любом формате (расширение «.key» не слишком информативно). При желании и PKCS#8, и PEM могут быть симметрично зашифрованы (с ключом, полученным из пароля). Существует также формат PKCS#12, который можно рассматривать как формат архива для коллекции сертификатов и закрытых ключей, обертывающий предыдущие форматы; PKCS#12 включает в себя множество уровней шифрования и известен в мире Microsoft под названием «PFX» (или «файл сертификата», что продолжает сбивать с толку).
Декодирование всех этих форматов возможно с помощью небольшого количества кода, но на этом этапе рекомендуется использовать библиотеку, которая уже выполняет такую работу, вместо создания собственной. Bouncy Castle обычно подозревается в этой работе.
Инструмент командной строки OpenSSL может помочь вам преобразовать некоторые форматы ключей и сертификатов.
Редактировать: если ваш закрытый ключ имеет формат PKCS#8 DER и не защищен паролем (PKCS#8 может это сделать), то вы можете декодировать его относительно простой код. DER — это набор правил преобразования структурированных данных в последовательность байтов. Элемент данных кодируется тремя последовательными частями:
- тег, который сообщает, какое это значение
- длина, которая кодирует количество байтов в третьей части
- значение, которое должно интерпретироваться как указанное в теге
отсюда и название «TLV» (как «тег, длина, значение»). Некоторые элементы сами являются структурами, содержащими подэлементы, и в этом случае значение состоит из объединения кодировок подэлементов, каждый со своим собственным тегом, длиной и значением.
Тег обычно состоит из одного байта; для ключей PKCS#8 и RSA вас интересуют теги 0x30 (для «SEQUENCE», т. е. элемента с подэлементами), 0x02 («INTEGER»: целочисленное значение) и 0x04 («OCTET STRING»: большой двоичный объект) .
Длина кодируется одним из следующих способов:
- один байт значения n от 0 до 127 (включительно): это кодирует длину n;
- байт со значением n, равным или превышающим 129, за которым следует точно n-128 байтов, которые кодируют длину в формате с обратным порядком байтов. Например, длина 324 будет закодирована как три байта: 0x82 0x01 0x44. Это читается как: «0x82 равно 128+2, следовательно, я должен прочитать два дополнительных байта; длина 256*0x01+0x44 = 324».
Для INTEGER значение должно интерпретироваться в соответствии со знаком и обратным порядком байтов (первый байт является наиболее значимым, а старший бит первого байта определяет знак целого числа; для RSA все значения положительны, поэтому первый байт должен иметь значение от 0 до 127). Обратите внимание, что System.Numerics.BigInteger
в .NET 4.0 имеет конструктор, который может декодировать группу байтов, но он ожидает их в соглашении с прямым порядком байтов, а не с прямым порядком байтов, поэтому вам придется изменить порядок байтов.
Структура PKCS#8:
PrivateKeyInfo ::= SEQUENCE {
version Version,
privateKeyAlgorithm AlgorithmIdentifier,
privateKey OCTET STRING,
attributes [0] Attributes OPTIONAL
}
Version ::= INTEGER { v1(0) }
Это нотация ASN.1. Здесь необходимо понимать, что объект является элементом ПОСЛЕДОВАТЕЛЬНОСТИ: он кодируется как тег ПОСЛЕДОВАТЕЛЬНОСТИ (0x30), затем длина (n), затем значение (n байт, точно). Затем значение состоит из последовательности закодированных элементов, каждый из которых имеет формат TLV. Первый элемент — это INTEGER, который должен иметь числовое значение 0 при нормальных условиях (ноль кодируется как «0x02 0x01 0x00»). Второй элемент — это AlgorithmIdentifier
, который я здесь не описываю; на самом деле это ПОСЛЕДОВАТЕЛЬНОСТЬ, и она определяет тип ключа (здесь должно быть сказано «это ключ RSA»); просто прочитайте тег (должен быть 0x30), затем длину и пропустите значение. Третий элемент — это СТРОКА ОКТЕТОВ: тег 0x04, затем длина m и значение m байт. Это то, что нас интересует. Это значение, которое является содержимым ОКТЕТНОЙ СТРОКИ, должно быть извлечено; мы расшифруем его в следующем абзаце. Четвертый элемент PrivateKeyInfo
SEQUENCE является необязательным (его может вообще не быть и обычно не будет) и может использоваться для кодирования различных расширений этого формата.
Предположим, что вы извлекли содержимое ОКТЕТНОЙ СТРОКИ. Это последовательность байтов, которая на самом деле является кодировкой DER структуры, которая в ASN.1 выглядит следующим образом:
RSAPrivateKey ::= SEQUENCE {
version INTEGER,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
-- otherPrimeInfos must be absent if version is two-prime,
-- present if version is multi-prime.
}
Таким образом, содержимое OCTET STRING должно начинаться с 0x30 (тег SEQUENCE), затем длина (r), затем r байт. Эти r байты представляют собой кодировку девяти целых чисел. Первое ЦЕЛОЕ ЧИСЛО должно быть 0; если это не так, то ключ RSA имеет более двух простых множителей, и вы обречены. Восемь последующих целых чисел — это искомые целые числа; просто расшифруйте их, и все готово. Последнее поле (otherPrimeInfos
) является необязательным и должно отсутствовать, если ваш ключ RSA является «нормальным» ключом RSA (с двумя простыми делителями, а не с тремя или более).
person
Thomas Pornin
schedule
21.03.2011