如何将ECDSA DER编码的签名数据转换为Microsoft CNG支持的格式? [英] How to convert ECDSA DER encoded signature data to microsoft CNG supported format?

查看:59
本文介绍了如何将ECDSA DER编码的签名数据转换为Microsoft CNG支持的格式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在准备一个微型驱动程序,以使用Microsoft CNG的 NCryptSignHash 功能执行智能卡登录.

当我使用智能卡中的SECP521R1 EC密钥执行签名时,它将生成长度为139的签名数据作为ECC签名数据格式:

  ECDSASignature :: = SEQUENCE {r整数整数} 

样本签名数据为

<预类= 朗 - 无prettyprint-越权"> <代码> 308188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E534BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4

但是当我使用 MS_KEY_STORAGE_PROVIDER 执行签名时,它会生成一个长度为132字节的符号.

将标牌数据大小从139减小到132的程序是什么?

解决方案

您的输入是X9.62签名格式,该格式是包含两个ASN.1/DER编码签名的SEQUENCE.这些整数是大小可变的,带符号的大端数字.它们以最小字节数进行编码.这意味着编码的大小可以变化.

这139个字节是常见的,因为它假定 r s 的最大编码大小.这些值是使用 modular 算法计算的,因此它们可以包含任意数量的位,最多为 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{私有静态整数BYTE_SIZE_BITS = 8;专用静态字节ASN1_SEQUENCE = 0x30;专用静态字节ASN1_INTEGER = 0x02;公共静态字节[]轻量级ConvertSignatureFromX9_62ToISO7816_8(int orderInBits,字节[] x9_62){int偏移= 0;如果(x9_62 [offset ++]!= ASN1_SEQUENCE){抛出新的IllegalSignatureFormatException(输入不是序列");}int sequenceSize = parseLength(x9_62,offset,out offset);int sequenceValueOffset =偏移量;int nBytes =(orderInBits + BYTE_SIZE_BITS-1)/BYTE_SIZE_BITS;byte [] iso7816_8 =新的byte [2 * nBytes];//检索并复制r如果(x9_62 [offset ++]!= ASN1_INTEGER){抛出新的IllegalSignatureFormatException(输入不是整数");}int rSize = parseLength(x9_62,offset,out offset);copyToStatic(x9_62,offset,rSize,iso7816_8,0,nBytes);偏移量+ = rSize;//-检索并复制s如果(x9_62 [offset ++]!= ASN1_INTEGER){抛出新的IllegalSignatureFormatException(输入不是整数");}int sSize = parseLength(x9_62,offset,out offset);copyToStatic(x9_62,offset,sSize,iso7816_8,nBytes,nBytes);偏移量+ = sSize;if(offset!= sequenceValueOffset + sequenceSize){抛出新的IllegalSignatureFormatException("SEQUENCE对于r和s的编码而言太小或太大");}返回iso7816_8;}/***将可变大小,有符号的大字节序号复制为数组,作为静态大小,无符号的大字节序号.*假设从iso7816_8Offset将iso7816_8缓冲区清零了nBytes.*/私有静态无效copyToStatic(byte [] sint,int sintOffset,int sintSize,byte [] iso7816_8,int iso7816_8Offset,int nBytes){//如果整数以零开头,则跳过如果(sint [sintOffset] == 0x00){sintOffset ++;sintSize--;}//跳过零字节后,整数必须适合如果(sintSize> nBytes){抛出新的IllegalSignatureFormatException("r或s的数字格式太大");}//将其复制到正确的位置Array.Copy(sint,sintOffset,iso7816_8,iso7816_8Offset + nBytes-sintSize,sintSize);}/**长度值的独立BER解码,最大2 ^ 31 -1.*/私有静态int parseLength(byte []输入,int startOffset,out int偏移量){offset = startOffset;字节l1 =输入[offset ++];//-单字节长度编码的返回值如果(l1 <0x80){返回l1;}//否则长度的第一个字节指定后面的编码字节数int end =偏移量+ l1&0x7F;uint结果= 0;//---跳过最左边的零字节(用于BER)一会儿(偏移量<结束){如果(input [offset]!= 0x00){休息;}偏移++;}//-测试最大值如果(结束-偏移> sizeof(uint)){抛出新的IllegalSignatureFormatException("TLV的长度太大");}//---解析多字节长度编码一会儿(偏移量<结束){结果=(结果<< BYTE_SIZE_BITS)^ input [offset ++];}//-确保uint不大于int可以处理的大小如果(结果> Int32.MaxValue){抛出新的IllegalSignatureFormatException("TLV的长度太大");}//-返回多字节长度编码返回(int)结果;}} 

请注意,该代码在某种程度上是允许的,因为它不需要SEQUENCE和INTEGER长度编码的最小长度编码(应该).

它还允许错误编码的INTEGER值,这些值不必要地用零字节左填充.

这两个问题都不应该破坏算法的安全性,但是其他库可能并且应该不太宽容.

I am preparing a minidriver to perform sign in smartcard using NCryptSignHash function of Microsoft CNG.

When I perform sign with an SECP521R1 EC key in smartcard it generates a sign data with length of 139 as ECC signed data format:

ECDSASignature ::= SEQUENCE {
    r   INTEGER,
    s   INTEGER
}

Sample signed data is

308188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E534BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4

But when I perform Sign using MS_KEY_STORAGE_PROVIDER it generates a sign with length of 132 byte.

What is the procedure to reduce the sign data size from 139 to 132?

解决方案

Your input is an X9.62 signature format which is a SEQUENCE containing two ASN.1 / DER encoded signatures. These integers are variable sized, signed, big endian numbers. They are encoded in the minimum number of bytes. This means that the size of the encoding can vary.

The 139 bytes is common because it assumes the maximum size of the encoding for r and s. These values are computed using modular arithmetic and they can therefore contain any number of bits, up to the number of bits of order n, which is the same as the key size, 521 bits.


The 132 bytes are specified by ISO/IEC 7816-8 / IEEE P1363 which is a standard that deals with signatures for smart cards. The signature consists of the concatenation of r and s, where r and s are encoded as the minimum number of bytes to display a value of the same size as the order, in bytes. The r and s are statically sized, unsigned, big endian numbers.

The calculation of the number of bytes of r or s is ceil((double) n / 8) or (n + 8 - 1) / 8 where 8 is the number of bits in a byte. So if the elliptic curve is 521 bits then the resulting size is 66 bytes, and together they therefore consume 132 bytes.


Now on to the decoding. There are multiple ways of handling this: perform a full ASN.1 parse, obtain the integers and then encode them back again in the ISO 7816-8 form is the most logical one.

However, you can also see that you could simply copy bytes as r and s will always be non-negative (and thus unsigned) and big endian. So you just need to compensate for the size. Otherwise the only hard part is to be able to decode the length of the components within the X9.62 structure.


Warning: code in C# instead of C++ as I expected the main .NET language; language not indicated in question when I wrote the main part of the answer.

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;
    }
}

Note that the code is somewhat permissive in the fact that it doesn't require the minimum length encoding for the SEQUENCE and INTEGER length encoding (which it should).

It also allows wrongly encoded INTEGER values that are unnecessarily left-padded with zero bytes.

Neither of these issues should break the security of the algorithm but other libraries may and should be less permissive.

这篇关于如何将ECDSA DER编码的签名数据转换为Microsoft CNG支持的格式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆