mariadb encryption struct
第一 mariadb encryption struct
- 首先参考堆栈
#0 encryption_scheme_encrypt (src=0x7fffdbb8c026 "", slen=16338, dst=0x307c026 "", dlen=0x7fffb77fd610, scheme=0x2dd36b0, key_version=1, i32_1=0, i32_2=7, i64=1675525)
#1 0x0000000001148f2c in fil_encrypt_buf (crypt_data=0x2dd36b0, space=0, offset=7, lsn=1675525, src_frame=0x7fffdbb8c000 "y\267\017\211", page_size=..., dst_frame=0x307c000 "y\267\017\211")
#2 0x00000000011491d8 in fil_space_encrypt (space=0x2dc9ac0, offset=7, lsn=1675525, src_frame=0x7fffdbb8c000 "y\267\017\211", dst_frame=0x307c000 "y\267\017\211")
#3 0x00000000010b5682 in buf_page_encrypt_before_write (space=0x2dc9ac0, bpage=0x7fffdb58b000, src_frame=0x7fffdbb8c000 "y\267\017\211")
#4 0x00000000010c4156 in buf_flush_write_block_low (bpage=0x7fffdb58b000, flush_type=BUF_FLUSH_LIST, sync=false)
#5 0x00000000010c49c5 in buf_flush_page....
断点是encryption_scheme_encrypt,在mysql client中执行insert触发异步IO,这里的堆栈很明显是在刷一个16k的page进入加密流程
- 下一个参考堆栈
244│
245│ int encryption_scheme_encrypt(const unsigned char* src, unsigned int slen,
246│ unsigned char* dst, unsigned int* dlen,
247│ struct st_encryption_scheme *scheme,
248│ unsigned int key_version, unsigned int i32_1,
249│ unsigned int i32_2, unsigned long long i64)
250│ {
251│ return do_crypt(src, slen, dst, dlen, scheme, key_version, i32_1,
252├> i32_2, i64, ENCRYPTION_FLAG_NOPAD | ENCRYPTION_FLAG_ENCRYPT);
253│ }
所有的加解密都会走进去do_crypt()这个函数,加密是ENCRYPTION_FLAG_ENCRYPT,反过来解密是ENCRYPTION_FLAG_DECRYPT
- 加解密算法选择
mariadb在mysys_ssl/my_crypt.cc中有定义make_aes_dispatcher()一个inline类型fun,他根据klen入参决定匹配的EVP算法类型,这里相对简单,不再阐述
第二 SSL函数注册到mariadb内部数据结构
以插件函数example_key_management为例
在plugin中声明代码
struct st_mariadb_encryption example_key_management_plugin= {
MariaDB_ENCRYPTION_INTERFACE_VERSION,
get_latest_key_version,
get_key,
ctx_size,
ctx_init,
ctx_update,
ctx_finish,
get_length
};
注册一个st_mariadb_encryption数据结构,一个插件模式的钩子,那么在mariadb中,提供了两个对应的service接口用来注册插件钩子
第一个是service_encryption.h与sql/encryption.cc中定义的 struct encryption_service_st encryption_handler,实现注册ssl的方式
[liuzhuan] 这里的命名比较拗口,st_mariadb_encryption数据结构在插件层,encryption_service_st数据结构在内核层,encryption_service_st会主动把插件层的钩子加载到自己的函数指针
struct encryption_service_st encryption_handler在代码中会做以下定义
encryption_manager= plugin_lock(NULL, plugin_int_to_ref(plugin));
st_mariadb_encryption *handle= (struct st_mariadb_encryption*) plugin->plugin->info;
/*
Copmiler on Spark doesn't like the '?' operator here as it
belives the (uint (*)...) implies the C++ call model.
*/
if (handle->crypt_ctx_size)
encryption_handler.encryption_ctx_size_func= handle->crypt_ctx_size;
else
encryption_handler.encryption_ctx_size_func=
(uint (*)(unsigned int, unsigned int))my_aes_ctx_size;
encryption_handler.encryption_ctx_init_func=
handle->crypt_ctx_init ? handle->crypt_ctx_init : ctx_init;
encryption_handler.encryption_ctx_update_func=
handle->crypt_ctx_update ? handle->crypt_ctx_update : my_aes_crypt_update;
encryption_handler.encryption_ctx_finish_func=
handle->crypt_ctx_finish ? handle->crypt_ctx_finish : my_aes_crypt_finish;
encryption_handler.encryption_encrypted_length_func=
handle->encrypted_length ? handle->encrypted_length : get_length;
encryption_handler.encryption_key_get_func=
handle->get_key;
encryption_handler.encryption_key_get_latest_version_func=
handle->get_latest_key_version; // must be the last
return 0;
}
那么基本上,不管是mysql还是mariadb在注册plugin时,都是如此这般的加载钩子到函数指针,基本一水的套路,但你不能理解mariadb就是mysql,二者看起来很像,但核里的行为差别太大了;
注册后,调用者会通过service_encryption.h中的这组宏产生调用(这里用的是else分支后的宏)
#ifdef MYSQL_DYNAMIC_PLUGIN
extern struct encryption_service_st *encryption_service;
#define encryption_key_get_latest_version(KI) encryption_service->encryption_key_get_latest_version_func(KI)
#define encryption_key_get(KI,KV,K,S) encryption_service->encryption_key_get_func((KI),(KV),(K),(S))
#define encryption_ctx_size(KI,KV) encryption_service->encryption_ctx_size_func((KI),(KV))
#define encryption_ctx_init(CTX,K,KL,IV,IVL,F,KI,KV) encryption_service->encryption_ctx_init_func((CTX),(K),(KL),(IV),(IVL),(F),(KI),(KV))
#define encryption_ctx_update(CTX,S,SL,D,DL) encryption_service->encryption_ctx_update_func((CTX),(S),(SL),(D),(DL))
#define encryption_ctx_finish(CTX,D,DL) encryption_service->encryption_ctx_finish_func((CTX),(D),(DL))
#define encryption_encrypted_length(SL,KI,KV) encryption_service->encryption_encrypted_length_func((SL),(KI),(KV))
#else
extern struct encryption_service_st encryption_handler;
#define encryption_key_get_latest_version(KI) encryption_handler.encryption_key_get_latest_version_func(KI)
#define encryption_key_get(KI,KV,K,S) encryption_handler.encryption_key_get_func((KI),(KV),(K),(S))
#define encryption_ctx_size(KI,KV) encryption_handler.encryption_ctx_size_func((KI),(KV))
#define encryption_ctx_init(CTX,K,KL,IV,IVL,F,KI,KV) encryption_handler.encryption_ctx_init_func((CTX),(K),(KL),(IV),(IVL),(F),(KI),(KV))
#define encryption_ctx_update(CTX,S,SL,D,DL) encryption_handler.encryption_ctx_update_func((CTX),(S),(SL),(D),(DL))
#define encryption_ctx_finish(CTX,D,DL) encryption_handler.encryption_ctx_finish_func((CTX),(D),(DL))
#define encryption_encrypted_length(SL,KI,KV) encryption_handler.encryption_encrypted_length_func((SL),(KI),(KV))
#endif
第二个服务接口是service_encryption_scheme.h中的encryption_scheme_encrypt与encryption_scheme_decrypt,这两组函数用于提供存储引擎层加解密page以及其他层加解密临时表,binlog等
int encryption_scheme_encrypt(const unsigned char* src, unsigned int slen,
unsigned char* dst, unsigned int* dlen,
struct st_encryption_scheme *scheme,
unsigned int key_version, unsigned int i32_1,
unsigned int i32_2, unsigned long long i64);
int encryption_scheme_decrypt(const unsigned char* src, unsigned int slen,
unsigned char* dst, unsigned int* dlen,
struct st_encryption_scheme *scheme,
unsigned int key_version, unsigned int i32_1,
unsigned int i32_2, unsigned long long i64);
第三 具体的加解密流程
以加密为开始
(gdb) f 3
#3 0x00000000009b022d in encryption_scheme_encrypt (src=0x7fffdbb8c026 "", slen=16338, dst=0x307c026 "", dlen=0x7fffb77fd610, scheme=0x2dd36b0, key_version=1, i32_1=0, i32_2=7, i64=1675525) [liuzhuan] 被其他层调用,处于服务层
(gdb) f 2
#2 0x00000000009b01b3 in do_crypt (src=0x7fffdbb8c026 "", slen=16338, dst=0x307c026 "", dlen=0x7fffb77fd610, scheme=0x2dd36b0, key_version=1, i32_1=0, i32_2=7, i64=1675525, flag=3) [liuzhuan] 加解密的分支入口函数
在do_crypt()中,scheme_get_key()会被间接调用,这是个特别特别重要的位置!
scheme_get_key(),他的作用就是在file_key_management插件中拿到key::
这个插件不是很难读懂代码,有兴趣的自己去看即可,他主要输出一个hashmap keys,这是在mariadb中很重要的一个全局变量,
他缓存了来自文件中配置的主key,这个主key用来生成内部加解密用的mk,这一点,非常的相仿mysql(oracle)的双层key机制
scheme_get_key()会在keys中使用keyid与key version匹配id,这里匹配后的key进入到一个数据结构,st_encryption_scheme_key,这个结构用来缓存内部key,他也就是外部主key生成的mk
mk的生成也依赖具体的ssl函数,输出是一个16位的buf,后面所有的数据加解密都是使用mk,而外部的主key将不再被使用
(gdb) f 1
#1 0x00000000009afa06 in encryption_crypt (src=0x7fffdbb8c026 "", slen=16338, dst=0x307c026 "", dlen=0x7fffb77fd610, key=0x7fffb77fd524 "\244`M\214\213\263\254\\\364E8\344\364$\213\341\377\177", klen=16, i
v=0x7fffb77fd510 "", ivlen=16, flags=3, key_id=1, key_version=1) [liuzhuan] 这里在mk生成时是输出mk,在加解密时是调用插件中的具体ssl相关函数
(gdb) f 0
#0 ctx_init (ctx=0x7fffb77fd220, key=0x7fffb77fd524 "\244`M\214\213\263\254\\\364E8\344\364$\213\341\377\177", klen=16, iv=0x7fffb77fd510 "", ivlen=16, flags=3, key_id=1, key_version=1) [liuzhuan] 这里就已经跑到了插件层调用相关ssl函数了
第四 问题
mariadb主key是以下格式定义:
1;xxxxxxxxxxxxxxxx
2;xxxxxxxxxxxxxxxx
3;xxxxxxxxxxxxxxxx
id 1必须存在,用来加解密table space,包括以下(4-byte space id, 4-byte page position (offset, page number, etc), and the 8-byte LSN)
同时,id 1会用来加密binlog
id 2必须存在,用来加密解密临时表,aria表类型
大于id 2的数字key用来加密解密其他业务实体表(其实只有这里才会加解密你自己的表)
并在server接口中(service_encryption_scheme.h)缓存mk的数据结构中有以下问题
#define ENCRYPTION_SCHEME_KEY_INVALID -1
#define ENCRYPTION_SCHEME_BLOCK_LENGTH 16
struct st_encryption_scheme_key {
unsigned int version;
unsigned char key[ENCRYPTION_SCHEME_BLOCK_LENGTH]; //[liuzhuan] 这里无法使用32位的key
};
struct st_encryption_scheme {
unsigned char iv[ENCRYPTION_SCHEME_BLOCK_LENGTH];
struct st_encryption_scheme_key key[3];
unsigned int keyserver_requests;
unsigned int key_id;
unsigned int type;
void (*locker)(struct st_encryption_scheme *self, int release);
};