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

ECDSA sign with OpenSSL, verify with Crypto++

本文关键字:使用 Crypto++ 验证 ECDSA OpenSSL      更新时间:2023-10-16

我用戴的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字节吗?

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表示序列中的第一个项目是Integer。10告诉您第一个整数的长度为0x10字节。以下0x10(16)字节是ECDSA签名的"r"值。第一个整数后面是字节02。这告诉你序列的第二个整数即将开始。11告诉您,接下来的0x11(17)字节组成第二个整数,这是ECDSA签名的"s"值。它是11个字节,因为Integer的第一个字节是00。每当整数的第一个字节>=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

"额外"字节用于ASN.1格式。

divb>我可以告诉openssl在不将消息放入签名的情况下对消息进行签名,这样得到的签名正好是32字节吗?

sandeep>cryptopp中有任何函数可以进行这种转换吗?

正如@CodesInChaos所说,问题在于签名编码。另请参阅比预期更长的OpenSSL ECDSA签名。

正如@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包。它是一个社区插件,而不是库本身的一部分。

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

首先,您应该知道128位的EC提供了大约64位的安全性。其次,我不认为这是openssl添加的消息,因为5个字节还不够。无论如何,您可以使用head或tail来删除多余的字节。