zoukankan      html  css  js  c++  java
  • 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 "y26717211", page_size=..., dst_frame=0x307c000 "y26717211")
    #2  0x00000000011491d8 in fil_space_encrypt (space=0x2dc9ac0, offset=7, lsn=1675525, src_frame=0x7fffdbb8c000 "y26717211", dst_frame=0x307c000 "y26717211")
    #3  0x00000000010b5682 in buf_page_encrypt_before_write (space=0x2dc9ac0, bpage=0x7fffdb58b000, src_frame=0x7fffdbb8c000 "y26717211")
    #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`M214213263254\364E8344364$213341377177", 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`M214213263254\364E8344364$213341377177", 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);
    };
    
  • 相关阅读:
    tomcat war包自动化部署脚本
    nginx只允许域名访问,禁止ip访问
    Nginx Errors: upstream response cache error
    Linux进程的睡眠和唤醒
    传输层:UDP 协议
    IP网际协议
    应用层协议
    [Eclipse插件] Eclipse设置Tab键为空格(ctrl+shirt+f格式化生效)!
    [Android Pro] 使用CursorLoader异步加载数据 from 3.0
    [Android Memory] 内存分析工具 MAT 的使用
  • 原文地址:https://www.cnblogs.com/liuzhuan23/p/13092009.html
Copyright © 2011-2022 走看看