ECDSA算法签名与验签的程序实现

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

ECDSA(Elliptic Curve Digital Signature Algorithm)是一种基于椭圆曲线密码学的数字签名算法。它利用椭圆曲线上的点构成的阿贝尔群的性质,以及椭圆曲线离散对数问题的计算难度来确保签名的安全性。ECDSA的密钥和签名相比RSA等传统算法更短,但提供相同甚至更高的安全性。例如,256位的ECDSA签名具有与3072位的RSA签名相当的安全强度 。

本章重点在ECSDA算法的签名和验签的代码实现,不做算法的详细描述


提示:以下是本篇文章正文内容,下面案例可供参考

一、ECDSA的签名验证过程?

ECDSA的密钥生成包括私钥和公钥两部分。私钥是一个在一定范围内随机选择的整数,而公钥则是通过将私钥与椭圆曲线上的一个预定义的基点相乘得到的点 。

签名过程包括以下几个步骤:

  1. 对消息进行哈希处理,得到哈希值。
  2. 选择一个安全的随机数,计算其与基点的乘积,得到一个随机点。
  3. 使用随机数、消息哈希值和私钥计算签名的第二部分。
  4. 将随机点的x坐标和计算出的第二部分作为签名输出 。

验签需要以下数据:

  1. 对消息进行哈希处理,得到哈希值。
  2. 获取发送端提供的密钥对(X, Y)。
  3. 获取发送端提供的签名值[R, S]。

基本概念:

  1. 签名:发送者通过密钥生成公钥,并使用公钥对数据生成签名值
  2. 验签:接收者使用发送者提供的公钥,签名值对数据进行验证

一个基本的,常被忽略的事实是,签名和验签,处理的可以是原文而不是密文。也就是说,标准的签名和验签过程中,发送方和接受方都看到的是信息明文!

二、程序实现步骤 (需引用Mbedtls库实现)

1. 签名过程

代码如下:

//提供签名数据,生成公钥对(X,Y), 生成签名[R, S] 
void EcdSa_Sign_Test(uint8_t* hash, size_t hlen,
    				uint8_t* key_x, uint8_t* key_y,
    				uint8_t* sign_r, uint8_t* sign_s)
{
    int ret = 1;

    mbedtls_ecdsa_context ctx_sign;
    mbedtls_entropy_context entropy;
    mbedtls_ctr_drbg_context ctr_drbg;
    mbedtls_mpi r, s;

    unsigned char public_key_buffer[65];

    size_t public_key_size;
    size_t rlen, slen;

    mbedtls_ecdsa_init(&ctx_sign);
    mbedtls_ctr_drbg_init(&ctr_drbg);

    mbedtls_mpi_init(&r);
    mbedtls_mpi_init(&s);
    /*
     * Generate a key pair for signing
     */
    mbedtls_printf("\n  . Seeding the random number generator...");
    fflush(stdout);

    mbedtls_entropy_init(&entropy);
    if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
        NULL, 0)) != 0)
    {
        mbedtls_printf(" failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret);
        goto exit;
    }

    mbedtls_printf(" ok\n  . Generating key pair...");
    fflush(stdout);

    if ((ret = mbedtls_ecdsa_genkey(&ctx_sign, MBEDTLS_ECP_DP_SECP256R1,
        mbedtls_ctr_drbg_random, &ctr_drbg)) != 0)
    {
        mbedtls_printf(" failed\n  ! mbedtls_ecdsa_genkey returned %d\n", ret);
        goto exit;
    }

    mbedtls_printf(" ok (key size: %d bits)\n", (int)ctx_sign.grp.pbits);

    dump_pubkey("  + Public key: ", &ctx_sign);

    dunp_Getpubkey(&ctx_sign, public_key_buffer, &public_key_size);
    split_ecdsa_key(public_key_buffer, key_x, key_y);

    /*
     * Sign message hash
     */
    mbedtls_printf("  . Signing message hash...");
    fflush(stdout);

    if ((ret = mbedtls_ecdsa_sign(&ctx_sign.grp, &r, &s, &ctx_sign.d,
        hash, hlen,
        mbedtls_ctr_drbg_random, &ctr_drbg)) != 0)
    {
        mbedtls_printf(" failed\n  ! mbedtls_ecdsa_sign returned %d\n", ret);
        goto exit;
    }

    rlen = mbedtls_mpi_size(&r);
    slen = mbedtls_mpi_size(&s);
    mbedtls_mpi_write_binary(&r, sign_r, rlen);
    mbedtls_mpi_write_binary(&s, sign_s, slen);

    mbedtls_printf(" ok \n");

exit:
    mbedtls_ecdsa_free(&ctx_sign);
    mbedtls_ctr_drbg_free(&ctr_drbg);
    mbedtls_entropy_free(&entropy);
}

2. 公钥拆分和合并

代码如下:

//将公钥对(X , Y),合并成65字节的公钥
void convert_ecdsa_key(uint8_t* public_key, uint8_t* key_x, uint8_t* key_y) {
    public_key[0] = 0x04; // Prepend 0x04 to indicate compressed format
    memcpy(public_key + 1, key_x, 32);
    memcpy(public_key + 33, key_y, 32);

}

//将65字节的公钥,拆分为公钥对(X, Y)
void split_ecdsa_key(uint8_t* public_key, uint8_t* key_x, uint8_t* key_y) {
    // 复制前32字节到X
    memcpy(key_x, public_key + 1, 32);
    // 复制剩余字节到Y
    memcpy(key_y, public_key + 33, 32);
}

3. 验签过程

代码如下:

//提供Hash数据,公钥对(X, Y), 签名值[R, S]
void Ecdsa_SignVerify_Test(uint8_t* hash, size_t hlen,
                        uint8_t* key_x, uint8_t* key_y,
                        uint8_t* sign_r, uint8_t* sign_s)
{
    int ret = 1;

    mbedtls_ecdsa_context  ctx_verify;
    mbedtls_mpi r, s;

    size_t public_key_size;

    unsigned char public_key_buffer[65]; // 公钥的最大大小,65字节(包括结束字节)

    mbedtls_mpi_init(&r);
    mbedtls_mpi_init(&s);

    convert_ecdsa_key(public_key_buffer, key_x, key_y);

    mbedtls_mpi_read_binary(&r, sign_r, 32);
    mbedtls_mpi_read_binary(&s, sign_s, 32);

    /*
     * Transfer public information to verifying context
     *
     * We could use the same context for verification and signatures, but we
     * chose to use a new one in order to make it clear that the verifying
     * context only needs the public key (Q), and not the private key (d).
     */
    mbedtls_printf("  . Preparing verification context...");
    fflush(stdout);

    mbedtls_ecdsa_init(&ctx_verify);
    mbedtls_ecp_group_load(&ctx_verify.grp, MBEDTLS_ECP_DP_SECP256R1);

    public_key_size = sizeof(public_key_buffer);

    // 给ECDSA上下文写入公钥
    if ((ret = mbedtls_ecp_point_read_binary(&ctx_verify.grp, &ctx_verify.Q, public_key_buffer, public_key_size)) != 0)
    {
        mbedtls_printf(" failed\n  ! mbedtls_ecp_point_read_binary returned %d\n", ret);
        goto exit;
    }

    dump_pubkey(" \n + Verifying Public key: ", &ctx_verify);

    /*
     * Verify signature
     */
    mbedtls_printf(" ok\n  . Verifying signature...");
    fflush(stdout);

    if ((ret = mbedtls_ecdsa_verify(&ctx_verify.grp,
        hash, hlen, &ctx_verify.Q, &r, &s)) != 0)
    {
        mbedtls_printf(" failed\n  ! mbedtls_ecdsa_verify returned %d\n", ret);
        goto exit;
    }
    mbedtls_printf(" ok\n");

exit:

#if defined(_WIN32)
    mbedtls_printf("  + Press Enter to exit this program.\n");
    fflush(stdout); getchar();
#endif
    mbedtls_ecdsa_free(&ctx_verify);
}

4. 主函数实现

代码如下:

#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif

#if defined(MBEDTLS_PLATFORM_C)
#include "mbedtls/platform.h"
#else
#include <stdio.h>
#include <stdlib.h>
#define mbedtls_printf          printf
#define mbedtls_exit            exit
#define MBEDTLS_EXIT_SUCCESS    EXIT_SUCCESS
#define MBEDTLS_EXIT_FAILURE    EXIT_FAILURE
#endif /* MBEDTLS_PLATFORM_C */

#if defined(MBEDTLS_ECDSA_C) && \
    defined(MBEDTLS_ENTROPY_C) && defined(MBEDTLS_CTR_DRBG_C)
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/sha256.h"

#include <string.h>
#endif

int main( int argc, char *argv[] )
{
    int ret = 0;
    unsigned char message[100] = "qwertyuiopasdfghjklzxcvbnmasdqwdcsdkcasnjkc acmkscs,c";
    unsigned char hash[32];
    unsigned char key_X[32], key_Y[32];
    unsigned char sign_R[32], sign_S[32];

    size_t hlen;

    /*
     * Compute message hash
     */
    mbedtls_printf("  . Computing message hash...");
    fflush(stdout);

    if ((ret = mbedtls_sha256_ret(message, sizeof(message), hash, 0)) != 0)
    {
        mbedtls_printf(" failed\n  ! mbedtls_sha256_ret returned %d\n", ret);
    }
    hlen = sizeof(hash);
    mbedtls_printf(" ok\n");
    dump_buf("  + Hash: ", hash, hlen);


    mbedtls_printf("  . Sign_Test Start ...\n");
    EcdSa_Sign_Test(hash, hlen, key_X, key_Y, sign_R, sign_S);
    mbedtls_printf("  . Sign_Test end ...\n");
    dump_buf("\n  + hash: ", hash, 32);
    dump_buf("\n  + key_X: ", key_X, 32);
    dump_buf("\n  + key_Y: ", key_Y, 32);
    dump_buf("\n  + sign_R: ", sign_R, 32);
    dump_buf("\n  + sign_S: ", sign_S, 32);
    
    mbedtls_printf("  \n. Sign_Verify Start ...\n");
    Ecdsa_SignVerify_Test(hash, hlen, key_X, key_Y, sign_R, sign_S);

}

总结

这里采用PC端验证的,也可直接移植到单片机中,需注意在单片机中使用验签,仅验签算法,堆大小需要4K,加上其他程序代码,堆大小至少要在6K,否则会出现验签失败,返回值为-16,该程序在单片机中测试需要花费800ms左右

好的,我会分别回答您的两个问题。 首先是 JAVA 实现 ECDSA。 JAVA 中的 ECDSA可以通过使用 Bouncy Castle 加密库来实现。下面是一个简单的示例代码: ```java import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Hex; import java.security.*; import java.security.spec.ECGenParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; public class ECDSASample { public static void main(String[] args) throws Exception { Security.addProvider(new BouncyCastleProvider()); // 生成公私钥对 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC"); ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec("secp256k1"); keyPairGenerator.initialize(ecGenParameterSpec); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 加 String message = "hello world"; Signature signature = Signature.getInstance("SHA256withECDSA", "BC"); signature.initSign(keyPair.getPrivate()); signature.update(message.getBytes()); byte[] sigBytes = signature.sign(); System.out.println("Signature: " + Hex.toHexString(sigBytes)); } } ``` 在上面的代码中,我们首先使用 Bouncy Castle 加密库来实现 ECDSA。然后使用 KeyPairGenerator 生成公私钥对,并且使用 SHA256withECDSA 算法进行加。最后,我们使用 Hex.toHexString 将字节数组转换为十六进制字符串。 接下来是 C 语言实现 ECDSA 验签。 C 语言中的 ECDSA 验签可以通过使用 OpenSSL 库来实现。下面是一个简单的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <openssl/ec.h> #include <openssl/ecdsa.h> #include <openssl/evp.h> #include <openssl/obj_mac.h> int main() { // 生成公私钥对 EC_KEY *key = EC_KEY_new_by_curve_name(NID_secp256k1); EC_KEY_generate_key(key); const BIGNUM *priv_key = EC_KEY_get0_private_key(key); const EC_POINT *pub_key = EC_KEY_get0_public_key(key); // 加 char message[] = "hello world"; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); EVP_DigestInit(ctx, EVP_sha256()); EVP_DigestUpdate(ctx, message, strlen(message)); unsigned int sig_len = ECDSA_size(key); unsigned char *sig = (unsigned char *) malloc(sig_len); ECDSA_sign(0, (const unsigned char *) EVP_MD_CTX_md(ctx), EVP_MD_CTX_size(ctx), sig, &sig_len, (EC_KEY *) priv_key); // 验签 int result = ECDSA_verify(0, (const unsigned char *) EVP_MD_CTX_md(ctx), EVP_MD_CTX_size(ctx), sig, sig_len, (EC_KEY *) pub_key); printf("Verify result: %d\n", result); // 释放资源 free(sig); EVP_MD_CTX_free(ctx); EC_KEY_free(key); return 0; } ``` 在上面的代码中,我们首先使用 OpenSSL 库来实现 ECDSA。然后使用 EC_KEY_new_by_curve_name 函数生成公私钥对,并且使用 EVP_sha256 算法进行加验签。最后,我们使用 ECDSA_verify 函数对签名进行验签,并且输出验签结果。 希望我的回答能够帮到您,如有任何疑问,请随时提出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值