当前位置: 首页 > 知识库问答 >
问题:

Java的BouncyCastle并不总是验证OpenSSL ECDSA签名。

田文景
2023-03-14

我使用OpenSSL(在C中)对文本进行签名,但是我的Java程序并不总是验证签名的消息(只有大约1/5得到验证)。有趣的是,https://kjur.github.io/jsrsasign/sample/sample-ecdsa.html没有证实其中任何一个:

曲线名称:secp256k1签名算法:SHA256with ECDSA

私钥

431313701ec60d303fa7d027d5f1579eaa57f0e870b23e3a25876e61bed2caa3

密钥

035bcefc4a6ca257e394e82c20027db2af368474afb8917273713644f11a7cecb3

失败:

text to sign=
    pcax2727gRo8M6vf9Vjhr1JDrQ3rdPYu6xx81000pcax273z8kaV5Ugsiqz3tvWGo8Gg6sch6V4912341535867163229

signature=
    3044022061dff8e39f9324b0794ec2c58abda971898f694ca980baf3c2a4045a9048b441022054a2fb8ef3d383fd7eeb31425dba440e2fd2053778d4ab3725046385c7845cff0000

成功:

text to sign=
    pcax2727gRo8M6vf9Vjhr1JDrQ3rdPYu6xx81000pcax273z8kaV5Ugsiqz3tvWGo8Gg6sch6V4912341535867122614

signature=
    3046022100f200d0fb9e86a16bd46ee2dd11f1840a436d0a5c6823001a516e975a44906fcf022100d062a60611fc0f21d81fa3140741c8b6e650fff33d2c48aef69a3a40d7c7b3ca

爪哇

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个答案

祁正阳
2023-03-14

接近 ECDSA 签名长度以及如何为 java 指定签名长度.security.Signature sign 方法(以及更多链接)

ASN.1 DER 编码对于除某些非常有限的数据之外的所有数据都是可变大小的,特别是对于 ECDSA(或 DSA)签名。ECDSA_size返回给定密钥可能的最大长度,但每个实际签名可以是该长度或更短,具体取决于签名中值 r 和 s 的二进制表示形式,出于您的目的,这些值基本上可以被视为随机数。

在实际签名短于ECDSA_size的情况下,您仍然对整个缓冲区进行编码并将其传递给您的Java;请注意“失败”示例末尾的两个字节为零(0000在十六进制中)?DER解码器可以忽略尾随垃圾,当我在较旧的BouncyCastle和SunEC提供程序上测试这种情况时,它实际上可以正常工作,但从BouncyCastle 1.54开始对我来说失败了——有一个相当明确的例外,java.security.SignatureException:错误解码签名字节。-SunEC从8u121开始,原因或异常类似于java.security.SignatureException:签名编码无效

在对“松懈”编码(包括比特币中的secp256k1签名)进行一些成功的攻击之后,许多实现最近使DER解码更加严格 - 请参阅 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“素数”(Fp)曲线组,其辅助因子为1,其阶数非常接近底层字段大小,而底层字段大小又非常接近256位,这是8的倍数,因此该组中的签名将DER编码到最大长度(和工作)几乎正好是1/4(25%)的时间;其余时间它们会失败。

官方和最好的解决方案是使用指针中的更新值,这里是der_copy,由(any)i2d*例程输出,以确定编码的长度,并使用该长度。如果由于某种原因无法处理可变长度,您可以传输整个缓冲区,但在传递给BouncyCastle(或SunEC)之前将其截断,方法是使用2签名[1]作为有效长度——但如果您更改为大于约480位的曲线则不行;除此之外,它是不同的,也更复杂。

 类似资料:
  • 我应该如何更改A方法?任何想法都是好的。提前谢谢。

  • 代码在这里,使用BouncyCastle实现: 我希望能够读取此签名并使用公钥验证它。如何将其转换回BouncyCastle对象?

  • 下面是Java代码: 然后使用C代码来验证签名 我在想我需要将我的签名编码为hexa,但它并没有解决我的问题。我已经使用crypto编写了一个c版本的符号方法,并且已经验证过了。所以为什么当我使用java代码时,签名没有得到验证。谢谢

  • 问题内容: 我已经测试了一种解决方案,可以验证ECDSA签名如何从EC公共密钥字节中获取PublicKey对象?,该签名可以完美地处理给定的数据。 这是数据: 这是代码(显示 true ): 当我将签名和数据更改为来自已实施系统的示例输入时,就会出现我的问题: 新数据输出此错误: 我认为问题出在 安全消息 附带的签名上,因为: 密钥对的长度和格式与示例相同。并且是正确的,因为它来自对消息进行签名的

  • 下面是我的场景--我从条形码中读取数据,并将其转换为纯文本。本文是条码数据+数字签名的结合。数字签名附加在末尾,使我能够分离出实际数据和数字签名数据。数字签名数据使用sha256哈希-用户将公钥作为windows证书文件()发送给我。 所需实现:-需要从证书中提取公钥,并根据提供的条形码数据和数字签名验证公钥。 问题:

  • ConstraintViolationException在将无效调用放入控制器的contructor时引发: