代码之家  ›  专栏  ›  技术社区  ›  iOS-Developer84

如何将ecdsa-der编码的签名数据转换成微软cng支持的格式?

  •  1
  • iOS-Developer84  · 技术社区  · 8 年前

    我正在准备一个微型驱动程序,以便使用 NCryptSignHash 微软cng的功能。

    当我使用智能卡中的secp521r1 ec密钥执行签名时,它会生成长度为139的签名数据作为ecc签名数据格式:

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

    示例签名数据是

    308188024201A2001E9C0151C55BCA188F201020A84180B339E61EDE61F6EAD0B277321CAB81C87DAFC2AC65D542D0D0B01C3C5E25E9209C47CFDDFD5BBCAFA0D2AF2E7FD86701024200C103E534BD1378D8B6F5652FB058F7D5045615DCD940462ED0F923073076EF581210D0DD95BF2891358F5F743DB2EC009A0608CEFAA9A40AF41718881D0A26A7F4
    

    但当我使用 MS_KEY_STORAGE_PROVIDER 它生成一个长度为132字节的符号。

    如何将符号数据大小从139减小到132?

    2 回复  |  直到 8 年前
        1
  •  2
  •   Maarten Bodewes    8 年前

    您的输入是一个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解析,获取整数,然后以ISO7816-8的形式重新编码。

    但是,您还可以看到,您可以简单地将字节复制为 R S 将始终是非负(因此无符号)和大端。所以你只需要补偿尺寸。否则,唯一困难的部分是能够解码X9.62结构中组件的长度。


    警告 C代码代替C++作为我所期望的.NET.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;
        }
    }
    

    注意,代码是允许的,因为它不需要 最低限度 序列的长度编码和整数长度编码(它应该这样做)。

    它还允许错误编码的整数值不必要地用零字节填充。

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

        2
  •  1
  •   jww avp    8 年前

    如何将符号数据大小从139减小到132?

    您有一个ASN.1编码的签名(如下所示)。它是由Java、OpenSSL和其他一些库使用的。您需要p1363格式的签名,它是 r || s ,没有ASN.1编码。p1363被crypto++和其他一些库使用。(还有另一种常见的签名格式,那就是openpgp)。

    用于连接 罗氏 ,两者 r s 必须是66字节,因为八位字节边界上的secp-521r1字段元素大小。这意味着程序是,你必须剥去外层 SEQUENCE ,然后剥去两个 INTEGER ,然后连接两个整数的值。

    你的格式化 罗氏 使用样本数据的签名将是:

    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 class .


    $ 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-Signature Syntax and Processing . 它的格式不同于ASN.1、p1363、OpenPGP、CNG和其他库。

    ASN.1到p1363的转换相当简单。您可以在 ECDSA sign with BouncyCastle and verify with Crypto++ .

    你可能会发现 Cryptographic Interoperability: Digital Signatures 对代码项目有帮助。