Java BouncyCastle не всегда проверяет подпись OpenSSL ECDSA - PullRequest
0 голосов
/ 02 сентября 2018

Я подписываю текст с использованием OpenSSL (на C ++), однако моя Java-программа не всегда проверяет подписанные сообщения (проверяется только ~ 1 из 5). Интересно, что https://kjur.github.io/jsrsasign/sample/sample-ecdsa.html не проверяет ни одного из них:

Имя кривой: secp256k1 Алгоритм подписи: SHA256 с ECDSA

PrivateKey

431313701ec60d303fa7d027d5f1579eaa57f0e870b23e3a25876e61bed2caa3

ОткрытыйКлюч

035bcefc4a6ca257e394e82c20027db2af368474afb8917273713644f11a7cecb3

Ошибка

text to sign=
    pcax2727gRo8M6vf9Vjhr1JDrQ3rdPYu6xx81000pcax273z8kaV5Ugsiqz3tvWGo8Gg6sch6V4912341535867163229

signature=
    3044022061dff8e39f9324b0794ec2c58abda971898f694ca980baf3c2a4045a9048b441022054a2fb8ef3d383fd7eeb31425dba440e2fd2053778d4ab3725046385c7845cff0000

Успешный

text to sign=
    pcax2727gRo8M6vf9Vjhr1JDrQ3rdPYu6xx81000pcax273z8kaV5Ugsiqz3tvWGo8Gg6sch6V4912341535867122614

signature=
    3046022100f200d0fb9e86a16bd46ee2dd11f1840a436d0a5c6823001a516e975a44906fcf022100d062a60611fc0f21d81fa3140741c8b6e650fff33d2c48aef69a3a40d7c7b3ca

Java

private static final String SHA256WITH_ECDSA = "SHA256withECDSA";

public static boolean isValidSignature(PublicKey pub, byte[] dataToVerify, byte[] signature) {

    try {

        Signature sign = Signature.getInstance(SHA256WITH_ECDSA, BouncyCastleProvider.PROVIDER_NAME);

        sign.initVerify(pub);

        sign.update(dataToVerify);

        return sign.verify(signature);

    } catch (Exception e) {
        log.error("Error: " + e.getMessage());
    }

    return false;

}

C ++

std::vector<unsigned char> utils::crypto::sign(std::string& private_key_58, std::string& message) {

    auto priv_bytes = utils::base58::decode_base(private_key_58);

    auto digest = utils::crypto::sha256(message);

    auto key = utils::crypto::ec_new_keypair(priv_bytes);

    auto signature = ECDSA_do_sign(digest.data(), digest.size(), key);

    auto der_len = ECDSA_size(key);
    auto der = (uint8_t*) calloc(der_len, sizeof(uint8_t));
    auto der_copy = der;
    i2d_ECDSA_SIG(signature, &der_copy);

    std::vector<unsigned char> s (der, der+der_len);

    return s;

}

std::vector<unsigned char> utils::crypto::sha256(std::string& str) {

    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, str.c_str(), str.size());
    SHA256_Final(hash, &sha256);

    std::vector<unsigned char> data(hash, hash + SHA256_DIGEST_LENGTH);

    return data;

}

EC_KEY *utils::crypto::ec_new_keypair(std::vector<unsigned char>& priv_bytes) {

    EC_KEY *key = nullptr;
    BIGNUM *priv = nullptr;
    BN_CTX *ctx = nullptr;
    const EC_GROUP *group = nullptr;
    EC_POINT *pub = nullptr;

    key = EC_KEY_new_by_curve_name(NID_secp256k1);

    if (!key) {
        std::cerr << "Can't generate curve secp256k1\n";
        std::abort();
    }

    priv = BN_new();
    BN_bin2bn(priv_bytes.data(), 32, priv);
    EC_KEY_set_private_key(key, priv);

    ctx = BN_CTX_new();
    BN_CTX_start(ctx);

    group = EC_KEY_get0_group(key);
    pub = EC_POINT_new(group);
    EC_POINT_mul(group, pub, priv, NULL, NULL, ctx);
    EC_KEY_set_public_key(key, pub);

    EC_POINT_free(pub);
    BN_CTX_end(ctx);
    BN_CTX_free(ctx);
    BN_clear_free(priv);

    return key;
}

1 Ответ

0 голосов
/ 02 сентября 2018

Neardupes Длина подписи ECDSA и как указать длину подписи для java.security. Метод знака подписи (и другие ссылки там)

Кодировка DER ASN.1 имеет переменный размер для всех, кроме некоторых очень ограниченных данных, и в частности для сигнатур ECDSA (или DSA). ECDSA_size возвращает максимальную длину, возможную для данного ключа, но каждая фактическая сигнатура может быть либо этой длины, либо короче, в зависимости от двоичных представлений значений r и s в сигнатуре, что для ваших целей можно рассматривать по существу как случайные числа.

В тех случаях, когда фактическая сигнатура короче ECDSA_size, вы все равно кодируете весь буфер и передаете его в Java; обратите внимание на два байта нуля (0000 в шестнадцатеричном формате) в конце вашего «провального» примера? Декодер DER может игнорировать конечный мусор, и когда я тестирую такой случай на старых поставщиках BouncyCastle и SunEC, он на самом деле работает нормально, но у меня не получается, начиная с BouncyCastle 1.54 - за довольно явным исключением, java.security.SignatureException: error decoding signature bytes. - и SunEC, начиная с 8u121 с причиной или исключением, аналогичным java.security.SignatureException: Invalid encoding for signature.

Многие реализации в последнее время сделали ужесточение декодирования DER после некоторых успешных атак на «слабые» кодировки, включая сигнатуры secp256k1 в биткойнах - см. https://bitcoin.stackexchange.com/questions/51706/what-can-be-changed-in-signed-bitcoin-transaction и https://en.bitcoin.it/wiki/Transaction_malleability. Это упомянуто в примечаниях к выпуску Oracle Java 8u121 пункт «Добавлено больше проверок в код разбора DER», хотя я не вижу ничего похожего для Bouncy.

Поскольку secp256k1 является группой кривых Certicom / X9 'prime' (Fp), ее кофактор равен 1, а порядок очень близок к размеру основного поля, который, в свою очередь, очень близок к 256 битам, кратным 8, поэтому сигнатуры в этой группе будут DER-кодировать до максимальной длины (и работать) почти ровно в 1/4 (25%) времени; в остальное время они потерпят неудачу.

Официальное и лучшее решение - использовать обновленное значение в указателе, здесь der_copy, выводить (любой) подпрограммой i2d*, чтобы определить длину кодирования и использовать эту длину. Если по какой-то причине вы не можете обработать переменную длину, вы можете передать весь буфер, но затем обрезать его перед передачей в BouncyCastle (или SunEC), используя 2+signature[1] в качестве допустимой длины - но не при переходе на кривую большего размера чем около 480 бит; выше, что это отличается и сложнее.

...