Python gmssl.SM2签名与验证的SystemVerilog集成示例
摘要:本文提供一个复杂的、完整的SM2签名与验证的SystemVerilog集成示例。这个例子将涵盖以下更复杂的场景:
- 密钥生成:在UVM测试开始时,动态调用Python生成SM2密钥对(公钥和私钥)。
- 数据签名:使用生成的私钥对一个随机消息进行签名。
- 签名验证:使用公钥、原始消息和签名进行验证。
- 数据交互:处理更复杂的数据结构,如公钥(一个点)、私钥(一个大数)和签名(两个大数r和s)在Python和SystemVerilog之间的传递。
- 正反例测试:包含一个验证成功的正向测试和一个验证失败(使用错误数据)的反向测试。
整体架构
依然采用 UVM <-> DPI-C <-> Python
的三层架构。
步骤 1: 创建Python Oracle (sm2_oracle.py
)
这个Python脚本将提供三个核心功能:gen_keys
, sign
, verify
。我们使用argparse
来管理不同的命令行模式,这比简单的sys.argv
更健壮。
# sm2_oracle.py
import sys
import argparse
from gmssl.sm2 import sm2_crypt
# SM2标准中推荐的默认UserID
DEFAULT_USER_ID = '1234567812345678'
def gen_keys():
"""生成SM2密钥对并以hex格式打印到stdout"""
sm2_key = sm2_crypt.gen_key()
private_key = sm2_key.private_key.hex()
# 公钥格式通常是 04 || x || y
public_key = sm2_key.public_key.hex()
# 使用逗号分隔私钥和公钥
print(f"{private_key},{public_key}")
def sign(private_key_hex, data_hex):
"""使用私钥对数据进行签名"""
try:
data = bytes.fromhex(data_hex)
signer = sm2_crypt.Sm2Crypt(private_key=private_key_hex)
signature = signer.sign(data, DEFAULT_USER_ID.encode('utf-8'))
print(signature.hex())
except Exception as e:
print(f"Error in signing: {e}", file=sys.stderr)
sys.exit(1)
def verify(public_key_hex, data_hex, signature_hex):
"""使用公钥验证签名"""
try:
data = bytes.fromhex(data_hex)
signature = bytes.fromhex(signature_hex)
verifier = sm2_crypt.Sm2Crypt(public_key=public_key_hex)
if verifier.verify(signature, data, DEFAULT_USER_ID.encode('utf-8')):
print("PASS")
else:
print("FAIL")
except Exception as e:
print(f"Error in verification: {e}", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="SM2 Oracle for UVM via DPI-C")
subparsers = parser.add_subparsers(dest='command', required=True)
# gen_keys 命令
parser_gen = subparsers.add_parser('gen_keys', help='Generate SM2 key pair')
# sign 命令
parser_sign = subparsers.add_parser('sign', help='Sign data with a private key')
parser_sign.add_argument('--key', required=True, help='Private key in hex')
parser_sign.add_argument('--data', required=True, help='Data to sign in hex')
# verify 命令
parser_verify = subparsers.add_parser('verify', help='Verify a signature')
parser_verify.add_argument('--key', required=True, help='Public key in hex')
parser_verify.add_argument('--data', required=True, help='Original data in hex')
parser_verify.add_argument('--sig', required=True, help='Signature in hex')
args = parser.parse_args()
if args.command == 'gen_keys':
gen_keys()
elif args.command == 'sign':
sign(args.key, args.data)
elif args.command == 'verify':
verify(args.key, args.data, args.sig)
if __name__ == "__main__":
main()
步骤 2: 创建DPI-C桥接文件 (dpi_c_wrapper.c
)
这个C文件提供三个独立的函数,分别对应Python脚本的三个功能。
// dpi_c_wrapper.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 通用的Python调用函数
static char* call_python_script(const char* command) {
FILE *fp;
// 使用一个足够大的静态缓冲区来存储结果
static char result_buf[1024];
fp = popen(command, "r");
if (fp == NULL) {
snprintf(result_buf, sizeof(result_buf), "ERROR_POPEN");
return result_buf;
}
if (fgets(result_buf, sizeof(result_buf), fp) == NULL) {
result_buf[0] = '\0'; // 读取失败则返回空字符串
}
pclose(fp);
// 移除换行符
result_buf[strcspn(result_buf, "\n")] = 0;
return result_buf;
}
// 导出函数1: 生成密钥对
char* call_gmssl_sm2_gen_keys() {
return call_python_script("python3 sm2_oracle.py gen_keys");
}
// 导出函数2: 签名
char* call_gmssl_sm2_sign(const char* private_key_hex, const char* data_hex) {
char command[1024];
snprintf(command, sizeof(command), "python3 sm2_oracle.py sign --key %s --data %s", private_key_hex, data_hex);
return call_python_script(command);
}
// 导出函数3: 验证
char* call_gmssl_sm2_verify(const char* public_key_hex, const char* data_hex, const char* signature_hex) {
char command[1024];
snprintf(command, sizeof(command), "python3 sm2_oracle.py verify --key %s --data %s --sig %s", public_key_hex, data_hex, signature_hex);
return call_python_script(command);
}
步骤 3: 编写UVM代码 (sm2_pkg.sv
)
这个包里包含了事务、参考模型和测试用例。
// sm2_pkg.sv
package sm2_pkg;
import uvm_pkg::*;
`include "uvm_macros.svh"
// 导入DPI-C函数
import "DPI-C" function string call_gmssl_sm2_gen_keys();
import "DPI-C" function string call_gmssl_sm2_sign(input string private_key_hex, input string data_hex);
import "DPI-C" function string call_gmssl_sm2_verify(input string public_key_hex, input string data_hex, input string signature_hex);
// 事务对象
class sm2_transaction extends uvm_sequence_item;
`uvm_object_utils(sm2_transaction)
// SM2密钥长度为256位,公钥为点(x,y),签名也为(r,s)
logic [255:0] private_key;
logic [511:0] public_key; // 256-bit x, 256-bit y
rand logic [255:0] message; // 为简化,我们直接对一个256位随机数签名
logic [511:0] signature; // 256-bit r, 256-bit s
bit verification_passed;
function new(string name = "sm2_transaction");
super.new(name);
endfunction
endclass
// 参考模型
class sm2_ref_model extends uvm_component;
`uvm_component_utils(sm2_ref_model)
function new(string name = "sm2_ref_model", uvm_component parent=null);
super.new(name, parent);
endfunction
// 任务1: 生成密钥对
task gen_keys(output logic [255:0] priv_key, output logic [511:0] pub_key);
string key_pair_hex, priv_key_hex, pub_key_hex;
int comma_pos;
key_pair_hex = call_gmssl_sm2_gen_keys();
// 在SystemVerilog中解析返回的 "priv,pub" 字符串
comma_pos = key_pair_hex.find(",");
if (comma_pos == 0) begin
`uvm_fatal("REF_MODEL", "Failed to find comma in key_pair string from Python.")
return;
end
priv_key_hex = key_pair_hex.substr(0, comma_pos - 1);
// 公钥格式为 04 || x || y, gmssl返回的hex不含'04'前缀
pub_key_hex = key_pair_hex.substr(comma_pos + 1, key_pair_hex.len() - 1);
if ($sscanf(priv_key_hex, "%h", priv_key) != 1) `uvm_error("REF_MODEL", "Failed to parse private key");
// 公钥是 512 bit (x,y),gmssl返回的hex正好是128个字符
if ($sscanf(pub_key_hex, "%h", pub_key) != 1) `uvm_error("REF_MODEL", "Failed to parse public key");
endtask
// 任务2: 签名
task sign(input logic [255:0] priv_key, input logic [255:0] msg, output logic [511:0] sig);
string priv_key_s, msg_s, sig_s;
priv_key_s = $sformatf("%064h", priv_key);
msg_s = $sformatf("%064h", msg);
sig_s = call_gmssl_sm2_sign(priv_key_s, msg_s);
if ($sscanf(sig_s, "%h", sig) != 1) `uvm_error("REF_MODEL", "Failed to parse signature");
endtask
// 任务3: 验证
task verify(input logic [511:0] pub_key, input logic [255:0] msg, input logic [511:0] sig, output bit passed);
string pub_key_s, msg_s, sig_s, result_s;
// 公钥需要去掉 '04' 前缀 (如果存在),但我们的Python脚本返回的是纯x,y拼接,所以直接用
pub_key_s = $sformatf("%0128h", pub_key);
msg_s = $sformatf("%064h", msg);
sig_s = $sformatf("%0128h", sig);
result_s = call_gmssl_sm2_verify(pub_key_s, msg_s, sig_s);
passed = (result_s == "PASS");
endtask
endclass
// 测试用例
class base_test extends uvm_test;
`uvm_component_utils(base_test)
sm2_ref_model model;
sm2_transaction tr;
function new(string name = "base_test", uvm_component parent=null);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
model = sm2_ref_model::type_id::create("model", this);
tr = sm2_transaction::type_id::create("tr");
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
// --- 1. 正向测试: 签名并成功验证 ---
`uvm_info(get_type_name(), "----------- Starting Positive Test Case -----------", UVM_LOW)
// 生成密钥
model.gen_keys(tr.private_key, tr.public_key);
`uvm_info(get_type_name(), $sformatf("Generated PrivKey: %064h", tr.private_key), UVM_MEDIUM)
`uvm_info(get_type_name(), $sformatf("Generated PubKey: %0128h", tr.public_key), UVM_MEDIUM)
// 随机化消息
assert(tr.randomize(message));
`uvm_info(get_type_name(), $sformatf("Message to sign: %064h", tr.message), UVM_MEDIUM)
// 签名
model.sign(tr.private_key, tr.message, tr.signature);
`uvm_info(get_type_name(), $sformatf("Generated Sig: %0128h", tr.signature), UVM_MEDIUM)
// 验证 (模拟DUT行为)
model.verify(tr.public_key, tr.message, tr.signature, tr.verification_passed);
if (tr.verification_passed)
`uvm_info(get_type_name(), "SUCCESS: Signature verification PASSED as expected.", UVM_LOW)
else
`uvm_fatal(get_type_name(), "FAILURE: Signature verification FAILED unexpectedly.")
// --- 2. 反向测试: 使用错误的消息进行验证 ---
`uvm_info(get_type_name(), "----------- Starting Negative Test Case -----------", UVM_LOW)
logic [255:0] wrong_message = ~tr.message;
`uvm_info(get_type_name(), $sformatf("Verifying with WRONG message: %064h", wrong_message), UVM_MEDIUM)
model.verify(tr.public_key, wrong_message, tr.signature, tr.verification_passed);
if (!tr.verification_passed)
`uvm_info(get_type_name(), "SUCCESS: Signature verification FAILED as expected.", UVM_LOW)
else
`uvm_fatal(get_type_name(), "FAILURE: Signature verification PASSED unexpectedly with wrong data.")
phase.drop_objection(this);
endtask
endclass
endpackage
步骤 4: 编译和运行
创建 top.sv
和 Makefile
。
文件: top.sv
import uvm_pkg::*;
import sm2_pkg::*;
module top;
initial begin
run_test("base_test");
end
endmodule
文件: Makefile
SIM = vcs
# 编译C代码为共享库
dpi_c_wrapper.so: dpi_c_wrapper.c
gcc -shared -fPIC -o $@ $<
# 编译和仿真
run: dpi_c_wrapper.so
$(SIM) -sverilog +v2k -full64 -debug_access+all \
-LDFLAGS "-Wl,--no-as-needed" \
top.sv sm2_pkg.sv \
-l dpi_c_wrapper.so
./simv
clean:
rm -rf simv* csrc ucli.key *.log *.so DVEfiles/
如何运行:
- 将
sm2_oracle.py
,dpi_c_wrapper.c
,sm2_pkg.sv
,top.sv
, 和Makefile
放在同一个目录下。 - 在终端中执行
make run
。
预期输出
你将看到类似下面的日志,清晰地展示了正向和反向测试的流程和结果:
UVM_INFO ... [base_test] ----------- Starting Positive Test Case -----------
UVM_INFO ... [base_test] Generated PrivKey: <64-char-hex>
UVM_INFO ... [base_test] Generated PubKey: <128-char-hex>
UVM_INFO ... [base_test] Message to sign: <64-char-hex>
UVM_INFO ... [base_test] Generated Sig: <128-char-hex>
UVM_INFO ... [base_test] SUCCESS: Signature verification PASSED as expected.
UVM_INFO ... [base_test] ----------- Starting Negative Test Case -----------
UVM_INFO ... [base_test] Verifying with WRONG message: <64-char-hex>
UVM_INFO ... [base_test] SUCCESS: Signature verification FAILED as expected.
这个更复杂的示例展示了如何在UVM环境中处理非对称加密的完整生命周期,包括动态生成密钥和处理更复杂的数据格式,为您构建强大的SM2验证环境提供了一个坚实的基础。