Feat -[Mysql 调用SM3 算法实现国密]
MySQL 本身并不直接支持 SM3 加密算法,主要用于数字签名和验证。MySQL 内置的加密函数主要支持一些常见的哈希算法(如 MD5、SHA1、SHA2 等),但不包括 SM3。
如果需要在 MySQL 中使用 SM3 算法,可以通过以下方式实现:
-
方法1:使用外部程序或脚本;
应用程序层面(如 Python、Java 等)使用 SM3 算法对数据进行加密,然后将加密后的结果存储到 MySQL 中。
灵活性高,可以使用任意变成语言实现;
避免修改Mysql服务服务器配置;
-
方法2:使用 MySQL 自定义函数(UDF),即以下使用;
性能高,直接在Mysql 中运行
适合频繁调用的场景
思路:
- 编写 C/C++ 代码实现 SM3 算法
- 使用 OpenSSL 或其他支持 SM3 的库编写 SM3 哈希计算的代码;
- 编译成共享库(如
.so
文件)
- 在 MySQL 中加载 UDF;
- 调用自定义函数;
CentOS 上的注意事项
前置条件
- 操作系统 Centos 7.x
- MySQL 8.xx
- OpenSSL 版本 1.1.1 或更高(支持 SM3 算法)
- GCC:用于编译 C/C++ 代码
检查 OpenSSL 版本
openssl version
如果版本低于 1.1.1,请升级 OpenSSL
-
CentOS 7 默认的 OpenSSL 版本可能较低(1.0.2),不支持 SM3。
[root@iZhp39hauxbv1s8uh5x0vqZ mysql]# openssl version OpenSSL 1.0.2k-fips 26 Jan 2017
-
如果需要升级 OpenSSL,可以参考以下步骤
sudo yum install epel-release sudo yum install openssl11 openssl11-devel [root@iZhp39hauxbv1s8uh5x0vqZ mysql]# openssl version OpenSSL 1.0.2k-fips 26 Jan 2017 # 以下默认使用 openssl11 version [root@iZhp39hauxbv1s8uh5x0vqZ mysql]# openssl11 version OpenSSL 1.1.1k FIPS 25 Mar 2021
然后编译时指定 OpenSSL 1.1.1 的路径:
gcc -shared -fPIC -o sm3_udf.so sm3_udf.c -I /usr/include/mysql -I /usr/include/openssl11 -L /usr/lib64/openssl11 -lssl -lcrypto
-shared :生成共享库
-fPIC :生成位置无关代码
-o sm3_udf.so :指定输出文件名
-I /usr/inclode/mysql :指定MYSQL 头文件路径
-lssl -lcrypto :链接 OpenSLL 库
MySQL 插件目录权限
- 确保 MySQL 用户对插件目录有读写权限;
安装 GCC 和 OpenSSL
sudo yum install gcc openssl openssl-devel
安装 MySQL 开发库
sudo yum install mysql-devel
编写 SM3 的 C++ 代码
注:这里用电话号码举例:
变形输出=SM3(电话号码全文UTF-8编码)(64字符,英文按小写输出)。机构客户、保险公司员工与销售人员的联系电话不做变形。
例:
传真/座机号码:010-66279455,变形后:
0702465666b4df0c2f7ddefe86f4be1c69c239765e2e8d3288128b5e1ba0be31
手机号码:12345678912,变形后:
f30dbe8891134b9528adf0b6083246d7fa65eac0c546723ba79abeb595153f94
创建 sm3_phone_udf.cpp
#include <mysql.h>
#include <openssl/evp.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h> // 包含 bool 类型的头文件
// SM3 哈希计算函数
void sm3_hash(const char *input, size_t input_len, unsigned char *output) {
EVP_MD_CTX *mdctx;
const EVP_MD *md;
unsigned int md_len;
md = EVP_sm3(); // 获取 SM3 算法
mdctx = EVP_MD_CTX_new(); // 创建上下文
EVP_DigestInit_ex(mdctx, md, NULL); // 初始化
EVP_DigestUpdate(mdctx, input, input_len); // 更新数据
EVP_DigestFinal_ex(mdctx, output, &md_len); // 计算哈希
EVP_MD_CTX_free(mdctx); // 释放上下文
}
// MySQL UDF 初始化函数
extern "C" bool sm3_phone_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT) {
strcpy(message, "sm3_phone function requires exactly one string argument.");
return true; // 返回 true 表示初始化失败
}
initid->max_length = 64; // 设置返回值的最大长度(64 字符)
initid->ptr = (char *)malloc(65); // 分配内存存储哈希值
if (initid->ptr == NULL) {
strcpy(message, "Memory allocation failed.");
return true;
}
return false; // 返回 false 表示初始化成功
}
// MySQL UDF 主函数
extern "C" char *sm3_phone(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
if (args->args[0] == NULL) {
*is_null = 1;
return NULL;
}
const char *phone = args->args[0]; // 获取输入的电话号码
size_t phone_len = args->lengths[0]; // 获取输入的长度
// 计算 SM3 哈希
unsigned char hash[EVP_MAX_MD_SIZE];
sm3_hash(phone, phone_len, hash);
// 将哈希值转换为十六进制字符串(小写)
char *hex_hash = initid->ptr;
for (int i = 0; i < EVP_MD_size(EVP_sm3()); i++) {
sprintf(hex_hash + (i * 2), "%02x", hash[i]);
}
hex_hash[64] = '\0'; // 确保字符串以 null 结尾
*length = 64; // 设置返回值的长度
return hex_hash;
}
// MySQL UDF 清理函数
extern "C" void sm3_phone_deinit(UDF_INIT *initid) {
if (initid->ptr) {
free(initid->ptr); // 释放内存
}
}
编译代码
g++ -shared -fPIC -o sm3_phone_udf.so sm3_phone_udf.cpp -I /usr/include/mysql -I /usr/include/openssl11 -L /usr/lib64/openssl11 -lssl -lcrypto
登录msyql,查看插件安装目录
mysql> SHOW VARIABLES LIKE 'plugin_dir';
+---------------+--------------------------+
| Variable_name | Value |
+---------------+--------------------------+
| plugin_dir | /usr/lib64/mysql/plugin/ |
+---------------+--------------------------+
1 row in set (0.01 sec)
Copy 至插件安装目录
sudo cp sm3_phone_udf.so /usr/lib64/mysql/plugin/
登录msyql,查看插件安装目录
mysql> DROP FUNCTION sm3_phone_udf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE FUNCTION sm3_phone RETURNS STRING SONAME 'sm3_phone_udf.so';
Query OK, 0 rows affected (0.00 sec)
-- 输出为 64 字符的小写十六进制字符串
mysql> SELECT '12345678912' AS phone, sm3('12345678912') AS sm3_hash
-> UNION ALL
-> SELECT '010-66279455' AS phone, sm3('010-66279455') AS sm3_hash;
+--------------+------------------------------------------------------------------------------------------------------------------------------------+
| phone | sm3_hash |
+--------------+------------------------------------------------------------------------------------------------------------------------------------+
| 12345678912 | 0x66333064626538383931313334623935323861646630623630383332343664376661363565616330633534363732336261373961626562353935313533663934 |
| 010-66279455 | 0x30373032343635363636623464663063326637646465666538366634626531633639633233393736356532653864333238383132386235653162613062653331 |
+--------------+------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
Test
-- 任意IP登录
mysql> use mysql;
Database changed
mysql> update user set host = '%' where user = 'root';
Query OK, 1 row affected (