Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ECDSA sign with BouncyCastle and verify with Crypto++

Here is the Java code:

public static String sign(String data) throws Exception {
    KeyPair keyPair = loadKeyPair(System.getProperty("user.dir"), "ECDSA");
    Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
    signature.initSign(keyPair.getPrivate(), new SecureRandom());

    byte[] message = data.getBytes();
    signature.update(message);

    byte[] sigBytes = signature.sign();        
    String signatureStr = new BigInteger(1, sigBytes).toString(16);
    return signatureStr;
}

Then the C++ Code to verify signatures

bool VerifyMessage( const ECDSA<ECP, SHA256>::PublicKey& key, const string& message, const string& signature )
{
    bool result = false;

    // Hexa encoding version, more readable
    std::string decodedSignature;
    StringSource(signature, true,
                    new HexDecoder(
                       new StringSink(decodedSignature)));

    StringSource(decodedSignature+message, true,
                    new SignatureVerificationFilter(ECDSA<ECP,SHA256>::Verifier(key),
                       new ArraySink((byte*)&result, sizeof(result))));

    return result;
}

I was thinking that I need to encode my signature to hexa but it didn't resolve my problem. I've written a c++ version of the sign method using crypto++ and it's verified. so why when I use the java code, the signature is not verified. Thanks

like image 445
zoraj Avatar asked Jan 28 '23 16:01

zoraj


2 Answers

... why when I use the java code, the signature is not verified?

OpenSSL and Java use an ASN.1/DER encoding for the signature, and Crypto++ uses IEEE P1363's format for the signature.

  • ASN.1: SEQUENCE ::= { r INTEGER, s INTEGER }
  • P1363: [byte array r][byte array s]

You need to convert between the formats. Crypto++ provides DSAConvertSignatureFormat to convert between formats. There is an example on the Crypto++ wiki at Elliptic Curve Digital Signature Algorithm | OpenSSL and Java Interop.

Here is the Crypto++ code from the wiki. It uses OpenSSL and its command line tools rather than Java. There is no material difference because OpenSSL and Java output signatures in ASN.1/DER format.

#include "cryptlib.h"
#include "eccrypto.h"
#include "files.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. It 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;
}

And here is the result of running the test program.

$ ./test.exe
Signature (64):
0866C8F16F1500408AE21B4056289C178BCA649937DC35ADAD60184D63CF4A06784CB70BA3FF4FCE
D301275C6CED06F0D7636DC6BE0659E8C3A5CE8AF1DE01D5
Verified message

Here is the setup I used to reproduce cat test.txt | openssl dgst -ecdsa-with-SHA1 -sign sample.key -keyform DER > test.sig. It is from @DivB's question at ECDSA sign with OpenSSL, verify with Crypto++.

$ 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
       :   }
like image 187
jww Avatar answered Feb 03 '23 18:02

jww


By the way, another way around your problem (especially allowing you to avoid the command line) would be to modify the Java code in order to have a way to produce the R and S values, as well as to reproduce the DER encoded values.

For example you can extract the R and S values from the Java signature using those:

public static BigInteger extractR(byte[] signature) throws Exception {
    int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
    int lengthR = signature[startR + 1];
    return new BigInteger(Arrays.copyOfRange(signature, startR + 2, startR + 2 + lengthR));
}

public static BigInteger extractS(byte[] signature) throws Exception {
    int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
    int lengthR = signature[startR + 1];
    int startS = startR + 2 + lengthR;
    int lengthS = signature[startS + 1];
    return new BigInteger(Arrays.copyOfRange(signature, startS + 2, startS + 2 + lengthS));
}

These methods are notably used in Wycheproof to play around with the BigIntegers directly.

These can allow you to reconstruct the P1363 encoding used by CryptoPP in Java, but be careful not to forget the left padding with 0s of the bytearrays. (Otherwise you may have problems when the R or S bytearray is smaller than the expected length.)

And you can also reconstruct the DER encoded signature from big integers using:

public static byte[] derSign(BigInteger r, BigInteger s) throws Exception {
    byte[] rb = r.toByteArray();
    byte[] sb = s.toByteArray();
    int off = (2 + 2) + rb.length;
    int tot = off + (2 - 2) + sb.length;
    byte[] der = new byte[tot + 2];
    der[0] = 0x30;
    der[1] = (byte) (tot & 0xff);
    der[2 + 0] = 0x02;
    der[2 + 1] = (byte) (rb.length & 0xff);
    System.arraycopy(rb, 0, der, 2 + 2, rb.length);
    der[off + 0] = 0x02;
    der[off + 1] = (byte) (sb.length & 0xff);
    System.arraycopy(sb, 0, der, off + 2, sb.length);
    return der;
}

As you can see, these methods might be translated into C++ code, since they are really basic byte manipulations, but that's another story ;)

like image 31
Lery Avatar answered Feb 03 '23 19:02

Lery