[SKE]UVM环境下OpenSSL加密算法参考模型设计
UVM环境下OpenSSL加密算法参考模型设计
摘要:在UVM测试平台中,需要一个基于OpenSSL的Golden Model来验证硬件加密模块。OpenSSL提供了丰富的加密接口:对称加密可使用EVP接口(例如调用EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)
初始化AES上下文,并使用EVP_EncryptUpdate
/EVP_EncryptFinal_ex
执行加密 (tongsuo.readthedocs.io)),国密SM4也可以用类似方式调用EVP_sm4_cbc()
等函数 (docs.openssl.org);DES和3DES可用EVP_des_cbc()
/EVP_des_ede3_cbc()
等函数处理;RSA则提供了RSA_public_encrypt
和RSA_private_decrypt
等接口实现非对称加密和解密 (hayageek.com)。下文示例中,我们分别给出AES、SM4、DES/3DES和RSA的参考模型代码,并展示如何通过DPI将这些C++函数导入SystemVerilog环境。
一、C++参考模型代码示例
以下示例代码使用OpenSSL的EVP接口和RSA接口实现加解密,并通过DPI-C暴露给SV调用。注意实际使用时需适当处理错误检查和内存管理,这里为示例简化处理流程。
// encryption_reference.cpp
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include "svdpi.h"// 辅助函数:打印OpenSSL错误(简化实现)
void print_openssl_error(const char* msg) {fprintf(stderr, "%s: %s\n", msg, ERR_error_string(ERR_get_error(), NULL));
}// AES-CBC 加密
extern "C" int aes_encrypt(svOpenArrayHandle plaintext_hdl, int pt_len,svOpenArrayHandle key_hdl, int key_len,svOpenArrayHandle iv_hdl,svOpenArrayHandle ciphertext_hdl) {// 将svOpenArrayHandle指针转换为unsigned char*unsigned char* pt = (unsigned char*)svGetArrayPtr(plaintext_hdl);unsigned char* key = (unsigned char*)svGetArrayPtr(key_hdl);unsigned char* iv = (unsigned char*)svGetArrayPtr(iv_hdl);unsigned char* ct = (unsigned char*)svGetArrayPtr(ciphertext_hdl);EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();if (!ctx) return -1;// 使用AES-128-CBC作为示例;key_len假设为16(128位)if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {print_openssl_error("AES EncryptInit");EVP_CIPHER_CTX_free(ctx);return -1;}int outl1 = 0;if (!EVP_EncryptUpdate(ctx, ct, &outl1, pt, pt_len)) {print_openssl_error("AES EncryptUpdate");EVP_CIPHER_CTX_free(ctx);return -1;}int outl2 = 0;if (!EVP_EncryptFinal_ex(ctx, ct + outl1, &outl2)) {print_openssl_error("AES EncryptFinal");EVP_CIPHER_CTX_free(ctx);return -1;}EVP_CIPHER_CTX_free(ctx);return outl1 + outl2; // 返回密文长度
}// AES-CBC 解密
extern "C" int aes_decrypt(svOpenArrayHandle ciphertext_hdl, int ct_len,svOpenArrayHandle key_hdl, int key_len,svOpenArrayHandle iv_hdl,svOpenArrayHandle plaintext_hdl) {unsigned char* ct = (unsigned char*)svGetArrayPtr(ciphertext_hdl);unsigned char* key = (unsigned char*)svGetArrayPtr(key_hdl);unsigned char* iv = (unsigned char*)svGetArrayPtr(iv_hdl);unsigned char* pt = (unsigned char*)svGetArrayPtr(plaintext_hdl);EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();if (!ctx) return -1;if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {print_openssl_error("AES DecryptInit");EVP_CIPHER_CTX_free(ctx);return -1;}int outl1 = 0;if (!EVP_DecryptUpdate(ctx, pt, &outl1, ct, ct_len)) {print_openssl_error("AES DecryptUpdate");EVP_CIPHER_CTX_free(ctx);return -1;}int outl2 = 0;if (!EVP_DecryptFinal_ex(ctx, pt + outl1, &outl2)) {print_openssl_error("AES DecryptFinal");EVP_CIPHER_CTX_free(ctx);return -1;}EVP_CIPHER_CTX_free(ctx);return outl1 + outl2;
}// SM4-CBC 加密/解密(用mode区分,1=ECB,2=CBC)
extern "C" int sm4_crypt(svOpenArrayHandle in_hdl, int in_len,svOpenArrayHandle key_hdl,svOpenArrayHandle iv_hdl, int mode, int enc,svOpenArrayHandle out_hdl) {unsigned char* in = (unsigned char*)svGetArrayPtr(in_hdl);unsigned char* key = (unsigned char*)svGetArrayPtr(key_hdl);unsigned char* iv = (unsigned char*)svGetArrayPtr(iv_hdl);unsigned char* out = (unsigned char*)svGetArrayPtr(out_hdl);const EVP_CIPHER* cipher = NULL;if (mode == 1) {cipher = EVP_sm4_ecb();} else {cipher = EVP_sm4_cbc();}EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();if (!ctx) return -1;if (enc) {if (!EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) {print_openssl_error("SM4 EncryptInit");EVP_CIPHER_CTX_free(ctx);return -1;}int outl1 = 0;if (!EVP_EncryptUpdate(ctx, out, &outl1, in, in_len)) {print_openssl_error("SM4 EncryptUpdate");EVP_CIPHER_CTX_free(ctx);return -1;}int outl2 = 0;if (!EVP_EncryptFinal_ex(ctx, out + outl1, &outl2)) {print_openssl_error("SM4 EncryptFinal");EVP_CIPHER_CTX_free(ctx);return -1;}EVP_CIPHER_CTX_free(ctx);return outl1 + outl2;} else {if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) {print_openssl_error("SM4 DecryptInit");EVP_CIPHER_CTX_free(ctx);return -1;}int outl1 = 0;if (!EVP_DecryptUpdate(ctx, out, &outl1, in, in_len)) {print_openssl_error("SM4 DecryptUpdate");EVP_CIPHER_CTX_free(ctx);return -1;}int outl2 = 0;if (!EVP_DecryptFinal_ex(ctx, out + outl1, &outl2)) {print_openssl_error("SM4 DecryptFinal");EVP_CIPHER_CTX_free(ctx);return -1;}EVP_CIPHER_CTX_free(ctx);return outl1 + outl2;}
}// DES-EDE3 (3DES)加密
extern "C" int des3_encrypt(svOpenArrayHandle plaintext_hdl, int pt_len,svOpenArrayHandle key_hdl,svOpenArrayHandle iv_hdl, svOpenArrayHandle ciphertext_hdl) {unsigned char* pt = (unsigned char*)svGetArrayPtr(plaintext_hdl);unsigned char* key = (unsigned char*)svGetArrayPtr(key_hdl);unsigned char* iv = (unsigned char*)svGetArrayPtr(iv_hdl);unsigned char* ct = (unsigned char*)svGetArrayPtr(ciphertext_hdl);EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();if (!ctx) return -1;// 3DES_EDE3_CBC,对应EVP_des_ede3_cbc()if (!EVP_EncryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, key, iv)) {print_openssl_error("3DES EncryptInit");EVP_CIPHER_CTX_free(ctx);return -1;}int outl1 = 0;if (!EVP_EncryptUpdate(ctx, ct, &outl1, pt, pt_len)) {print_openssl_error("3DES EncryptUpdate");EVP_CIPHER_CTX_free(ctx);return -1;}int outl2 = 0;if (!EVP_EncryptFinal_ex(ctx, ct + outl1, &outl2)) {print_openssl_error("3DES EncryptFinal");EVP_CIPHER_CTX_free(ctx);return -1;}EVP_CIPHER_CTX_free(ctx);return outl1 + outl2;
}// 3DES解密(类似AES-CBC解密步骤)
extern "C" int des3_decrypt(svOpenArrayHandle ciphertext_hdl, int ct_len,svOpenArrayHandle key_hdl,svOpenArrayHandle iv_hdl, svOpenArrayHandle plaintext_hdl) {unsigned char* ct = (unsigned char*)svGetArrayPtr(ciphertext_hdl);unsigned char* key = (unsigned char*)svGetArrayPtr(key_hdl);unsigned char* iv = (unsigned char*)svGetArrayPtr(iv_hdl);unsigned char* pt = (unsigned char*)svGetArrayPtr(plaintext_hdl);EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();if (!ctx) return -1;if (!EVP_DecryptInit_ex(ctx, EVP_des_ede3_cbc(), NULL, key, iv)) {print_openssl_error("3DES DecryptInit");EVP_CIPHER_CTX_free(ctx);return -1;}int outl1 = 0;if (!EVP_DecryptUpdate(ctx, pt, &outl1, ct, ct_len)) {print_openssl_error("3DES DecryptUpdate");EVP_CIPHER_CTX_free(ctx);return -1;}int outl2 = 0;if (!EVP_DecryptFinal_ex(ctx, pt + outl1, &outl2)) {print_openssl_error("3DES DecryptFinal");EVP_CIPHER_CTX_free(ctx);return -1;}EVP_CIPHER_CTX_free(ctx);return outl1 + outl2;
}// RSA加密(使用公钥加密/私钥解密)
// 假设传入的key_buf包含PEM格式的公钥文本
extern "C" int rsa_encrypt(svOpenArrayHandle plaintext_hdl, int pt_len,svOpenArrayHandle key_hdl, svOpenArrayHandle ciphertext_hdl) {unsigned char* pt = (unsigned char*)svGetArrayPtr(plaintext_hdl);unsigned char* key_buf = (unsigned char*)svGetArrayPtr(key_hdl);unsigned char* ct = (unsigned char*)svGetArrayPtr(ciphertext_hdl);BIO* bio = BIO_new_mem_buf(key_buf, -1);if (!bio) return -1;RSA* rsa = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);BIO_free(bio);if (!rsa) {print_openssl_error("RSA create");return -1;}// 使用OAEP填充int len = RSA_public_encrypt(pt_len, pt, ct, rsa, RSA_PKCS1_OAEP_PADDING);RSA_free(rsa);if (len < 0) {print_openssl_error("RSA_public_encrypt");}return len;
}// RSA解密(使用私钥解密)
// 假设传入的key_buf包含PEM格式的私钥文本
extern "C" int rsa_decrypt(svOpenArrayHandle ciphertext_hdl, int ct_len,svOpenArrayHandle key_hdl, svOpenArrayHandle plaintext_hdl) {unsigned char* ct = (unsigned char*)svGetArrayPtr(ciphertext_hdl);unsigned char* key_buf = (unsigned char*)svGetArrayPtr(key_hdl);unsigned char* pt = (unsigned char*)svGetArrayPtr(plaintext_hdl);BIO* bio = BIO_new_mem_buf(key_buf, -1);if (!bio) return -1;RSA* rsa = PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);BIO_free(bio);if (!rsa) {print_openssl_error("RSA create");return -1;}int len = RSA_private_decrypt(ct_len, ct, pt, rsa, RSA_PKCS1_OAEP_PADDING);RSA_free(rsa);if (len < 0) {print_openssl_error("RSA_private_decrypt");}return len;
}
上面代码示例中,svOpenArrayHandle
用于接收SystemVerilog传来的动态数组(如byte []
)。可以使用svGetArrayPtr(handle)
将其转换为常规的unsigned char*
指针来操作 (dvtalk.me)。例如,对于AES加密函数,我们首先创建一个EVP_CIPHER_CTX
,调用EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)
来设置AES-128-CBC加密上下文(其中key和iv需按实际长度提供 (tongsuo.readthedocs.io));然后调用EVP_EncryptUpdate
处理输入明文,最后EVP_EncryptFinal_ex
完成加密,得到输出密文长度。SM4加解密的流程与此类似,只需替换为EVP_sm4_cbc()
等SM4算法对象 (docs.openssl.org)。对DES/3DES而言,可使用EVP_des_ede3_cbc()
等作为Cipher类型进行加解密。RSA加密示例中,我们通过BIO_new_mem_buf
将SystemVerilog传入的PEM格式公钥载入内存,然后用PEM_read_bio_RSA_PUBKEY
得到RSA*
结构,再调用RSA_public_encrypt
(参考 (hayageek.com))。解密时则读入私钥并调用RSA_private_decrypt
。
二、DPI-C 导出及UVM调用
上述C++函数编译后需作为共享库(SO/DLL)加载到仿真中,然后在SystemVerilog中用import "DPI-C"
声明对应函数并调用。编译时需要加上OpenSSL库,例如示例Makefile可参考:
LDFLAGS = -Wl,-rpath,$(OPENSSL_PATH)/lib -lssl -lcrypto
CFLAGS = -fPIC -shared -I$(QUESTA_SIM_INS_DIR)/questasim/includeall: libcrypto_models.solibcrypto_models.so: encryption_reference.cppg++ $(CFLAGS) $(LDFLAGS) -o libcrypto_models.so encryption_reference.cpp
上面使用了-fPIC -shared
将代码编译为可加载共享库,同时链接了libssl
和libcrypto
(dvtalk.me)。运行仿真时,需要在EDA工具的仿真选项(例如Modelsim/Questa的+sv_lib
)中指定加载生成的libcrypto_models.so
。
在SystemVerilog UVM测试代码中,需要类似如下方式导入C函数(以AES加密为例):
import "DPI-C" function int aes_encrypt(byte plaintext[], int pt_len,byte key[], int key_len,byte iv[], byte ciphertext[]);
上述import
语句的参数顺序和类型须与C++函数定义一致 (dvtalk.me)。调用时,SystemVerilog只需准备好byte
类型的动态数组并传入,如:
byte pt_data[] = '{8'h01, 8'h23, 8'h45, 8'h67};
byte key[] = '{8'h0f, 8'h47, ... }; // 16字节AES密钥
byte iv[16] = '{default:'h00};
byte ct[64]; int ct_len;
ct_len = aes_encrypt(pt_data, pt_data.size(), key, key.size(), iv, ct);
加密函数返回值为输出长度,ct
数组长度需足够大以容纳输出 (dvtalk.me)。其他算法的导入方式类似,如sm4_crypt(...)
、des3_encrypt(...)
、rsa_encrypt(...)
等均可通过类似的import "DPI-C"
语句调用。
通过上述流程,我们即可在UVM测试中调用OpenSSL参考模型,对AES、SM4、DES/3DES、RSA等平台进行加解密计算,作为硬件模块的Golden Reference,对比验证硬件结果的正确性 (tongsuo.readthedocs.io) (hayageek.com) (docs.openssl.org) (dvtalk.me)。
三、参考文献:
OpenSSL EVP加密文档 (tongsuo.readthedocs.io) (docs.openssl.org);RSA加密示例 (hayageek.com);SystemVerilog DPI-C使用示例 (dvtalk.me) (dvtalk.me)。
四、Learn more:
- EVP_EncryptInit
- EVP_sm4_cbc - OpenSSL Documentation
- RSA Encryption & Decryption Example with OpenSSL in C
- DPI example with AES-Openssl C-model | dvtalk
- DPI example with AES-Openssl C-model | dvtalk
- DPI example with AES-Openssl C-model | dvtalk