使用OpenSSL进行ECDSA签名,使用Crypto++进行验证

4
我在我的应用程序中使用Wei Dai的Crypto++创建了一个ECDSA密钥对(secp128r1)。签名和验证的工作正常。为了最小化签名长度(恰好为32字节),我没有将消息本身添加到签名中。
然而,当我使用openssl创建签名时:
$ cat test.txt | openssl dgst -ecdsa-with-SHA1 -sign sample.key -keyform DER > act.bin

显然,OpenSSL会将消息本身放入签名中,导致签名更大(例如39字节)。如果我设置CryptoPP :: SignatureVerificationFilter :: PUT_MESSAGE ,则可以使用Crypto ++验证签名。

我能否告诉OpenSSL在签名时不要将消息放入签名中,以便生成的签名恰好为32字节?


1
这些额外的字节很可能是由于BER编码的ASN.1引起的。 - CodesInChaos
另请参见Crypto++维基上的DSAConvertSignatureFormat。这是一个相对较新的页面,在这个问题之后编写。 - jww
3个回答

10

CodesInChaos 是正确的。签名中的额外字节来自ASN.1编码,而不是正在签名的原始消息。例如,这是使用 secp128r1 椭圆曲线ECDSA密钥生成的39字节签名:

30 25 02 10 4E 32 32 90 CA D9 BD D2 5F 8B BE 3B
F2 BF E9 7F 02 11 00 A7 83 A6 68 AD 74 7E 1A 0E
8F 73 BD DF 7A E8 B5

这里的30表示接下来是一个序列,25告诉你这个序列有0x25字节长。02表示序列中的第一项是一个整数,10告诉你这个整数有0x10(16)字节长。接下来的16字节是ECDSA签名的“r”值。在第一个整数后面是字节02。这告诉你序列的第二个整数即将开始。11告诉你接下来的0x11(17)个字节组成了第二个整数,“s”值,因为第一个字节是00,所以它是11字节长。当整数的第一个字节大于等于0x80时,会插入"00",这是避免最高位是1,从而表示负整数。

经过上述步骤后,真实的签名值为:

r: 4E 32 32 90 CA D9 BD D2 5F 8B BE 3B F2 BF E9 7F
s: A7 83 A6 68 AD 74 7E 1A 0E 8F 73 BD DF 7A E8 B5

"Extra"字节是用于ASN.1格式的。


在 Crypto++ 中有没有可以执行这种转换的函数? - SandeepAggarwal
@SandeepAggarwal - 是的,您可以使用Crypto++ DSAConvertSignatureFormat 在ASN.1/DER、P1363和OpenPGP格式之间进行转换。 ASN.1/DER是一种比较流行的格式,Java、OpenSSL和其他一些软件都在使用它。 - jww

2
我能否告诉openssl在不将消息放入签名的情况下签署消息,以便生成的签名恰好为32字节? 并且
sandeep> 在cryptopp中是否有任何函数可以执行此转换?
正如@CodesInChaos所述,问题在于签名编码。另请参见OpenSSL ECDSA signatures longer than expected
正如@Sandeep在评论中建议的那样,另一种选择是让Crypto ++消耗OpenSSL签名。
这是一个Crypto ++测试程序,用于从OpenSSL和Java使用的ASN.1 / DER输出进行转换,并将其转换为Crypto ++使用的IEEE P1363。它主要来自Crypto ++维基上的椭圆曲线数字签名算法
#include "cryptlib.h"
#include "eccrypto.h"
#include "files.h"
#include "oids.h"
#include "dsa.h"
#include "sha.h"
#include "hex.h"

#include <iostream>

using namespace CryptoPP;

int main(int argc, char* argv[])
{
    // Load DER encoded public key
    FileSource pubKey("secp256k1-pub.der", true /*binary*/);
    ECDSA<ECP, SHA1>::Verifier verifier(pubKey);

    // Java or OpenSSL created signature. Is is ANS.1
    //   SEQUENCE ::= { r INTEGER, s INTEGER }.
    const byte derSignature[] = {
        0x30, 0x44, 0x02, 0x20, 0x08, 0x66, 0xc8, 0xf1,
        0x6f, 0x15, 0x00, 0x40, 0x8a, 0xe2, 0x1b, 0x40,
        0x56, 0x28, 0x9c, 0x17, 0x8b, 0xca, 0x64, 0x99,
        0x37, 0xdc, 0x35, 0xad, 0xad, 0x60, 0x18, 0x4d,
        0x63, 0xcf, 0x4a, 0x06, 0x02, 0x20, 0x78, 0x4c,
        0xb7, 0x0b, 0xa3, 0xff, 0x4f, 0xce, 0xd3, 0x01,
        0x27, 0x5c, 0x6c, 0xed, 0x06, 0xf0, 0xd7, 0x63,
        0x6d, 0xc6, 0xbe, 0x06, 0x59, 0xe8, 0xc3, 0xa5,
        0xce, 0x8a, 0xf1, 0xde, 0x01, 0xd5
    };

    // P1363 'r || s' concatenation. The size is 32+32 due to field
    // size for r and s in secp-256. It is not 20+20 due to SHA-1.
    SecByteBlock signature(verifier.SignatureLength());
    DSAConvertSignatureFormat(signature, signature.size(), DSA_P1363,
                          derSignature, sizeof(derSignature), DSA_DER);

    // Message "Attack at dawn!"
    const byte message[] = {
        0x41, 0x74, 0x74, 0x61, 0x63, 0x6b, 0x20, 0x61,
        0x74, 0x20, 0x64, 0x61, 0x77, 0x6e, 0x21, 0x0a
    };

    // https://www.cryptopp.com/wiki/Elliptic_Curve_Digital_Signature_Algorithm
    bool result = verifier.VerifyMessage(message, sizeof(message), signature, signature.size());
    if (result)
        std::cout << "Verified message" << std::endl;
    else
        std::cout << "Failed to verify message" << std::endl;

    return 0;
}

在这里是运行测试程序的结果。
$ ./test.exe
Signature (64):
0866C8F16F1500408AE21B4056289C178BCA649937DC35ADAD60184D63CF4A06784CB70BA3FF4FCE
D301275C6CED06F0D7636DC6BE0659E8C3A5CE8AF1DE01D5
Verified message

以下是我用来复现 cat test.txt | openssl dgst -ecdsa-with-SHA1 -sign sample.key -keyform DER > test.sig 的设置。

$ cat test.txt
Attack at dawn!

$ hexdump -C test.txt
00000000  41 74 74 61 63 6b 20 61  74 20 64 61 77 6e 21 0a  |Attack at dawn!.|
00000010

# Create private key in PEM format
$ openssl ecparam -name secp256k1 -genkey -noout -out secp256k1-key.pem

$ cat secp256k1-key.pem
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIO0D5Rjmes/91Nb3dHY9dxmbM7gVfxmB2+OVuLmWMbGXoAcGBSuBBAAK
oUQDQgAEgVNEuirUNCEVdf7nLSBUgU1GXLrtIBeglIbK54s91HlWKOKjk4CkJ3/B
wGAfcYKa+DgJ2IUQSD15K1T/ghM9eQ==
-----END EC PRIVATE KEY-----

# Convert private key to ASN.1/DER format
$ openssl ec -in secp256k1-key.pem -inform PEM -out secp256k1-key.der -outform DER

$ dumpasn1 secp256k1-key.der
  0 116: SEQUENCE {
  2   1:   INTEGER 1
  5  32:   OCTET STRING
       :     ED 03 E5 18 E6 7A CF FD D4 D6 F7 74 76 3D 77 19
       :     9B 33 B8 15 7F 19 81 DB E3 95 B8 B9 96 31 B1 97
 39   7:   [0] {
 41   5:     OBJECT IDENTIFIER secp256k1 (1 3 132 0 10)
       :     }
 48  68:   [1] {
 50  66:     BIT STRING
       :       04 81 53 44 BA 2A D4 34 21 15 75 FE E7 2D 20 54
       :       81 4D 46 5C BA ED 20 17 A0 94 86 CA E7 8B 3D D4
       :       79 56 28 E2 A3 93 80 A4 27 7F C1 C0 60 1F 71 82
       :       9A F8 38 09 D8 85 10 48 3D 79 2B 54 FF 82 13 3D
       :       79
       :     }
       :   }

# Create public key from private key
$ openssl ec -in secp256k1-key.der -inform DER -pubout -out secp256k1-pub.der -outform DER

$ dumpasn1 secp256k1-pub.der
  0  86: SEQUENCE {
  2  16:   SEQUENCE {
  4   7:     OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
 13   5:     OBJECT IDENTIFIER secp256k1 (1 3 132 0 10)
       :     }
 20  66:   BIT STRING
       :     04 81 53 44 BA 2A D4 34 21 15 75 FE E7 2D 20 54
       :     81 4D 46 5C BA ED 20 17 A0 94 86 CA E7 8B 3D D4
       :     79 56 28 E2 A3 93 80 A4 27 7F C1 C0 60 1F 71 82
       :     9A F8 38 09 D8 85 10 48 3D 79 2B 54 FF 82 13 3D
       :     79
       :   }

# Sign the message using the private key
$ cat test.txt | openssl dgst -ecdsa-with-SHA1 -sign secp256k1-key.der -keyform DER > test.sig

# Dump the signature as hex
$ hexdump -C test.sig
00000000  30 44 02 20 08 66 c8 f1  6f 15 00 40 8a e2 1b 40  |0D. .f..o..@...@|
00000010  56 28 9c 17 8b ca 64 99  37 dc 35 ad ad 60 18 4d  |V(....d.7.5..`.M|
00000020  63 cf 4a 06 02 20 78 4c  b7 0b a3 ff 4f ce d3 01  |c.J.. xL....O...|
00000030  27 5c 6c ed 06 f0 d7 63  6d c6 be 06 59 e8 c3 a5  |'\l....cm...Y...|
00000040  ce 8a f1 de 01 d5                                 |......|
00000046

# Dump the signature as ASN.1/DER
$ dumpasn1 test.sig
  0  68: SEQUENCE {
  2  32:   INTEGER
       :     08 66 C8 F1 6F 15 00 40 8A E2 1B 40 56 28 9C 17
       :     8B CA 64 99 37 DC 35 AD AD 60 18 4D 63 CF 4A 06
 36  32:   INTEGER
       :     78 4C B7 0B A3 FF 4F CE D3 01 27 5C 6C ED 06 F0
       :     D7 63 6D C6 BE 06 59 E8 C3 A5 CE 8A F1 DE 01 D5
       :   }

在Crypto++中,可以使用PEM编码的密钥而不是ASN.1/DER编码的密钥。要这样做,您需要使用PEM Pack。它是社区插件,不是库的一部分。

如果您将PEM Pack添加到库中,则需要重新构建库。或者,您可以将其作为程序的一部分进行构建。


0
首先,您应该知道128位的EC提供大约64位的安全性。 其次,我不认为这是openssl添加的消息,因为5个字节不足以支持此操作。 无论如何,您可以使用head或tail来删除额外的字节。

安全性不是大问题,我使用它来激活/硬件锁定我的软件。 你指的是openssl添加的哪个“消息”? 当我创建签名时,我可以选择放置消息或不放置(http://www.cryptopp.com/wiki/SignerFilter)。如果我选择不这样做,签名会变小,但我需要连接签名和消息来验证它。 openssl似乎会放置消息,导致签名变长,并且与我的签名验证不兼容。我能告诉openssl dgst不要放置消息吗? - divB
"...你可以使用head或tail来删除多余的字节..." - 这两个字节位于流的中间。这两个字节是参数sINTEGER的ASN.1类型和长度。 - jww

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接