aardio 文档

使用 BouncyCastle 在 aardio 中实现 ECDSA(DER 签名格式)

概述

ECDSA(椭圆曲线数字签名算法) 被广泛应用于比特币和以太坊(使用 secp256k1 曲线),用于生成消息(mm)的签名(r,sr,s)。整个过程包括对消息进行哈希处理,然后使用私钥创建签名。公钥可用于验证签名。我们可以使用多种曲线(如 192 位、256 位、384 位和 521 位曲线)和多种哈希算法(如 MD5、SHA-1 和 SHA-256)。

密钥对生成

在椭圆曲线上,我们有一个基点 GG 和阶数 nn。首先生成一个私钥 dd,然后计算公钥:

Q=d⋅GQ = d \cdot G

公钥是曲线上的一个点,通过对基点 GG 进行 dd 次点加运算得到。

消息签名

对于消息 mm,我们使用私钥生成签名(r,sr,s)。首先创建一个随机 nonce 值 kk,然后计算曲线上的点:

P=k⋅GP = k \cdot G

接着计算该点的 x 坐标:

r=Pxmod  nr = P_x \mod n

这样就得到了签名的 rr 值。然后计算消息的哈希值:

e=H(m)e = H(m)

最后计算 ss 值:

s=k−1⋅(e+d⋅r)mod  ns = k^{-1} \cdot (e + d \cdot r) \mod n

签名就是(r,sr,s)。在 aardio 中可以这样实现:

import BouncyCastle;

// 初始化签名器
var signer = BouncyCastle.Security.SignerUtilities.GetSigner(signtype);

// 获取密钥对中的私钥和公钥
var privateKey = keyPair.Private;
var publicKey = keyPair.Public;

// 初始化签名器进行签名
signer.Init(true, privateKey);

// 更新要签名的消息数据
signer.BlockUpdate(raw.buffer(msg), 0, #msg);

签名验证

我们可以通过消息 mm、签名(r,sr,s)和公钥 QQ 来验证签名:

e=H(m)e = H(m)

然后计算:

w=s−1mod  nw = s^{-1} \mod n

u1=e⋅wu_1 = e \cdot w

u2=r⋅wu_2 = r \cdot w

接着计算曲线上的点:

X=u1⋅G+u2⋅QX = u_1 \cdot G + u_2 \cdot Q

然后取 XX 点的 x 坐标:

x=Xxmod  nx = X_x \mod n

如果 xx 等于 rr,则签名验证通过。aardio 实现如下:

// 初始化签名器进行验证
var signer2 = BouncyCastle.Security.SignerUtilities.GetSigner(signtype);
signer2.Init(false, publicKey);
signer2.BlockUpdate(raw.buffer(msg), 0, #msg);

// 验证签名
var rtn = signer2.VerifySignature(signer.GenerateSignature());

完整示例代码

以下是在 aardio 中使用 BouncyCastle 实现 ECDSA 的完整示例:

import crypt;
import BouncyCastle;
import console;

// 配置参数
var curvename = "secp256k1";
var signtype = "SHA1withECDSA";
var msg = "Hello";

// 支持的签名类型: SHA1withECDSA, SHA256withECDSA, SHA224withECDSA, SHA384withECDSA, SHA512withECDSA

// 获取指定曲线的参数
var ecParams = BouncyCastle.Asn1.X9.ECNamedCurveTable.GetByName(curvename);
var curveparam = BouncyCastle.Crypto.Parameters.ECDomainParameters(
    ecParams.Curve, 
    ecParams.G, 
    ecParams.N, 
    ecParams.H, 
    ecParams.GetSeed()
);

// 初始化密钥对生成器
var keygenParams = BouncyCastle.Crypto.Parameters.ECKeyGenerationParameters(
    curveparam, 
    BouncyCastle.Security.SecureRandom()
);

var generator = BouncyCastle.Crypto.Generators.ECKeyPairGenerator("ECDSA");
generator.Init(keygenParams);
var keyPair = generator.GenerateKeyPair();

// 创建签名
var signer = BouncyCastle.Security.SignerUtilities.GetSigner(signtype);
var privateKey = keyPair.Private;
var publicKey = keyPair.Public;

signer.Init(true, privateKey);
signer.BlockUpdate(raw.buffer(msg), 0, #msg);

// 验证签名
var signer2 = BouncyCastle.Security.SignerUtilities.GetSigner(signtype);
signer2.Init(false, publicKey);
signer2.BlockUpdate(raw.buffer(msg), 0, #msg);
var rtn = signer2.VerifySignature(signer.GenerateSignature());

// 输出结果
console.log("== 曲线:", curvename);
console.log("== 消息:", msg);
console.log("== 签名类型:", signtype);
console.log("== 签名 ===");
console.log("== 签名:", string.hex(signer.GenerateSignature(),""), 
            "[", crypt.encodeBin(signer.GenerateSignature()), "]");
console.log("== 验证结果:", rtn);

console.log("== 私钥 ===");
console.log("== D ==", tostring(privateKey.D));

console.log("== 公钥 ===");
console.log("== Q_x ==", publicKey.Q.XCoord);
console.log("== Q_y ==", publicKey.Q.YCoord);

console.log("曲线详情: G=", ecParams.G);
console.log("N=", ecParams.N);
console.log("H=", ecParams.H);
console.log("A=", ecParams.Curve.A);
console.log("B=", ecParams.Curve.B);
console.log("字段大小=", ecParams.Curve.FieldSize);  

console.pause();

简化版示例

以下是更简化的 ECDSA 实现示例:

import BouncyCastle;
import console;

var msg = "Hello";
var method = "SHA-1withECDSA";

// 初始化密钥对生成器
var gen = BouncyCastle.Crypto.Generators.ECKeyPairGenerator("ECDSA");
var secureRandom = BouncyCastle.Security.SecureRandom();
var keyGenParam = BouncyCastle.Crypto.KeyGenerationParameters(secureRandom, 256);
gen.Init(keyGenParam);
var keyPair = gen.GenerateKeyPair();

// 获取私钥和公钥参数
var privateKeyParam = keyPair.Private;
var publicKeyParam = keyPair.Public;

// 创建签名
var signer = BouncyCastle.Security.SignerUtilities.GetSigner(method);
signer.Init(true, keyPair.Private);
signer.BlockUpdate(raw.buffer(msg), 0, #msg);
var signature = signer.GenerateSignature();

// 验证签名
signer = BouncyCastle.Security.SignerUtilities.GetSigner(method);
signer.Init(false, keyPair.Public);
signer.BlockUpdate(raw.buffer(msg), 0, #msg);
var rtn = signer.VerifySignature(signature);

// 输出结果
console.log("消息:", msg);
console.log("=== 私钥 ===");
console.log("D=", privateKeyParam.D.ToString());
console.log("方法=", method);

console.log("=== 公钥 ===");
console.log("Gy=", publicKeyParam.Parameters.G.YCoord.ToString());
console.log("Gx=", publicKeyParam.Parameters.G.XCoord.ToString());
console.log("曲线=", publicKeyParam.Parameters.Curve.ToString());
console.log("阶数=", publicKeyParam.Parameters.N.ToString());
console.log("Qx=", publicKeyParam.Q.XCoord.ToString());
console.log("Qy=", publicKeyParam.Q.YCoord.ToString());

console.log("=== 签名 ===");
console.log("签名: r=", string.left(string.hex(signature), 64));
console.log("验证结果=", rtn);


console.pause();

注意事项

  1. 在实际应用中,应使用更安全的哈希算法(如 SHA-256 或 SHA-512)而不是 SHA-1
  2. 随机数 kk 的生成必须安全且不可预测,重复使用相同的 kk 值会导致私钥泄露
  3. 对于比特币和以太坊应用,应使用 "secp256k1" 曲线
  4. DER 格式的签名可以方便地在不同系统间交换和验证

通过 BouncyCastle 扩展库,aardio 可以方便地实现各种密码学操作,包括 ECDSA 签名和验证。

参考链接: 1 2

Markdown 格式