Я готовлю минидрайвер для выполнения входа в смарт-карту с помощью NCryptSignHash
функция Microsoft CNG.
Когда я выполняю подпись с ключом EC SECP521R1 в смарт-карте, он генерирует данные знака длиной 139 в виде формата данных со знаком ECC:
ECDSASignature ::= SEQUENCE {
r INTEGER,
s INTEGER
}
Образец подписанных данных
308188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E534BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4
Но когда я выполняю Sign, используя MS_KEY_STORAGE_PROVIDER
генерирует знак длиной 132 байта.
Какова процедура для уменьшения размера данных знака с 139 до 132?
Ваш ввод — это формат подписи X9.62, который представляет собой ПОСЛЕДОВАТЕЛЬНОСТЬ, содержащую две подписи в кодировке ASN.1 / DER. Эти целые числа имеют переменные размеры, числа со знаком, большие порядковые номера. Они закодированы в минимальном количестве байтов. Это означает, что размер кодировки может варьироваться.
139 байтов являются общими, поскольку они предполагают максимальный размер кодирования для r
а также s
, Эти значения рассчитываются с использованием модульная арифметические и, следовательно, они могут содержать любое количество битов, вплоть до количества битов порядка n
, что соответствует размеру ключа, 521 бит.
132 байта определены стандартом ISO / IEC 7816-8 / IEEE P1363, который является стандартом, касающимся подписей для смарт-карт. Подпись состоит из объединения r
а также s
, где r
а также s
кодируются как минимальное количество байтов для отображения значения того же размера, что и порядок, в байтах. r
а также s
статические по размеру, без знака, большие порядковые номера.
Расчет количества байтов r
или же s
является ceil((double) n / 8)
или же (n + 8 - 1) / 8
где 8 — количество бит в байте. Таким образом, если эллиптическая кривая составляет 521 бит, то результирующий размер составляет 66 байтов, и вместе они, следовательно, потребляют 132 байта.
Теперь перейдем к расшифровке. Есть несколько способов справиться с этим: выполнить полный анализ ASN.1, получить целые числа и затем снова закодировать их в форме ISO 7816-8 — наиболее логичный.
Тем не менее, вы также можете увидеть, что вы можете просто скопировать байты как r
а также s
всегда будет неотрицательным (и, следовательно, без знака) и прямым порядком байтов. Так что вам просто нужно компенсировать размер. В противном случае единственная сложная часть — это возможность декодировать длину компонентов в структуре X9.62.
Предупреждение: код на C # вместо C ++, как я и ожидал, основной язык .NET; Язык не указан в вопросе, когда я написал основную часть ответа.
class ConvertECDSASignature
{
private static int BYTE_SIZE_BITS = 8;
private static byte ASN1_SEQUENCE = 0x30;
private static byte ASN1_INTEGER = 0x02;
public static byte[] lightweightConvertSignatureFromX9_62ToISO7816_8(int orderInBits, byte[] x9_62)
{
int offset = 0;
if (x9_62[offset++] != ASN1_SEQUENCE)
{
throw new IllegalSignatureFormatException("Input is not a SEQUENCE");
}
int sequenceSize = parseLength(x9_62, offset, out offset);
int sequenceValueOffset = offset;
int nBytes = (orderInBits + BYTE_SIZE_BITS - 1) / BYTE_SIZE_BITS;
byte[] iso7816_8 = new byte[2 * nBytes];
// retrieve and copy r
if (x9_62[offset++] != ASN1_INTEGER)
{
throw new IllegalSignatureFormatException("Input is not an INTEGER");
}
int rSize = parseLength(x9_62, offset, out offset);
copyToStatic(x9_62, offset, rSize, iso7816_8, 0, nBytes);
offset += rSize;
// --- retrieve and copy s
if (x9_62[offset++] != ASN1_INTEGER)
{
throw new IllegalSignatureFormatException("Input is not an INTEGER");
}
int sSize = parseLength(x9_62, offset, out offset);
copyToStatic(x9_62, offset, sSize, iso7816_8, nBytes, nBytes);
offset += sSize;
if (offset != sequenceValueOffset + sequenceSize)
{
throw new IllegalSignatureFormatException("SEQUENCE is either too small or too large for the encoding of r and s");
}
return iso7816_8;
}
/**
* Copies an variable sized, signed, big endian number to an array as static sized, unsigned, big endian number.
* Assumes that the iso7816_8 buffer is zeroized from the iso7816_8Offset for nBytes.
*/
private static void copyToStatic(byte[] sint, int sintOffset, int sintSize, byte[] iso7816_8, int iso7816_8Offset, int nBytes)
{
// if the integer starts with zero, then skip it
if (sint[sintOffset] == 0x00)
{
sintOffset++;
sintSize--;
}
// after skipping the zero byte then the integer must fit
if (sintSize > nBytes)
{
throw new IllegalSignatureFormatException("Number format of r or s too large");
}
// copy it into the right place
Array.Copy(sint, sintOffset, iso7816_8, iso7816_8Offset + nBytes - sintSize, sintSize);
}
/*
* Standalone BER decoding of length value, up to 2^31 -1.
*/
private static int parseLength(byte[] input, int startOffset, out int offset)
{
offset = startOffset;
byte l1 = input[offset++];
// --- return value of single byte length encoding
if (l1 < 0x80)
{
return l1;
}
// otherwise the first byte of the length specifies the number of encoding bytes that follows
int end = offset + l1 & 0x7F;
uint result = 0;
// --- skip leftmost zero bytes (for BER)
while (offset < end)
{
if (input[offset] != 0x00)
{
break;
}
offset++;
}
// --- test against maximum value
if (end - offset > sizeof(uint))
{
throw new IllegalSignatureFormatException("Length of TLV is too large");
}
// --- parse multi byte length encoding
while (offset < end)
{
result = (result << BYTE_SIZE_BITS) ^ input[offset++];
}
// --- make sure that the uint isn't larger than an int can handle
if (result > Int32.MaxValue)
{
throw new IllegalSignatureFormatException("Length of TLV is too large");
}
// --- return multi byte length encoding
return (int) result;
}
}
Обратите внимание, что код является несколько разрешающим в том, что он не требует минимальный кодирование длины для кодирования длины SEQUENCE и INTEGER (что и должно быть).
Это также позволяет неправильно кодировать значения INTEGER, которые излишне дополняются левым нулем.
Ни одна из этих проблем не должна нарушать безопасность алгоритма, но другие библиотеки могут и должны быть менее разрешительными.
Какова процедура для уменьшения размера данных знака с 139 до 132?
У вас есть подпись в кодировке ASN.1 (показано ниже). Используется Java, OpenSSL и некоторыми другими библиотеками. Вам нужна подпись в формате P1363, которая является объединением r || s
без кодировки ASN.1. P1363 используется Crypto ++ и несколькими другими библиотеками. (Есть еще один распространенный формат подписи, и это OpenPGP).
Для объединения r || s
, и то и другое r
а также s
должен быть 66 байтов из-за размера элемента поля secp-521r1 на границе октета. Это означает, что процедура, вы должны лишить внешнего SEQUENCE
, а затем раздеть два INTEGER
, а затем объединить значения двух целых чисел.
Ваш отформатированный r || s
подпись с использованием вашего образца данных будет:
01 A2 00 1E ... 7F D8 67 01 || 00 C1 03 E5 ... 0A 26 A7 F4
Microsoft .Net 2.0 имеет классы ASN.1, которые позволяют вам манипулировать данными, закодированными в ASN.1. Увидеть Класс AsnEncodedData.
$ echo 08188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB
81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E5
34BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F
743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4 | xxd -r -p > signature.bin
$ dumpasn1 signature.bin
0 136: SEQUENCE {
3 66: INTEGER
: 01 A2 00 1E 9C 01 51 C5 5B CA 18 8F 20 10 20 A8
: 41 80 B3 39 E6 1E DE 61 F6 EA D0 B2 77 32 1C AB
: 81 C8 7D AF C2 AC 65 D5 42 D0 D0 B0 1C 3C 5E 25
: E9 20 9C 47 CF DD FD 5B BC AF A0 D2 AF 2E 7F D8
: 67 01
71 66: INTEGER
: 00 C1 03 E5 34 BD 13 78 D8 B6 F5 65 2F B0 58 F7
: D5 04 56 15 DC D9 40 46 2E D0 F9 23 07 30 76 EF
: 58 12 10 D0 DD 95 BF 28 91 35 8F 5F 74 3D B2 EC
: 00 9A 06 08 CE FA A9 A4 0A F4 17 18 88 1D 0A 26
: A7 F4
: }
0 warnings, 0 errors.
Еще один интересный момент: .Net использует формат XML, подробно RFC 3275, Синтаксис и обработка XML-подписи. Это другой формат, чем ASN.1, P1363, OpenPGP, CNG и другие библиотеки.
Преобразование ASN.1 в P1363 довольно тривиально. Вы можете увидеть пример использования библиотеки Crypto ++ на ECDSA подписать с BouncyCastle и проверить с помощью Crypto ++.
Вы можете найти Криптографическая совместимость: цифровые подписи на Code Project полезно.