CryptDB代码分析1-lua与加密库
之前的文章 ”CryptDB原理概述“ 介绍了CryptDB的基本原理,接下来从代码的角度介绍其实现原理。本文首先关注mysql-proxy的lua脚本与CryptDB加密库的交互过程。
前期准备
在进行源码阅读和调试之前,首先需要进行CryptDB的安装。 之前已经对CryptDB在ubuntu 16.04上的安装做过介绍。也可以使用我在github上共享的项目:https://github.com/yiwenshao/Practical-Cryptdb,里面对原始的安装脚本做了小改,在ubuntu16.04只要执行INSTALL.sh 就可以完成全部的安装工作。
安装完成以后,首先执行如下的命令:
1 | mkdir shadow |
启动mysql-proxy[1],然后就可以通过MySQL的客户端连接mysql-proxy来完成数据库操作。 所有SQL语句首先经过mysql-proxy的加密处理, 然后转发给MySQL服务器。 对于MySQL服务器的返回结果, 也是先转发给mysql-proxy, 经过解密处理以后返回给客户端。
mysql-proxy通过lua脚本调用CryptDB的加密库来完成SQL语句的加密操作,而上面建立的shadow目录则是为了保存一个embedded模式的MySQL数据库, 里面存储了密钥以及加密洋葱的结构的等相关信息。
所以,接下来,我们先从lua脚本的入口出发,看一个SQL语句在加密过程中会经过哪些模块。
mysql-proxy的与CryptDB的交互
mysql-proxy提供了如下几个函数, 在客户端执行SQL的不同阶段, 会调用这几个函数, 这些函数实现在 wrapper.lua 中:
- read_auth()
- disconnect_client()
- read_query(packet)
- read_query_result(inj)
在上面的四个函数的实现中,调用了CryptDB的加密库中的几个函数,这些函数实现在 mysqlproxy/ConnectWrapper.cc 中,分别是:
- connect
- disconnect
- rewrite
- next
所以,所有的CryptDB操作,都是以上面的8个函数为基础的。对于CryptDB加密库而言,里面的connect以及disconnect 只在客户端建立连接和客户端离开的时候被调用,所有的加解密功能都是通过rewrite和next这两个函数来完成,这两个函数就是CryptDB所有功能的入口。
回到mysql-proxy的lua脚本中的四个函数,可以分以下三个阶段来介绍。
客户端建立连接
在这个阶段,lua脚本中的函数read_auth被调用,其内部调用CryptDB的函数connect. 在这个阶段,需要为每个客户端建立一个WrapperState结构来保存相关的信息。不同的客户端通过ip+port来标识,多个客户端的信息则通过一个如下的map结构来进行保存:
1 |
|
要使用加密库,还需要进行适当的初始化,并且多个client之间有共享状态。所以,如果当前连接进来的是第一个客户端,则需要对这个共享状态进行初始化,其对应的是一个变量:
1 | SharedProxyState * shared_ps//多个client的共享状态 |
客户端离开
当客户端关闭的时候,lua脚本中的disconnect_client函数被调用。其内部调用CryptDB的disconnect函数。这个阶段会把map中保存的客户端的信息删除。
客户端的命令执行流程
客户端发送命令给mysql-proxy的时候,lua脚本中的**read_query(packet)函数被调用,参数packet中包含了SQL命令。MySQL执行结果返回的时候,lua脚本中的read_query_result(inj)**函数被调用,参数inj包含了返回结果。
- 首先来看read_query函数。
当read_query 函数获取到客户端发送的明文SQL 命令的时候,会调用lua脚本中的read_query_real函数,其内部首先调用CryptDB库中的rewrite函数,完成SQL语句的改写。改写后的SQL语句保存在之前介绍过的 clients 结构中。然后调用lua脚本中的next _handler 函数,其内部调用CryptDB库中的next函数。在next函数中,首先执行函数获得一个参数result_type,分为三种情况,根据不同的结果选择不同的执行流程,包含了SQL命令执行的所有情况,分别如下:
1 | //mysqlproxy/ConnectWrapper.cc |
对于一般的SQL语句的执行,分为两种情况,第一种是直接进入QUERY_USE_RESULTS分支,返回SQL语句给lua脚本,SQL执行的结果直接返回给客户端。 第二种是进入第一个分支QUERY_COME_AGAIN,返回SQL语句给lua脚本转发到MySQL执行,返回的结果再次进入next函数,执行并且进入第三个分支,返回解密的结果给客户端。
另外,对于有些语句,并不需要调用rewrite函数进行加密,在lua脚本的阶段直接过滤了,这些情况就更加简单。
- 然后来看read _query_results阶段。
当上面介绍的加密SQL语句发送给MySQL执行,并返回执行结果给mysql-proxy的时候,会调用lua脚本中的**read _query_results(inj)**函数。如果在read_query阶段进入了第二个分支,那么lua脚本会设置一个全局变量skip为true,read_query_results的处理就被跳过,直接返回结果给客户端。如果在read_query阶段进入了第一个分支,则会在这里再次调用next_handler函数,从而进入next函数,再次执行并进入switch分支的判断流程。
两个执行的例子
一些解密的细节以及类的介绍将在后续的文章中给出。在这里,给出两个SQL语句执行的例子,用于说明执行过程中lua脚本以及CryptDB库中的几个函数的调用过程,以及几个主要执行分支的含义。
- show databases;
该命令的处理流程:
首先进入read_query,内部调用CryptDB的rewrite函数进行加密,然后调用lua中的next_handler,内部调用CryptDB的next函数,根据上面介绍的,进入switch的第二个分支,表示执行命令的结果不需要处理,直接返回给客户端。然后给lua脚本传递改写以后的命令。
再次回到lua脚本的next_handler函数,其处理了query results分支,将获得的命令转发给MySQL执行。执行完成以后的结果返回给mysql-proxy的时候,会调用read_query_result函数。根据上面介绍的,由于在read_query阶段进入了第二个分支,这里的处理会被跳过,也就是不做任何处理,结果直接返回给客户端。这样,客户端就得到了show databases的执行结果。
- select * from student;
我们假设已经有了一张表,对其进行select操作,其对应的执行流程如下。
首先进入read_query,内部调用CryptDB的rewrite函数进行SQL语句的加密,返回以后调用lua中的next_handler函数,内部调用CryptDB的next函数,根据上面介绍的,进入第一个分支:QUERY_COME_AGAIN。返回加密以后的SQL命令给lua脚本。
在lua脚本中,next_handler处理了again分支,发送加密命令给MySQL,获得select返回的加密结果以后,lua脚本中的read_query_results被调用。由于在read_query阶段进入了第一个分支,这里会继续调用next_handler函数,并进入到next函数,执行获得result_type,然后这次进入到RESULTS分支。在lua脚本中,处理了results分支,将解密后的结果返回给客户端,这样就完成了整个流程。
总结
本文介绍了SQL加密执行过程中,mysql-proxy使用的lua脚本与CryptDB的加密库的交互过程,其中主要的几个函数是lua脚本中的read_query,read_query_results,以及CryptDB库中的rewrite和next。代码位于wrapper.lua以及mysqlproxy/ConnectWrapper.cc中。