Java — ECDSA подписать с BouncyCastle и проверить с помощью Crypto ++

Вот код Java:

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;
}

Затем код C ++ для проверки подписей

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;
}

Я думал, что мне нужно закодировать мою подпись в гекса, но это не решило мою проблему. Я написал c ++ версию метода sign с использованием crypto ++, и она проверена. так почему, когда я использую код Java, подпись не проверяется. Спасибо

2

Решение

… почему при использовании java-кода подпись не проверяется?

OpenSSL и Java используют кодировку ASN.1 / DER для подписи, а Crypto ++ использует формат IEEE P1363 для подписи.

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

Вам нужно конвертировать между форматами. Crypto ++ обеспечивает DSAConvertSignatureFormat конвертировать между форматами. В вики Crypto ++ есть пример на Алгоритм цифровой подписи эллиптической кривой | OpenSSL и Java Interop.

Вот код Crypto ++ из вики. Он использует OpenSSL и его инструменты командной строки, а не Java. Материальных различий нет, потому что OpenSSL и Java выводят подписи в формате ASN.1 / DER.

#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.
byte signature[0x40];
DSAConvertSignatureFormat(signature, sizeof(signature), DSA_P1363,
derSignature, sizeof(derSignature), DSA_DER);

// Cross check
std::cout << "Signature:\n";
ArraySource(signature, sizeof(signature), true, new HexEncoder(new FileSink(std::cout)));
std::cout << std::endl;

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

// Standard signature checking in Crypto++
// https://www.cryptopp.com/wiki/Elliptic_Curve_Digital_Signature_Algorithm
bool result = verifier.VerifyMessage(message, sizeof(message), signature, sizeof(signature));
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, Это из вопроса @ DivB в ECDSA подписать с помощью OpenSSL, проверить с помощью 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
:   }
2

Другие решения

Кстати, другой способ решения вашей проблемы (особенно позволяющий вам избегать командной строки) состоит в том, чтобы модифицировать код Java, чтобы иметь возможность получать значения R и S, а также воспроизводить значения, закодированные в DER.

Например, вы можете извлечь значения R и S из подписи Java, используя их:

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));
}

Эти методы особенно используется в Wycheproof поиграть с BigIntegers напрямую.

Это может позволить вам восстановить кодировку P1363, используемую CryptoPP в Java, но будьте осторожны, чтобы не забыть левый отступ с 0 байт-массивов. (В противном случае могут возникнуть проблемы, когда байтовый массив R или S меньше ожидаемой длины.)

И вы также можете восстановить DER-кодированную подпись из больших целых чисел, используя:

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;
}

Как видите, эти методы могут быть переведены в код C ++, так как они на самом деле являются базовыми манипуляциями с байтами, но это уже другая история;)

1

По вопросам рекламы [email protected]