iOS*_*r84 2 c++ cryptography elliptic-curve cng
我正在准备一个微型驱动程序,以使用 Microsoft CNG 的功能执行智能卡登录NCryptSignHash
。
当我使用智能卡中的 SECP521R1 EC 密钥执行签名时,它会生成长度为 139 的签名数据作为 ECC 签名数据格式:
ECDSASignature ::= SEQUENCE {
r INTEGER,
s INTEGER
}
Run Code Online (Sandbox Code Playgroud)
签名数据样本是
308188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E534BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4
Run Code Online (Sandbox Code Playgroud)
但是当我使用它执行 Sign 时,MS_KEY_STORAGE_PROVIDER
它会生成一个长度为 132 字节的符号。
将符号数据大小从 139 减少到 132 的过程是什么?
您的输入是 X9.62 签名格式,它是包含两个 ASN.1 / DER 编码签名的 SEQUENCE。这些整数是可变大小、有符号、大端数。它们以最少的字节数进行编码。这意味着编码的大小可以变化。
r
139 字节很常见,因为它假定和的编码的最大大小s
。这些值是使用模算术计算的,因此它们可以包含任意数量的位数,最多可达 order 的位数n
,这与密钥大小相同,即 521 位。
132 字节由 ISO/IEC 7816-8 / IEEE P1363 指定,该标准是处理智能卡签名的标准。签名由r
和的串联组成s
,其中r
和s
被编码为显示与订单大小相同的值的最小字节数(以字节为单位)。和是静态大小、无符号r
、s
大端数。
r
or的字节数的计算方法s
是ceil((double) n / 8)
or(n + 8 - 1) / 8
其中 8 是一个字节中的位数。因此,如果椭圆曲线为 521 位,则结果大小为 66 字节,因此它们总共消耗 132 字节。
现在开始解码。有多种方法可以处理此问题:执行完整的 ASN.1 解析,获取整数,然后以 ISO 7816-8 形式再次将它们编码回来,这是最合乎逻辑的方法。
但是,您还可以看到,您可以简单地将字节复制为 ,r
并且s
将始终为非负(因此无符号)和大端字节序。所以你只需要补偿尺寸即可。否则,唯一困难的部分是能够解码 X9.62 结构内组件的长度。
警告:使用 C# 编写代码,而不是我期望的主要 .NET 语言中的 C++;当我写答案的主要部分时,没有指出问题中的语言。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,该代码在某种程度上是宽松的,因为它不需要SEQUENCE 和 INTEGER 长度编码的最小长度编码(它应该如此)。
它还允许错误编码的 INTEGER 值,这些值不必要地用零字节进行左填充。
这些问题都不应破坏算法的安全性,但其他库可能而且应该不那么宽容。