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);
};

posted on 2020-06-11 11:09  360~倒立扣篮  阅读(167)  评论(0编辑  收藏  举报

导航