前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[MYSQL] mysql数据加密原理和解析

[MYSQL] mysql数据加密原理和解析

原创
作者头像
大大刺猬
发布于 2024-09-27 09:52:32
发布于 2024-09-27 09:52:32
70300
代码可运行
举报
文章被收录于专栏:大大刺猬大大刺猬
运行总次数:0
代码可运行

导读

上一章我们讲了mysql压缩原理(含lz4压缩格式)并解析, 细心的同学应该发现旁边就是加密的相关代码. 那本章就来讲讲mysql加密和解析.

理论上, 看完本篇文章, 就能通过 keyring文件解析ibd文件了. 仅考虑社区版的keyring插件

mysql加密

低版本是使用plugin, 高版本使用Components.(花里胡哨的). 本次使用Plugin的方式安装keyring. 参考如下:

代码语言:shell
AI代码解释
复制
# 配置文件添加如下信息:
early-plugin-load=keyring_file.so
keyring_file_data=/usr/local/mysql/keyring/keyring2

# 重启mysql实例
systemctl restart mysqld_3314

注: 这个keyring2(名字随便取)文件别整丢了, 不然数据就gg了. 我测试的时候,换了个新名字(生成新的master_key)之后, 旧的表就无法读取了. 会报错:2024-09-27T02:23:25.097676Z 9 ERROR InnoDB Encryption information in datafile: ./db1/t20240926.ibd can't be decrypted, please confirm that keyring is loaded. 做校验的时候,没注意, 坑了我一手......

表加密

本次演示解析如下表

代码语言:sql
AI代码解释
复制
create table db1.t20240926(id int primary key, name varchar(200)) encryption='y';
insert into db1.t20240926 values(1,'ddcw');
insert into db1.t20240926 values(2,'ddcw');

-- 给已有的表设置加密
alter table db1.t1 encryption='y';

表空间加密

general tablespace也是支持加密的. 虽然使用场景少

代码语言:sql
AI代码解释
复制
ALTER [UNDO] TABLESPACE tablespace_name
  NDB only:
    {ADD | DROP} DATAFILE 'file_name'
    [INITIAL_SIZE [=] size]
    [WAIT]
  InnoDB and NDB:
    [RENAME TO tablespace_name]
  InnoDB only:
    [AUTOEXTEND_SIZE [=] 'value']
    [SET {ACTIVE | INACTIVE}]
    [ENCRYPTION [=] {'Y' | 'N'}]
  InnoDB and NDB:
    [ENGINE [=] engine_name]
  Reserved for future use:
    [ENGINE_ATTRIBUTE [=] 'string']

master_key轮换

有时候一个key用久了, 就觉得不安全, 想换一个也是可以的. mysql支持轮转key

代码语言:sql
AI代码解释
复制
ALTER INSTANCE ROTATE MASTER KEY;

mysql加密原理解析

mysql的加密实际上是分为两部分的, keyring file里面存储了一系列master_key, 然后使用master_key加密tablespace_key(加密之后的tablespace_key放在fsp), tablespace_key才是用来加密数据page的

这种设计应该是为了支持轮转key

大概如下图:

虽然图看着丑, 但意思就是这样的.

或者借用Mayank Prasad的图如下:

keyring file

现在来具体瞧瞧, 先看瞧瞧keyring file格式, 该格式是二进制的. 无法直接查看.

看了下源码, 复杂到离谱. 但好歹有大佬解析过的. 我们就直接看格式吧.

其实也能猜到大概, 但做亦或那里就难发现了..

keyring_file由一系列master_key组成. 格式如下:

对象

大小(字节)

描述

header

24

描述信息, 比如版本之类的

total_length

8

该master_key占的总大小

key_id_length

8

key_id长度

key_type_length

8

加密算法类型, 通常为AES

user_id_length

8

user_id,没发现有啥用...

key_length

8

key的长度

key_id

fsp保存的key_id和这个呼应上了,就取这个的key

key_type

加密算法的类型

user_id

没dio用

key

32(通常是)

给tablespace_key加密的key, 得先和obfuscate_str做亦或

....

n

重复master_key

EOF

3

EOF

....

...

..

虽然看起来有丢丢复杂, 但实际上就一丢丢信息... 我们可以使用如下python代码来解析

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
import struct
from Crypto.Cipher import AES
keyring_filename = '/usr/local/mysql/keyring/keyring2'
filename = '/data/mysql_3314/mysqldata/db1/t20240926.ibd'
def read_keyring(data):
	offset = 24
	kd = {}
	xor_str = '*305=Ljt0*!@$Hnm(*-9-w;:'.encode()
	while True:
		if data[offset:offset+3] == b'EOF':
			break
		total_length, key_id_length, key_type_length, user_id_length, key_length = struct.unpack_from('<QQQQQ', data, offset) # 注意是小端字节序...
		offset += 40
		key_id = data[offset:offset+key_id_length].decode()
		offset += key_id_length
		key_type = data[offset:offset+key_type_length].decode()
		offset += key_type_length
		user_id = data[offset:offset+user_id_length]
		offset += user_id_length
		key = data[offset:offset+key_length]
		keyt = bytes([key[i] ^ xor_str[i%24] for i in range(len(key))])
		offset += key_length
		kd[key_id] = {'key':keyt,'key_type':key_type}
		if offset % 8 != 0:
			offset += 8 - (offset % 8)
	return kd

with open(keyring_filename,'rb') as f:
	keyring_data = f.read()

kd = read_keyring(keyring_data)
print(kd)

我这里只有一个key, 如果做过rotate的, 或者给其它实例使用过的, 那么就会存在多个. 比如:

keyring格式整体比较简单, 就是得和一个常量做亦或比较坑人.

encryption_metadata

在解析得到master_key之后, 我们就可以解析fsp去获取tablespace_key了. 先看看fsp中记录的encryption_metadata格式吧. 总大小是115字节. 在我们之前解析sdi的时候有见到过(当时年轻,不知其含义)

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
#MAGIC_SIZE=3  KEY_LEN=32  SERVER_UUID_LEN=36
#(MAGIC_SIZE + sizeof(uint32) + (KEY_LEN * 2) + SERVER_UUID_LEN + sizeof(uint32))
INFO_SIZE = 3+4+32*2+36+4
INFO_MAX_SIZE = INFO_SIZE + 4
#SDI_OFFSET = 38+112+40*256 + INFO_MAX_SIZE
SDI_VERSION = 1

  /* Encryption info to be filled in following format
    --------------------------------------------------------------------------
   | Magic bytes | master key id | server uuid | tablespace key|iv | checksum |
    --------------------------------------------------------------------------
  */

具体内容如下:

对象

大小(字节)

描述

magic

3

版本关键字

master_key_id

4

master_key_id.

server_uuid

36

server_uuid

key_info

32*2

tablespace_key+iv

checksum

4

使用crc32c校验的

null

4

空了4字节,不造干嘛的

5.7.11引入的加密功能, 具体的magci对应如下

代码语言:c++
AI代码解释
复制
KEY_MAGIC_V1[] = "lCA";  // 5.7.11
KEY_MAGIC_V2[] = "lCB";  // 5.7.12+
KEY_MAGIC_V3[] = "lCC";  // 8.0.5+

keyring中的master_id实际上是encryption_metadata中的uuid+master_id.

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
master_key = kd['INNODBKey'+'-'+server_uuid+'-'+str(master_id)]['key']

熟悉aes的cbc模式的小伙伴可能会疑惑,iv不是要求16字节么, 这里是使用的32字节啊.... (实际上是取的32字节中的前16字节. 小坑).

我们再使用代码解析下吧. 这里的crc32是使用的crc32算法, 可参考之前坏块校验

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
## 解析keyring的代码我就省略了, 上面有的.
kd = read_keyring(keyring_data)
f = open(filename,'rb')
fsp = f.read(16384)
#struct.unpack('>BBHHH',fsp[26:34])
data = fsp[10390:10390+115]
print(data[:3]) # lCC
master_id = struct.unpack('>L',data[3:7])[0]
server_uuid = data[7:7+36].decode()
master_key = kd['INNODBKey'+'-'+server_uuid+'-'+str(master_id)]['key']
ase = AES.new(master_key,AES.MODE_ECB)
print('MASTER_KEY:',master_key)
key_info = ase.decrypt(data[43:43+32*2])
print('KEY:',key_info[:32])
print('IV:',key_info[32:48])

那怎么校验呢? 先别急.

官方为了支持rotate, 使用了keyring,里面保存多个key,那么就得确保里面的key能够解析fsp的tablespace_key. 所以整了个校验位.... 我们来校验下.

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
# crc32c的导入参考: https://github.com/ddcw/ddcw/tree/master/python/check_innodb_file  我这里就省略了.
calculate_crc32c(key_info) # 小坑,是校验的整个key_info(不是key+iv). mysql到处给我埋坑....
struct.unpack('>L',fsp[10390:10390+115][-8:-4])[0]

看来我们成功解析到了tablespace_key.

解析加密后的数据文件

既然tablespace_key已经获取到了, 那就该解析数据了. 加密的格式和压缩页的格式是一样的. 那就只需要把解压换成解密就行了(就换一个汉字). 先看看长什么样子.

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
f.seek(4*16384,0)
data = f.read(16384)
struct.unpack('>BBHHH',data[26:34])
data[:200]

看起来是个index page. 而且数据全是加密的. 那就开始解密吧.

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
backend = default_backend()
cipher = Cipher(algorithms.AES(key_info[:32]), modes.CBC(key_info[32:48]),backend=backend)
decryptor = cipher.decryptor()
dedata = data[:38] + decryptor.update(data[38:])
dedata[:200]

看到眼熟的infimum了. 那就说明我们基本上解析对了. 但我们再拼接为sql瞅瞅.

ibd2sql

我们还是使用ibd2sql来解析.

代码语言:shell
AI代码解释
复制
wget https://github.com/ddcw/ibd2sql/archive/refs/heads/main.zip
unzip main.zip
cd ibd2sql-main
vim ibd2sql/ibd2sql.py添加如下逻辑

from ibd2sql import encrypt
....
# 之前压缩页那再来个elif (我们没有提前解析fsp的encryption_metadata, 所以得把fd也搞过去.)
elif data[24:26] == b'\x00\x0f': # 15: 加密页
			FIL_PAGE_VERSION,FIL_PAGE_ALGORITHM_V1,FIL_PAGE_ORIGINAL_TYPE_V1,FIL_PAGE_ORIGINAL_SIZE_V1,FIL_PAGE_COMPRESS_SIZE_V1 = struct.unpack('>BBHHH',data[26:34])
			data = data[:24] + struct.pack('>H',FIL_PAGE_ORIGINAL_TYPE_V1) + b'\x00'*8 + data[34:38] + encrypt.decrypt(self.f,data[38:])

然后再把上面解密的代码整合一下得到encrypt.

代码语言:python
代码运行次数:0
运行
AI代码解释
复制
# vim ibd2sql/encrypt.py
import struct
from Crypto.Cipher import AES
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

keyring_filename = '/usr/local/mysql/keyring/keyring2'
def read_keyring(data):
	offset = 24
	kd = {}
	xor_str = '*305=Ljt0*!@$Hnm(*-9-w;:'.encode()
	while True:
		if data[offset:offset+3] == b'EOF':
			break
		total_length, key_id_length, key_type_length, user_id_length, key_length = struct.unpack_from('<QQQQQ', data, offset)
		offset += 40
		key_id = data[offset:offset+key_id_length].decode()
		offset += key_id_length
		key_type = data[offset:offset+key_type_length].decode()
		offset += key_type_length
		user_id = data[offset:offset+user_id_length]
		offset += user_id_length
		key = data[offset:offset+key_length]
		keyt = bytes([key[i] ^ xor_str[i%24] for i in range(len(key))])
		offset += key_length
		kd[key_id] = {'key':keyt,'key_type':key_type}
		if offset % 8 != 0:
			offset += 8 - (offset % 8)
	return kd

with open(keyring_filename,'rb') as f:
	keyring_data = f.read()

def decrypt(f,bdata):
	f.seek(0,0)
	kd = read_keyring(keyring_data)
	fsp = f.read(16384)
	data = fsp[10390:10390+115]
	master_id = struct.unpack('>L',data[3:7])[0]
	server_uuid = data[7:7+36].decode()
	master_key = kd['INNODBKey'+'-'+server_uuid+'-'+str(master_id)]['key']
	ase = AES.new(master_key,AES.MODE_ECB)
	key_info = ase.decrypt(data[43:43+32*2])
	backend = default_backend()
	cipher = Cipher(algorithms.AES(key_info[:32]), modes.CBC(key_info[32:48]),backend=backend)
	decryptor = cipher.decryptor()
	return decryptor.update(bdata)

直接解析加密的ibd文件 (作者又没加encrypt属性...)

看起来我们是解析成功的了.

总结

mysql的加密数据是使用keyring来实rotate的. 即keyring文件中的master_key来加密fsp中的tablespace_key, 而数据页的加密实际上是使用tablespace_key来加密的. 如果加密文件丢了/损坏/替换了, 数据就恢复不了了. 加密主要是使用aes算法.(ecb模式和cbc模式都用了).

不建议使用数据库层的加密,比较耗费cpu.

解析的时候由于keyring替换了一次, 导致做校验的时候一直没通过, 找了很久原因. 最终看了下日志, 有[MY-012226] [InnoDB] Encryption information in datafile 才发现原因的..

可以根据文中的步骤来测试, 也可以等下个版本ibd2sql更新了再去测试.

参考:

https://dev.mysql.com/blog-archive/mysql-innodb-transparent-tablespace-encryption/

https://mysql.wisborg.dk/2019/01/28/automatic-decryption-of-mysql-binary-logs-using-python/

https://github.com/ddcw/ibd2sql

https://github.com/mysql/mysql-server

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
ggtree-给你的进化树盛世美颜
ggtree是ggplot2的拓展包,可以应用于进化树的绘制,还能对进化树丰富的注释分析。
作图丫
2022/03/29
12.5K0
ggtree-给你的进化树盛世美颜
跟着NatureCommunications学作图:R语言ggtree根据分组给进化树上色
https://www.nature.com/articles/s41467-022-29438-7
用户7010445
2023/01/06
1.6K0
跟着NatureCommunications学作图:R语言ggtree根据分组给进化树上色
跟着Nature Plants学作图:R语言ggtree包展示进化树
https://www.nature.com/articles/s41477-022-01146-6#Sec44
用户7010445
2022/05/23
3.6K0
跟着Nature Plants学作图:R语言ggtree包展示进化树
跟着NatureGenetics学作图:R语言ggplot2做进化树图及添加不同形状的背景色块
https://www.nature.com/articles/s41588-022-01127-7#Sec31
用户7010445
2023/01/06
1.6K0
跟着NatureGenetics学作图:R语言ggplot2做进化树图及添加不同形状的背景色块
跟着Nature Communications学作图:R语言ggtree绘制进化树
A highly conserved core bacterial microbiota with nitrogen-fixation capacity inhabits the xylem sap in maize plants
用户7010445
2023/01/06
1.3K0
跟着Nature Communications学作图:R语言ggtree绘制进化树
如何使用R语言ggtree包在进化树上标记自己取样测序的样本
随着三代测序技术的发展和测序成本的下降,现在基于三代测序数据组装基因组做泛基因组的研究越来越多。虽然测序成本降低了许多,但也是相对于之前,做大规模的测序组装的费用也是非常昂贵的,现在通常的做法是如果做了大规模的二代测序,通常会利用这些数据做的进化树,然后根据进化树的分布在每一个类群里选取一些有代表性的个体去做三代测序组装。比如大豆cell发表的泛基因组论文,就是从2000多份材料里选择26份有代表性的材料。
用户7010445
2024/02/23
3110
如何使用R语言ggtree包在进化树上标记自己取样测序的样本
使用Y叔神包ggtree进行基因家族基因进化树构建
大家好,我是技能树的老朋友啦,三年前在群主的第一波RNA-seq入门8步活动中因为表现优异获得群主青睐成为技能树VIP一员,也开启了自己的学习经验分享人生!
生信技能树
2019/08/02
9.1K1
使用Y叔神包ggtree进行基因家族基因进化树构建
跟着Nature学作图:R语言ggplot2+ggtree树图组合热图
1、进化树中挑选子集 2、进化树默认是左下角到右上角这种布局,如何调整成左上到右下角这种布局 3、进化树把某个clade压缩成三角性状 4、给进化树添加根小尾巴
用户7010445
2024/02/03
8800
跟着Nature学作图:R语言ggplot2+ggtree树图组合热图
R语言的ggtree展示进化树的一些常用操作
可以首先加上theme_tree2()函数显示出坐标轴范围,然后用xlim()函数更改坐标轴范围
用户7010445
2021/01/06
14K0
一步一步教你使用ggtree
ggtree是R语言中一个强大的系统发育树可视化及注释软件包,在Bioconductor中发布,同时兼有ggplot2的优点。ggtree可以读取多种格式(包括newick,nexus,NHX,jplace和phylip)的系统发育树,并结合不同类型的相关数据进行注释分析。在R中ggtree的安装方法如下:
SYSU星空
2022/05/05
9.6K0
一步一步教你使用ggtree
R语言ggtree画圆形的树状图展示聚类分析的结果
那么圆形的树状图如何实现呢?我查找了一下相关资料。R语言包dendextend这个包可以实现,利用help(package="dendextend")查看帮助文档,能够看到其中的一个小例子
用户7010445
2020/11/13
3.7K0
R语言ggtree画圆形的树状图展示聚类分析的结果
跟着ISEM学作图:R语言ggtree+ggplot2组合进化树和气泡图
论文 Conserved and reproducible bacterial communities associate with extraradical hyphae of arbuscular mycorrhizal fungi image.png 今天的推文我们来重复一下论文中的 Figure 2 image.png 没有找到论文提供的原始数据,这里数据我自己构造一份 首先是左侧的进化树文件 (((A8:0.9735669859,((A5:0.7219205995,A9:0.53850
用户7010445
2022/05/23
1.4K0
跟着ISEM学作图:R语言ggtree+ggplot2组合进化树和气泡图
跟着Nature学作图:R语言ggtree给进化树的节点添加饼状图
https://www.nature.com/articles/s41586-022-04897-6
用户7010445
2023/01/06
7220
跟着Nature学作图:R语言ggtree给进化树的节点添加饼状图
R语言ggtree+msa可视化进化树+多序列比对的结果
里面一小部分内容是关于进化树的可视化展示并且关联多序列比对的结果的。记录下这个代码
用户7010445
2021/11/16
2.1K0
R语言ggtree+msa可视化进化树+多序列比对的结果
只需2步!用ChatGPT打造CNS级精美图表
各位科研小伙伴,是不是经常遇到这样的情况:看到论文里一个超级赞的图,结果作者既没有标明图的类型,也没提供相应的代码?只能干瞪眼,到处求源代码
用户11203141
2025/03/06
1290
只需2步!用ChatGPT打造CNS级精美图表
文献笔记五十六:武汉新型冠状病毒的进化分析2
论文题目 Evolution of the novel coronavirus from the ongoing Wuhan outbreak and modeling of its spike pr
用户7010445
2020/03/03
5520
跟着Nature Methods学画图:R语言ggplot2+ggtree+aplot画气泡图组合聚类树图
论文对应的代码是公开的 https://github.com/ajwilk/2020_Wilk_COVID
用户7010445
2021/03/15
2.4K0
技术贴:R语言拼图全面介绍
说起R语言的拼图,可能大家一点都不陌生,比如常用的“cowplot”和“patchwork”。gridExtra包也提供了一个拼图函数”grid.arrange“。另外,南方医科大学余光创教授也开发了一个更为神奇的拼图R包:aplot。本文将依次对它们的用法进行介绍。
作图丫
2022/03/29
6K0
技术贴:R语言拼图全面介绍
R语言读入比对好的fasta文件然后做NJ树并做boostrap检验
今天的推文内容主要参考 https://www.rpubs.com/michelleprem/683962 https://fuzzyatelin.github.io/bioanth-stats/module-24/module-24.html 首先是读入数据 今天推文用到的示例数据是参考链接2中提供的usflu.fasta,fasta文件已经比对好,R语言里读入fasta格式的数据可以使用adegenet包中的fasta2DNAbin函数 #install.packages("adegenet") li
用户7010445
2021/01/20
1.7K0
R语言读入比对好的fasta文件然后做NJ树并做boostrap检验
R语言ggtree按照指定的节点旋转树
http://yulab-smu.top/treedata-book/index.html
用户7010445
2021/01/20
1.9K0
R语言ggtree按照指定的节点旋转树
推荐阅读
相关推荐
ggtree-给你的进化树盛世美颜
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档