当前位置: 首页 > news >正文

MySQL SM4 UDF 安装与使用

MySQL SM4 UDF 安装与使用文档

本文档详细介绍了将SM4加解密添加到MySQL插件中,可以在MySQL命令直接进行加解密。


一、功能概述

本 UDF 提供以下四个函数,用于在 MySQL 中进行 SM4 加解密操作:
注意:如果返回文字不是字符串就使用cast

函数名功能说明
cast(sm4_ecb_encrypt(msg, key) as char)使用 SM4-ECB 模式加密明文,返回 Base64 编码的密文
sm4_ecb_decrypt(b64_ciphertext, key)解密 Base64 编码的 SM4-ECB 密文,返回明文
cast(sm4_sic_encrypt(msg, key, iv) as char)使用 SM4-SIC(CTR)模式加密明文,返回 Base64 编码的密文
sm4_sic_decrypt(b64_ciphertext, key, iv)解密 Base64 编码的 SM4-SIC 密文,返回明文

特点

  • 支持标准 SM4 算法(符合 GM/T 0002-2012)
  • ECB 模式使用 PKCS#7 填充
  • SIC 模式为流式加密,无需填充
  • 所有输出均经 Base64 编码便于存储和传输
  • 支持 128 位密钥(16 字节)

二、环境要求

  • 操作系统:Linux(如 CentOS、Ubuntu 等)
  • MySQL 版本:5.7 或 8.0(需支持 UDF)
  • 开发工具
    • gcc 编译器
    • make 工具
    • mysql-devellibmysqlclient-dev
  • 权限:具备 root 或 sudo 权限以安装插件

三、编译与安装

1. 准备源码文件

将以下代码保存为 sm4_udf.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <mysql.h>
#include <ctype.h>/* ===== SM4 constants ===== */
static const uint8_t SM4_SBOX[256] = {0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c,0x05,0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86,0x06,0x99,0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed,0xcf,0xac,0x62,0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa,0x75,0x8f,0x3f,0xa6,0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c,0x19,0xe6,0x85,0x4f,0xa8,0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb,0x0f,0x4b,0x70,0x56,0x9d,0x35,0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25,0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52,0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38,0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,0xe0,0xae,0x5d,0xa4,0x9b,0x34,0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,0x1d,0xf6,0xe2,0x2e,0x82,0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,0xd5,0xdb,0x37,0x45,0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,0x8d,0x1b,0xaf,0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,0x0a,0xc1,0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,0x89,0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84,0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39,0x48
};static const uint32_t FK[4] = {0xa3b1bac6,0x56aa3350,0x677d9197,0xb27022dc};
static const uint32_t CK[32] = {
0x00070e15,0x1c232a31,0x383f464d,0x545b6269,0x70777e85,0x8c939aa1,0xa8afb6bd,0xc4cbd2d9,
0xe0e7eef5,0xfc030a11,0x181f262d,0x343b4249,0x50575e65,0x6c737a81,0x888f969d,0xa4abb2b9,
0xc0c7ced5,0xdce3eaf1,0xf8ff060d,0x141b2229,0x30373e45,0x4c535a61,0x686f767d,0x848b9299,
0xa0a7aeb5,0xbcc3cad1,0xd8dfe6ed,0xf4fb0209,0x10171e25,0x2c333a41,0x484f565d,0x646b7279
};/* ===== helpers ===== */
static uint32_t rotl(uint32_t x,int n){return (x<<n)|(x>>(32-n));}
static uint32_t bytes_to_uint32(const uint8_t *b){return ((uint32_t)b[0]<<24)|((uint32_t)b[1]<<16)|((uint32_t)b[2]<<8)|(uint32_t)b[3];}
static void uint32_to_bytes(uint32_t v,uint8_t *b){b[0]=(v>>24)&0xFF;b[1]=(v>>16)&0xFF;b[2]=(v>>8)&0xFF;b[3]=v&0xFF;}
static uint32_t sbox_transform(uint32_t x){uint8_t b[4];uint32_to_bytes(x,b);b[0]=SM4_SBOX[b[0]];b[1]=SM4_SBOX[b[1]];b[2]=SM4_SBOX[b[2]];b[3]=SM4_SBOX[b[3]];return bytes_to_uint32(b);}
static uint32_t linear_transform(uint32_t x){return x^rotl(x,2)^rotl(x,10)^rotl(x,18)^rotl(x,24);}
static uint32_t round_function(uint32_t x0,uint32_t x1,uint32_t x2,uint32_t x3,uint32_t rk){return x0^linear_transform(sbox_transform(x1^x2^x3^rk));}/* ===== key schedule ===== */
static void key_schedule(const uint8_t *key,uint32_t *rk){int i;uint32_t k[4];k[0]=bytes_to_uint32(key)^FK[0];k[1]=bytes_to_uint32(key+4)^FK[1];k[2]=bytes_to_uint32(key+8)^FK[2];k[3]=bytes_to_uint32(key+12)^FK[3];for(i=0;i<32;i++){uint32_t t=k[1]^k[2]^k[3]^CK[i];t=sbox_transform(t);t=t^rotl(t,13)^rotl(t,23);rk[i]=k[0]^t;k[0]=k[1];k[1]=k[2];k[2]=k[3];k[3]=rk[i];}
}/* ===== block encrypt/decrypt ===== */
static void sm4_encrypt_block(const uint8_t *in,const uint32_t *rk,uint8_t *out){int i;uint32_t x[4];x[0]=bytes_to_uint32(in);x[1]=bytes_to_uint32(in+4);x[2]=bytes_to_uint32(in+8);x[3]=bytes_to_uint32(in+12);for(i=0;i<32;i++){uint32_t tmp=round_function(x[0],x[1],x[2],x[3],rk[i]);x[0]=x[1];x[1]=x[2];x[2]=x[3];x[3]=tmp;}uint32_to_bytes(x[3],out);uint32_to_bytes(x[2],out+4);uint32_to_bytes(x[1],out+8);uint32_to_bytes(x[0],out+12);
}
static void sm4_decrypt_block(const uint8_t *in,const uint32_t *rk,uint8_t *out){uint32_t rk_rev[32];int i;for(i=0;i<32;i++) rk_rev[i]=rk[31-i];sm4_encrypt_block(in,rk_rev,out);
}/* ===== PKCS7 padding ===== */
static int pkcs7_pad(const uint8_t *in,int in_len,uint8_t *out){int pad_len=16-(in_len%16);memcpy(out,in,in_len);for(int i=0;i<pad_len;i++) out[in_len+i]=pad_len;return in_len+pad_len;
}
static int pkcs7_unpad(uint8_t *data,int len){if (len<=0) return 0;int pad=(int)data[len-1];if(pad>0 && pad<=16) return len-pad;return len;
}/* ===== SIC/CTR mode (encrypt/decrypt identical) ===== */
static void sm4_sic_crypt(const uint8_t *in,int len,const uint8_t *key,const uint8_t *iv,uint8_t *out){uint32_t rk[32];key_schedule(key,rk);uint8_t counter[16],stream[16];int i,j;memcpy(counter,iv,16);int blocks=(len+15)/16;for(i=0;i<blocks;i++){sm4_encrypt_block(counter,rk,stream);int block_size=(i==blocks-1)?(len-i*16):16;for(j=0;j<block_size;j++) out[i*16+j]=in[i*16+j]^stream[j];for(j=15;j>=0;j--){ if(++counter[j]) break; }}
}/* ===== ECB mode wrapper (with PKCS7 padding) ===== */
static void sm4_ecb_encrypt_impl(const uint8_t *in,int len,const uint8_t *key,uint8_t *out){uint32_t rk[32];key_schedule(key,rk);uint8_t block[512]; /* large buffer to be safe for UDF use */int padded_len = pkcs7_pad(in,len,block);for(int i=0;i<padded_len;i+=16) sm4_encrypt_block(block+i,rk,out+i);
}
static void sm4_ecb_decrypt_impl(const uint8_t *in,int len,const uint8_t *key,uint8_t *out){uint32_t rk[32];key_schedule(key,rk);for(int i=0;i<len;i+=16) sm4_decrypt_block(in+i,rk,out+i);int new_len=pkcs7_unpad(out,len);if(new_len>=0) out[new_len]='\0';
}/* ===== base64 ===== */
static const char base64_table[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static char* base64_encode(const uint8_t *in,int len){int out_len = ((len + 2) / 3) * 4;char *out = (char*)malloc(out_len + 1);if(!out) return NULL;int i,j=0;for(i=0;i<len;i+=3){int val=0,count = (len - i >= 3) ? 3 : (len - i),k;for(k=0;k<count;k++) val=(val<<8)|in[i+k];val <<= (3-count)*8;out[j++]=base64_table[(val>>18)&0x3F];out[j++]=base64_table[(val>>12)&0x3F];out[j++]= (count>1) ? base64_table[(val>>6)&0x3F] : '=';out[j++]= (count>2) ? base64_table[val&0x3F] : '=';}out[j]='\0'; return out;
}static uint8_t decode_table[256];
static void build_decode_table(){static int inited=0;if(inited) return; inited=1;memset(decode_table,0x80,sizeof(decode_table));for(int i=0;i<64;i++) decode_table[(unsigned char)base64_table[i]] = i;decode_table[(unsigned char)'='] = 0;
}
static uint8_t* base64_decode(const char *in,int *out_len){build_decode_table();int len = (int)strlen(in);uint8_t *out = (uint8_t*)malloc(len*3/4 + 1);if(!out) return NULL;int i,j=0;for(i=0;i<len;i+=4){uint32_t a = decode_table[(unsigned char)in[i]];uint32_t b = decode_table[(unsigned char)in[i+1]];uint32_t c = (i+2 < len) ? decode_table[(unsigned char)in[i+2]] : 0;uint32_t d = (i+3 < len) ? decode_table[(unsigned char)in[i+3]] : 0;uint32_t val = (a<<18) | (b<<12) | (c<<6) | d;out[j++] = (val>>16)&0xFF;if(in[i+2] != '=') out[j++] = (val>>8)&0xFF;if(in[i+3] != '=') out[j++] = val&0xFF;}*out_len = j;return out;
}/* safe strdup */
static char* safe_strdup(const char *s){if(!s) return NULL;size_t l = strlen(s)+1;char *p = (char*)malloc(l);if(p) memcpy(p,s,l);return p;
}/* ===== UDF functions =====*    Names must be e.g. sm4_ecb_encrypt_init / sm4_ecb_encrypt etc.*    *//* sm4_ecb_encrypt_init */
bool sm4_ecb_encrypt_init(UDF_INIT *initid, UDF_ARGS *args, char *message){if(args->arg_count != 2){ strcpy(message,"expected 2 arguments"); return true; }args->arg_type[0] = STRING_RESULT;args->arg_type[1] = STRING_RESULT;initid->maybe_null = 0;return false;
}
char* sm4_ecb_encrypt(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length,char *is_null, char *error){(void)initid; (void)is_null; (void)error;const char *msg = (const char*)args->args[0];const char *key = (const char*)args->args[1];if(!msg || !key) return NULL;int mlen = (int)strlen(msg);int blocks = ((mlen + 15) / 16) * 16;uint8_t *cipher = (uint8_t*)malloc(blocks);if(!cipher) return NULL;sm4_ecb_encrypt_impl((const uint8_t*)msg, mlen, (const uint8_t*)key, cipher);char *b64 = base64_encode(cipher, blocks);free(cipher);if(!b64) return NULL;*length = strlen(b64);return b64; /* caller (MySQL) will not free; typical pattern returns malloc'ed ptr */
}/* sm4_ecb_decrypt_init */
bool sm4_ecb_decrypt_init(UDF_INIT *initid, UDF_ARGS *args, char *message){if(args->arg_count != 2){ strcpy(message,"expected 2 arguments"); return true; }args->arg_type[0] = STRING_RESULT;args->arg_type[1] = STRING_RESULT;initid->maybe_null = 0;return false;
}
char* sm4_ecb_decrypt(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length,char *is_null, char *error){(void)initid; (void)is_null; (void)error;const char *b64 = (const char*)args->args[0];const char *key = (const char*)args->args[1];if(!b64 || !key) return NULL;int clen = 0;uint8_t *cipher = base64_decode(b64, &clen);if(!cipher) return NULL;/* clen must be multiple of 16 */uint8_t *plain = (uint8_t*)malloc(clen + 1);if(!plain){ free(cipher); return NULL; }sm4_ecb_decrypt_impl(cipher, clen, (const uint8_t*)key, plain);int new_len = pkcs7_unpad(plain, clen);if(new_len < 0) new_len = 0;plain[new_len] = '\0';free(cipher);char *ret = safe_strdup((char*)plain);free(plain);if(!ret) return NULL;*length = strlen(ret);return ret;
}/* sm4_sic_encrypt_init */
bool sm4_sic_encrypt_init(UDF_INIT *initid, UDF_ARGS *args, char *message){if(args->arg_count != 3){ strcpy(message,"expected 3 arguments"); return true; }args->arg_type[0] = STRING_RESULT;args->arg_type[1] = STRING_RESULT;args->arg_type[2] = STRING_RESULT;initid->maybe_null = 0;return false;
}
char* sm4_sic_encrypt(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length,char *is_null, char *error){(void)initid; (void)is_null; (void)error;const char *msg = (const char*)args->args[0];const char *key = (const char*)args->args[1];const char *iv  = (const char*)args->args[2];if(!msg || !key || !iv) return NULL;int mlen = (int)strlen(msg);uint8_t *cipher = (uint8_t*)malloc(mlen);if(!cipher) return NULL;sm4_sic_crypt((const uint8_t*)msg, mlen, (const uint8_t*)key, (const uint8_t*)iv, cipher);char *b64 = base64_encode(cipher, mlen);free(cipher);if(!b64) return NULL;*length = strlen(b64);return b64;
}/* sm4_sic_decrypt_init */
bool sm4_sic_decrypt_init(UDF_INIT *initid, UDF_ARGS *args, char *message){if(args->arg_count != 3){ strcpy(message,"expected 3 arguments"); return true; }args->arg_type[0] = STRING_RESULT;args->arg_type[1] = STRING_RESULT;args->arg_type[2] = STRING_RESULT;initid->maybe_null = 0;return false;
}
char* sm4_sic_decrypt(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length,char *is_null, char *error){(void)initid; (void)is_null; (void)error;const char *b64 = (const char*)args->args[0];const char *key = (const char*)args->args[1];const char *iv  = (const char*)args->args[2];if(!b64 || !key || !iv) return NULL;int clen=0;uint8_t *cipher = base64_decode(b64,&clen);if(!cipher) return NULL;uint8_t *plain = (uint8_t*)malloc(clen + 1);if(!plain){ free(cipher); return NULL; }sm4_sic_crypt(cipher, clen, (const uint8_t*)key, (const uint8_t*)iv, plain);plain[clen] = '\0';free(cipher);char *ret = safe_strdup((char*)plain);free(plain);if(!ret) return NULL;*length = clen;return ret;
}

2. 编译共享库

执行以下命令编译生成 .so 插件文件:

gcc -std=c99 -fPIC -shared -O2 -Wall -fvisibility=default \$(mysql_config --cflags) \-o sm4_udf.so sm4_udf.c \$(mysql_config --libs)

⚠️ 注意:mysql_config 必须在 PATH 中,否则需手动指定路径。mysql_config 在MySQL的bin目录下

3. 复制插件到 MySQL 插件目录

cp sm4_udf.so $(mysql_config --plugindir)/

确认复制成功:

ls -l $(mysql_config --plugindir)/sm4_udf.so

4. 验证符号导出(可选)

检查是否包含所需的函数符号:

nm -D $(mysql_config --plugindir)/sm4_udf.so | grep sm4

应看到如下符号:

sm4_ecb_encrypt
sm4_ecb_decrypt
sm4_sic_encrypt
sm4_sic_decrypt
...

四、在 MySQL 中注册 UDF 函数

登录 MySQL(需有 SUPER 权限或 CREATE FUNCTION 权限):

mysql -u root -p

执行以下 SQL 注册函数:

-- 删除旧函数(如果存在)
DROP FUNCTION IF EXISTS sm4_ecb_encrypt;
DROP FUNCTION IF EXISTS sm4_ecb_decrypt;
DROP FUNCTION IF EXISTS sm4_sic_encrypt;
DROP FUNCTION IF EXISTS sm4_sic_decrypt;-- 创建新函数
CREATE FUNCTION sm4_ecb_encrypt RETURNS STRING SONAME 'sm4_udf.so';
CREATE FUNCTION sm4_ecb_decrypt RETURNS STRING SONAME 'sm4_udf.so';
CREATE FUNCTION sm4_sic_encrypt  RETURNS STRING SONAME 'sm4_udf.so';
CREATE FUNCTION sm4_sic_decrypt  RETURNS STRING SONAME 'sm4_udf.so';

✅ 成功后,这些函数即可在 SQL 中使用。


五、使用示例

1. SM4-ECB 模式加解密

-- 加密,加密必须使用cast转换为字符串,as不是别名,是类型,必须char。
select cast(sm4_ecb_encrypt('Hello World', '1234567890abcdef') as char) as encrypted;-- 示例输出:
-- '9iCY9HhI0qQT+geIqZ8rcw=='-- 解密
select sm4_ecb_decrypt('9iCY9HhI0qQT+geIqZ8rcw==', '1234567890abcdef') as decrypted;
-- 输出:Hello World

🔐 注意:

  • 密钥必须为 16 字节(128 位),不足或超过都会导致错误行为。
  • ECB 不推荐用于敏感数据批量加密(模式不安全)。

2. SM4-SIC(CTR)模式加解密

-- 加密(需要 IV,也必须是 16 字节)
select cast(sm4_sic_encrypt('Hello World', '1234567890abcdef', 'fedcba0987654321') as char) as encrypted;-- 示例输出:
-- 'EVpRnWZdV0qpdHY='-- 解密(必须使用相同的 key 和 iv)
select sm4_sic_decrypt('EVpRnWZdV0qpdHY=', '1234567890abcdef', 'fedcba0987654321') asdecrypted;
-- 输出:Hello World

✅ SIC/CTR 模式优势:

  • 可并行加密
  • 不需要填充
  • 更适合大数据量加密

六、参数说明

参数类型要求
msg / b64_ciphertextVARCHARTEXT明文或 Base64 密文字符串
keyCHAR(16)VARCHAR必须为 16 字节长度的密钥
ivCHAR(16)VARCHAR初始向量,SIC 模式必需,建议随机且唯一

重要提示

  • 所有密钥和 IV 都应为 精确 16 字节
  • 若使用字符串作为密钥,请确保其长度为 16 字符(如十六进制需先解码)。

七、卸载 UDF

当不再需要时,可执行:

DROP FUNCTION IF EXISTS sm4_ecb_encrypt;
DROP FUNCTION IF EXISTS sm4_ecb_decrypt;
DROP FUNCTION IF EXISTS sm4_sic_encrypt;
DROP FUNCTION IF EXISTS sm4_sic_decrypt;

然后删除 .so 文件:

sudo rm $(mysql_config --plugindir)/sm4_udf.so

结语

本 UDF 实现了轻量级、高效、标准兼容的 SM4 加解密能力,适用于需要在数据库层面进行国密加密的场景(如字段加密、日志脱敏等)。请结合业务需求合理使用加密模式,并加强密钥安全管理。

http://www.xdnf.cn/news/1450873.html

相关文章:

  • 【计算机网络(自顶向下方法 第7版)】第一章 计算机网络概述
  • 《D (R,O) Grasp:跨机械手灵巧抓取的机器人 - 物体交互统一表示》论文解读
  • 实战演练(二):结合路由与状态管理,构建一个小型博客前台
  • Java基础知识点汇总(五)
  • 修订版!Uniapp从Vue3编译到安卓环境踩坑记录
  • 新手向:AI IDE+AI 辅助编程
  • 开源视频剪辑工具推荐
  • 经典资金安全案例分享:支付系统开发的血泪教训
  • Hadoop(七)
  • 数说故事 | 2025年运动相机数据报告,深挖主流品牌运营策略及行业趋势​
  • HarmonyOS路由导航方案演进:HMRouter基于Navigation封装,使用更方便
  • 【软考架构】嵌入式系统及软件
  • Shadcn UI – 开发者首选的高性能、高定制化 React 组件库
  • Flutter之riverpod状态管理详解
  • 第1章 Jenkins概述与架构
  • ⸢ 肆 ⸥ ⤳ 默认安全:安全建设方案 ➭ b.安全资产建设
  • HTTP性能优化
  • Rust 文件操作终极实战指南:从基础读写到进阶锁控,一文搞定所有 IO 场景
  • 设计模式3 创建模式之Singleton模式
  • 大数据工程师认证推荐项目:基于Spark+Django的学生创业分析可视化系统技术价值解析
  • 基于 EasyExcel + 线程池 解决 POI 导出时的内存溢出与超时问题
  • 如何简单理解状态机、流程图和时序图
  • Docker学习记录
  • 记一次 Nuxt 3 + pnpm Monorepo 中的依赖地狱:`@unhead/vue` 引发的致命错误
  • 封边机高级设置密码解锁指南:技术解析与安全操作建议
  • k8s基础(未完待续)
  • doubletrouble: 1靶场渗透
  • ubuntu-24.04.3-live-server连接不上xhell
  • 当数据库宕机时,PostgreSQL 高可用在背后做了什么?
  • 探索 PostgreSQL 和 MySQL 之间的主要差异和相似之处,找到满足您项目需求的最佳数据库解决方案。