首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何将ECDSA编码的签名数据转换为microsoft支持的格式?

如何将ECDSA编码的签名数据转换为microsoft支持的格式?
EN

Stack Overflow用户
提问于 2018-06-08 08:56:14
回答 2查看 2.6K关注 0票数 2

我正在准备使用微软公司的NCryptSignHash功能在智能卡中执行标记。

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

代码语言:javascript
复制
ECDSASignature ::= SEQUENCE {
    r   INTEGER,
    s   INTEGER
}

示例签名数据为

代码语言:javascript
复制
308188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E534BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4

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

将符号数据大小从139减少到132的步骤是什么?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-06-11 11:54:28

您的输入是X9.62签名格式,这是一个包含两个ASN.1 / DER编码签名的序列。这些整数是可变大小的,有符号的,大端点数。它们是以最小字节数编码的。这意味着编码的大小可能会有所不同。

139个字节是常见的,因为它假定rs的编码的最大大小。这些值是使用模块化算法计算的,因此它们可以包含任意数量的比特,最多可以包含与密钥大小相同的阶n位数,即521位。

132个字节是由处理智能卡签名的标准ISO/IEC 7816-8 / IEEE P1363指定的。签名包括rs的级联,其中rs编码为最小字节数,以字节为单位显示与顺序相同的值。rs是静态大小的、无符号的、大端数。

rs的字节数的计算是ceil((double) n / 8)(n + 8 - 1) / 8,其中8是字节中的位数。因此,如果椭圆曲线是521位,那么产生的大小是66个字节,因此它们一起消耗132个字节。

现在开始解码。处理这个问题的方法有多种:执行完整的ASN.1解析,获取整数,然后以ISO 7816-8格式重新编码它们是最符合逻辑的方法。

但是,您还可以看到,您可以简单地复制字节,因为rs总是非负的(因此没有签名)和大端字节。所以你只需要对尺寸进行补偿。否则,唯一困难的部分就是能够解码X9.62结构中组件的长度。

警告:用C#代替C++中的代码,就像我预期的主要.NET语言一样;当我编写答案的主体部分时,语言没有问题。

代码语言:javascript
复制
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;
    }
}

请注意,代码在某种程度上是允许的,因为它不需要序列和整数长度编码的最小长度编码(它应该这样做)。

它还允许错误编码的整数值,这是不必要的左填充零字节.

这两个问题都不应该破坏算法的安全性,但是其他库可能也应该不那么允许。

票数 6
EN

Stack Overflow用户

发布于 2018-06-11 05:26:38

将符号数据大小从139减少到132的步骤是什么?

您有一个ASN.1编码签名(如下所示)。它被Java、OpenSSL和其他一些库使用。您需要P1363格式的签名,这是r || s的级联,而不需要ASN.1编码。P1363由Crypto++和其他几个库使用。(还有另一种常见的签名格式,即OpenPGP)。

对于r || s的级联,由于八进制边界上的secp-521r1字段元素大小,rs都必须是66个字节。这意味着过程是,您必须剥离外部SEQUENCE,然后剥离两个INTEGER,然后连接两个整数的值。

使用示例数据格式化的r || s签名如下:

代码语言:javascript
复制
01 A2 00 1E ... 7F D8 67 01 || 00 C1 03 E5 ... 0A 26 A7 F4

MicrosoftASN2.0有允许您操作.Net 1编码数据的ASN.1类。见AsnEncodedData类

代码语言:javascript
复制
$ 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使用了RFC 3275,XML-签名语法和处理中详细描述的XML格式。它的格式不同于ASN.1、P1363、OpenPGP、CNG和其他库。

ASN.1到P1363的转换非常简单。您可以在用BouncyCastle标记ECDSA,用Crypto++进行验证上看到一个使用用BouncyCastle标记ECDSA,用Crypto++进行验证库的示例。

您可能会发现代码项目上的密码互操作:数字签名很有帮助。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50756907

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档