CryptDB要進(jìn)行數(shù)據(jù)加密,需要實(shí)現(xiàn)具體的加密算法,然后使用加密層類型來進(jìn)行封裝。如果用戶想在其上實(shí)現(xiàn)新的功能,一方面需要實(shí)現(xiàn)加密算法,一方面要添加加密層以及其他相關(guān)輔助結(jié)構(gòu)。本文介紹這兩者之間的一些接口設(shè)計(jì),方便大家基于現(xiàn)有代碼做實(shí)驗(yàn)。
加密算法
CryptDB使用了AES,OPE,blowfish,Pailliar,Search算法,用到了openssl以及NTL庫,相關(guān)代碼全部位于crypto目錄下。這里主要關(guān)注其對外的接口,忽略算法的實(shí)現(xiàn)步驟。要實(shí)現(xiàn)新的算法,需要使用類似的方法定義接口并做內(nèi)部實(shí)現(xiàn)。
幾種算法接口介紹
blowfish
blowfish算法實(shí)現(xiàn)位于crypto/blowfish.hh中。相關(guān)代碼如下:
class blowfish {
public:
blowfish(const std::string &key) {
BF_set_key(&k, key.size(), (const uint8_t*) key.data());
}
uint64_t encrypt(uint64_t pt) const {
uint64_t ct;
block_encrypt(&pt, &ct);
return ct;
}
uint64_t decrypt(uint64_t ct) const {
uint64_t pt;
block_decrypt(&ct, &pt);
return pt;
}
static const size_t blocksize = 8;
private:
BF_KEY k;
};
可以看到,要使用blowfish,首先要有密鑰key來完成初始化類,然后分別使用encrypt和decrypt函數(shù)來實(shí)現(xiàn)加解密。算法處理的數(shù)據(jù)類型是uint64_t。
AES
CryptDB使用了兩種AES的模式,其加解密相關(guān)的函數(shù)如下:
string
encrypt_AES_CBC(const string &ptext, const AES_KEY * enckey, string salt, bool dopad);
string
decrypt_AES_CBC(const string &ctext, const AES_KEY * deckey, string salt, bool dounpad);
string
encrypt_AES_CMC(const string &ptext, const AES_KEY * enckey, bool dopad);
string
decrypt_AES_CMC(const string &ctext, const AES_KEY * deckey, bool dopad);
可以看到,使用AES也是需要有密鑰,通過encrypt和decrypt函數(shù)來完成加解密功能。處理的數(shù)據(jù)類型是string。
Pailliar
class Paillier_priv : public Paillier{
NTL::ZZ decrypt(const NTL::ZZ &ciphertext) const;
NTL::ZZ encrypt(const NTL::ZZ &plaintext);
NTL::ZZ add(const NTL::ZZ &c0, const NTL::ZZ &c1) const;
...
}
//使用舉例
Paillier_priv * sk;
sk = new Paillier_priv();
ZZ pt0 = NTL::to_ZZ(1);
ZZ pt1 = NTL::to_ZZ(2);
const ZZ enc0 = sk->encrypt(pt0);
const ZZ dec0 = sk->decrypt(enc0);
const ZZ enc1 = sk->encrypt(pt1);
const ZZ dec1 = sk->decrypt(enc1);
assert((pt0+pt1)==sk->decrypt(sk->add(enc0,enc1)));
上面給出了一個(gè)Pailliar使用的例子,可以看到,Pailliar算法提供了加解密以及密文相加得到密文的功能(同態(tài)加法)。處理過程使用了NTL庫中的ZZ大整數(shù)類型。
Search
class search_priv : public search {
public:
search_priv(const std::string &key, size_t csize_arg = defsize)
: search(csize_arg), master_key(key) {}
std::vector<std::string>
transform(const std::vector<std::string> &words);
std::string
wordkey(const std::string &word);
private:
std::string
transform(const std::string &word);
std::string master_key;
};
對于search,首先需要對輸入文字做劃分,形成不同的關(guān)鍵字,對關(guān)鍵字進(jìn)行加密,這是函數(shù)transform的作用。在進(jìn)行match的時(shí)候,則是通過wordKey函數(shù)對關(guān)鍵字處理形成token,然后使用token調(diào)用match函數(shù)進(jìn)行匹配,輸出的結(jié)果是一個(gè)bool類型。
OPE
OPE算法對于字符串和整數(shù),實(shí)現(xiàn)分別如下:
class OPE {
NTL::ZZ encrypt(const NTL::ZZ &ptext);
NTL::ZZ decrypt(const NTL::ZZ &ctext);
};
OPE ope(rawkey,8*plain_size,8*ciph_size);
std::string ptext="helloworld";
std::string ps = toUpperCase(ptext);
if (ps.size() < plain_size)
ps = ps + std::string(plain_size - ps.size(), 0);
uint32_t pv = 0;
for (uint i = 0; i < plain_size; i++) {
pv = pv * 256 + static_cast<int>(ps[i]);
}
const ZZ enc = ope.encrypt(to_ZZ(pv));
可以看到,對于整數(shù)來說,提供了加解密函數(shù)。并且由于OPE的性質(zhì),密文有保序的性質(zhì)。對于字符串,還是同樣的類實(shí)現(xiàn),只是加密的時(shí)候做了處理,使得字符串加密依然可以保序。
接口總結(jié)
可以看到,在crypto目錄中的代碼提供了底層的加密功能,大部分類都提供了加解密函數(shù)encrypt和decrypt,如果算法有特定功能如同態(tài)加,則需要添加額外的函數(shù)。這些底層庫沒有使用MySQL內(nèi)部數(shù)據(jù)類型。 所以,如果要自己添加新的算法,首先需要在crypto目錄添加底層加密代碼,對外提供encrypt,decrypt以及密文計(jì)算函數(shù)。 這部分代碼可以獨(dú)立編譯運(yùn)行以及測試。
加密層
首先,下面代碼中用到的加密層以及洋蔥都是枚舉類型,其相關(guān)的定義如下:
//位于util/onions.hh
typedef enum onion {
oDET,
oOPE,
oAGG,
oSWP,
oPLAIN,
oBESTEFFORT,
oASHE,
oINVALID,
} onion;
enum class SECLEVEL {
INVALID,
PLAINVAL,
OPEFOREIGN,
OPE,
DETJOIN,
DET,
SEARCH,
HOM,
ASHE,
RND,
};
有了這些枚舉類型表示洋蔥和加密層,接下來就需要具體的加密層的實(shí)現(xiàn),以及一些輔助類來完成加密層的管理。
加密層實(shí)現(xiàn)
加密層相關(guān)的類主要實(shí)現(xiàn)了以下幾個(gè)函數(shù):
class EncLayer : public LeafDBMeta {
virtual Create_field *
newCreateField(const Create_field &cf,
const std::string &anonname = "") const = 0;
virtual Item *encrypt(const Item &ptext, uint64_t IV) const = 0;
virtual Item *decrypt(const Item &ctext, uint64_t IV) const = 0;
virtual Item *decryptUDF(Item * const col, Item * const ivcol = NULL)
const;
virtual std::string doSerialize() const = 0;
std::string serialize(const DBObject &parent) const
{
return serial_pack(this->level(), this->name(),
this->doSerialize());
}
};
其類的繼承結(jié)構(gòu)之前已經(jīng)介紹過,如下圖:
相關(guān)要點(diǎn)如下:
- serialize函數(shù)實(shí)現(xiàn)了加密層的序列化
- 反序列化功能在LayerFactory管理類中實(shí)現(xiàn),后面會(huì)介紹
- 加解密函數(shù)encrypt和decrypt,是對上面介紹的crypto目錄中的底層庫的封裝。由于這里處理的都是item類型,所以需要進(jìn)行item類型和普通數(shù)據(jù)類型的互相轉(zhuǎn)換
- decryptUDF用于返回一個(gè)UDF函數(shù),做洋蔥層次調(diào)整
- newCreateField用來處理加密帶來的數(shù)據(jù)類型的變化。比如原來是整數(shù)類型,經(jīng)過了Pailliar的加密,就變成了二進(jìn)制字符串類型
除了上面的通用函數(shù),具體的加密層也會(huì)有自己特有的函數(shù)來實(shí)現(xiàn)密文計(jì)算功能。
加密層的實(shí)現(xiàn)舉例
我們以pailliar(HOM)為例,給出一個(gè)加密層實(shí)現(xiàn)的例子:
class HOM : public EncLayer {
public:
Create_field * newCreateField(const Create_field &cf,
const std::string &anonname = "")
const;
Item *encrypt(const Item &p, uint64_t IV) const;
Item * decrypt(const Item &c, uint64_t IV) const;
//expr is the expression (e.g. a field) over which to sum
Item *sumUDA(Item *const expr) const;
Item *sumUDF(Item *const i1, Item *const i2) const;
protected:
std::string const seed_key;
static const uint nbits = 1024;
mutable Paillier_priv * sk;
};
Create_field *
HOM::newCreateField(const Create_field &cf,
const std::string &anonname) const{
return arrayCreateFieldHelper(cf, 2*nbits/BITS_PER_BYTE,
MYSQL_TYPE_VARCHAR, anonname,
&my_charset_bin);
}
Item *
HOM::encrypt(const Item &ptext, uint64_t IV) const{
const ZZ enc = sk->encrypt(ItemIntToZZ(ptext));
return ZZToItemStr(enc);
}
Item *
HOM::decrypt(const Item &ctext, uint64_t IV) const {
const ZZ enc = ItemStrToZZ(ctext);
const ZZ dec = sk->decrypt(enc);
return ZZToItemInt(dec);
}
上面的簡化代碼展示了以下三點(diǎn)
- 加密層類型是對crypto目錄中的加密相關(guān)類的封裝,比如這里的HOM封裝了Paillier_priv類型
- encrypt與decrypt中,對item類型進(jìn)行轉(zhuǎn)換,使得其能夠適配crypto目錄中相關(guān)底層庫進(jìn)行加解密,之后又將結(jié)果轉(zhuǎn)換為item類型返回
- sumUDF和sumUDA返回UDF,來實(shí)現(xiàn)MySQL Server端的同態(tài)加法操作(參考之前的文章以及原始論文中的介紹),其相關(guān)UDF的實(shí)現(xiàn)位于udf/edb.cc中,關(guān)于UDF可以參考這里,如果要自己寫加密層,同樣需要熟悉UDF的編寫規(guī)則
- newCreateField函數(shù)返回了新的Create_field類型,來表示經(jīng)過HOM加密以后的數(shù)據(jù)類型
對于最后一點(diǎn),我們繼續(xù)看arrayCreateFieldHelper函數(shù)內(nèi)部的具體實(shí)現(xiàn),其簡化的示例代碼如下:
Create_field*
lowLevelcreateFieldHelper(const Create_field *f0){
f0->length = field_length;
f0->sql_type = type;
return f0;
}
可以看到,Create_field中有l(wèi)enghth和sql_type兩個(gè)成員。一開始的時(shí)候,sql_type是MYSQL_TYPE_LONG,是表示整數(shù)類型。而這里的函數(shù)只要把sql_type變?yōu)镸YSQL_TYPE_VARCHAR并設(shè)置對應(yīng)的長度為256就行了。
再舉一個(gè)實(shí)際的例子,我們執(zhí)行這樣的SQL語句:CREATE TABLE student(id integer),并且對id這列只設(shè)置HOM一個(gè)洋蔥,在MySQL端執(zhí)行SHOW CREATE TABLE以后看到的結(jié)果是:
//這里省略了額外的salt列
CREATE TABLE `table_NRDDWIRZPY` (
`NSPUQRQGCEoADD` varbinary(256) DEFAULT NULL
);
可以看到,原來的integer變成了varbinary(256),這個(gè)變化就是通過newCreateField函數(shù)的Create_field機(jī)制實(shí)現(xiàn)的。我們在處理階段修改了解析以后的LEX結(jié)構(gòu)中的Create_field成員,所以在將LEX結(jié)構(gòu)轉(zhuǎn)化回字符串類型的SQL語句時(shí),就可以得到包含正確數(shù)據(jù)類型的SQL語句。
加密層管理
加密層的創(chuàng)建依靠LayerFactory結(jié)構(gòu),不同的加密層有自己的factory。而這些factory又通過EncLayerFactory類來實(shí)現(xiàn)管理,其創(chuàng)建相關(guān)的代碼以及類的結(jié)構(gòu)如下:
class EncLayerFactory {
public:
static std::unique_ptr<EncLayer>
encLayer(onion o, SECLEVEL sl, const Create_field &cf,
const std::string &key);
// creates EncLayer from its serialization
static std::unique_ptr<EncLayer>
deserializeLayer(unsigned int id, const std::string &serial);
};
std::unique_ptr<EncLayer>
EncLayerFactory::encLayer(onion o, SECLEVEL sl, const Create_field &cf,
const std::string &key)
{
switch (sl) {
case SECLEVEL::RND: {return RNDFactory::create(cf, key);}
case SECLEVEL::DET: {return DETFactory::create(cf, key);}
case SECLEVEL::DETJOIN: {return DETJOINFactory::create(cf, key);}
case SECLEVEL::OPE:{return OPEFactory::create(cf, key);}
case SECLEVEL::OPEFOREIGN:{return OPEFOREIGNFactory::create(cf,key);}
case SECLEVEL::HOM: {return HOMFactory::create(cf, key);}
case SECLEVEL::ASHE: {return std::unique_ptr<EncLayer>(new ASHE(cf,key));}
case SECLEVEL::SEARCH: {
return std::unique_ptr<EncLayer>(new Search(cf, key));
}
case SECLEVEL::PLAINVAL: {
return std::unique_ptr<EncLayer>(new PlainText());
}
default:{}
}
FAIL_TextMessageError("unknown or unimplemented security level");
}
std::unique_ptr<EncLayer>
EncLayerFactory::deserializeLayer(unsigned int id,
const std::string &serial){
assert(id);
const SerialLayer li = serial_unpack(serial);
switch (li.l) {
case SECLEVEL::RND:
return RNDFactory::deserialize(id, li);
case SECLEVEL::DET:
return DETFactory::deserialize(id, li);
case SECLEVEL::DETJOIN:
return DETJOINFactory::deserialize(id, li);
case SECLEVEL::OPEFOREIGN:
return OPEFOREIGNFactory::deserialize(id,li);
case SECLEVEL::OPE:
return OPEFactory::deserialize(id, li);
case SECLEVEL::HOM:
return std::unique_ptr<EncLayer>(new HOM(id, serial));
case SECLEVEL::ASHE: return std::unique_ptr<EncLayer>(new ASHE(id, serial));
case SECLEVEL::SEARCH:
return std::unique_ptr<EncLayer>(new Search(id, serial));
case SECLEVEL::PLAINVAL:
return std::unique_ptr<EncLayer>(new PlainText(id));
default:{}
}
FAIL_TextMessageError("unknown or unimplemented security level");
}
從上面可以看到LayerFactory系列的類,主要提供了create和deserialize函數(shù),前者用于在內(nèi)存中直接創(chuàng)建加密層,后者用于對磁盤讀取的數(shù)據(jù)做反序列化來創(chuàng)建加密層,上一篇文章介紹的元數(shù)據(jù)讀取過程中的反序列化函數(shù),就來自這里的LayerFactory。
總結(jié)
通過本文我們可以發(fā)現(xiàn),crypto目錄實(shí)現(xiàn)了底層加密庫,主要就是要對外提供encrypt,decrypt,以及密文計(jì)算函數(shù)。這個(gè)庫操作的數(shù)據(jù)類型是普通的字符串和整數(shù)。這個(gè)模塊和MySQL沒有依賴,可以獨(dú)立編譯。
CryptDB需要對MySQL的parser中的LEX結(jié)構(gòu)中的Item類型做加密,底層加密庫不能直接處理Item,所以在EncLayer中要做一個(gè)封裝,這部分的內(nèi)容主要實(shí)現(xiàn)在main/CryptoHandlers.cc,用于處理數(shù)據(jù)類型的轉(zhuǎn)化。此外,EncLayer還需要處理序列化,UDF返回等功能。為了輔助加密層類型的使用,設(shè)計(jì)了LayerFactory系列的類,用于構(gòu)造加密層類,這個(gè)構(gòu)造分為普通構(gòu)造和反序列化構(gòu)造。這些factory類又通過EncLayerFactory類型來進(jìn)行統(tǒng)一管理。通過這些機(jī)制,底層的加密庫就和CryptDB的實(shí)現(xiàn)連接起來了,CryptDB會(huì)調(diào)用封裝好的EncLayer,而不直接使用底層的加密庫。
參考文獻(xiàn)
https://github.com/yiwenshao/Practical-Cryptdb
原始鏈接:yiwenshao.github.io/2018/03/25/CryptDB代碼分析5-底層加密庫與加密層/
文章作者:Yiwen Shao
許可協(xié)議: Attribution-NonCommercial 4.0
轉(zhuǎn)載請保留以上信息, 謝謝!