在AI业务的开发的过程当中,咱们经常须要对模型文件进行加密。
咱们从如下几个方面来讲一说AES的加密原理以及AOE里的工程实践。git
常见的加密算法,主要分为两种:
对称加密,采用单密钥的加密方法,同一个密钥能够同时用来加密和解密。经常使用的对称加密算法有DES,3DES,AES等。
非对称加密,须要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。经常使用的非对称加密算法有RSA,Elgamal,ECC等。github
用个比喻来理解一下这2种不一样的加密方式:
对称加密:假设有一个密码箱,A设置了一个密码锁好箱子,B知道这个密码之后,输入这个密码就能够打开箱子,这个密码就是秘钥,A和B使用相同的密码。
非对称加密:有一把锁和一把惟一的钥匙,A用锁把箱子锁好,B只有用这把钥匙才能打开这个箱子。这把锁就能够理解为公钥,是公开的。这把钥匙能够理解为私钥,是不公开的,是不在网络里传输的,只有私钥的拥有者才能打开这个箱子。算法
简单比较一下他们的差别:安全
功能特性 | 对称加密 | 非对称加密 |
---|---|---|
密钥特征 | 加密方和解密方使用同一个密钥 | 加密方和解密方使用不一样的密钥 |
加密效率 | 效率高,速度快 | 速度慢 |
密钥安全性 | 传输过程当中容易泄漏 | 安全性高 |
在实际的应用中,通常都是对称加密和非对称加密算法相结合,这样既能够保证加密的速度,也能够保证秘钥传输的安全性。以AES和RSA结合为例,有一种通用的作法,在向服务器发送一段数据信息的时候,先用AES算法对数据进行加密,再使用服务器的RSA公钥对AES的密钥进行加密后,最后数据和加密后的AES KEY上传到服务器。这样只有知道了服务器RSA私钥,才能解开获得正确的AES KEY,最终用AES KEY才能解开这段密文。服务器
深度学习中,模型训练是最关键和最有挑战性的。不只须要有丰富的经验定义出合适的模型,还须要有大量的数据集,常常须要3-4天才能训练出一个模型。出于对知识产权的保护或者商业保护,咱们经常须要对训练出来的模型文件进行加密。思考一下咱们对模型加密的需求:速度要快,安全性要高,不依赖服务器的状况下本地也能加密解密。另外还须要有一个摘要算法来校验,保证模型的完整性。咱们初步考虑使用AES算法,摘要算法使用MD5。下面咱们来看看AES算法是如何工做的。微信
AES,Advanced Encryption Standard,是对称加解密算法的最经典算法之一,标准的AES加密位宽是每一个块128bit,密钥分为128bit,192bit,256bit,这就是一般说的AES-128,AES-192,AES-256。咱们从几个方面来具体了解一下AES加密。网络
AES算法主要有四种操做处理,分别是轮密钥加层(Add Round Key)、字节代换层(SubBytes)、行位移层(Shift Rows)、列混淆层(Mix Columns)。AES加密的过程,并非明文和密钥简单运算一下。以AES128为例,它要执行10轮加密。实际的执行过程是这样的:函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static
void
Cipher(state_t* state,
const
uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key
AddRoundKey(0, state, RoundKey);
// There will be Nr rounds.
for
(round = 1; round < Nr; ++round)
{
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(round, state, RoundKey);
}
// The last round is given below.
SubBytes(state);
ShiftRows(state);
AddRoundKey(Nr, state, RoundKey);
}
|
SubBytes,字节混淆。AES处理的最小单位是8位无符号整数,正好能够对应伽罗瓦域GF(2^8)上的一个元素,混淆的方法是先计算每个字节在GF(2^8)的乘法逆元,目的是提供非线性变换。再对结果作一次仿射变换,目的是改变掉伽罗瓦域的结构。工具
由于GF(2^8)中的每个元素,均可以用多项式来表示:a7x^7 + a6x^6 + a5x^5 + ... + a1x + a0,其中a7-a0,只能从GF(2)中取值。因此对某个字节计算逆元,能够转换成求多项式的逆元。以0xac为例,写成二进制是10101100,写成多项式就是x^7 + x^5 + x^3 + x^2,计算逆元就变成计算这个多项式的逆元。获得多项式逆元之后,咱们能够把它转换成16进制的值。仿射变换简单理解,就是经过某个计算,获得一个新的值。具体的计算方法,咱们就很少介绍了。实际中,能够用查表法来完成这一步骤。性能
由于这些步骤获得的每一个字节的值是固定对,因此咱们能够计算出GF(2^8)中每一个元素对应的值,最后咱们能够获得了一张16*16的查找表,叫作Substitution-box,简称sbox或者s盒。对于每一个输入的字节,例如输入字节的十六进制形式位0xAB,则在表格的纵坐标中定位A,再在纵坐标中定位B,最后使用s(a,b)的值来替换这个字节的值。经过这个步骤,咱们的输入数据D1-D16,变成了S1-S16。
将16个S盒变换后的字节,从上往下、从左到右地写成了一个矩阵。第一行保持不动,第二行向左移动1格,第三行向左移动2格,第四行向左滑移动3格,如图所示:
用下面这个矩阵和ShiftRows以后的结果相乘。列混淆操做混淆了输入矩阵的每一列,使输入的每一个字节都会影响到4个输出字节。这部分包含了矩阵乘法,伽罗瓦域内加法和乘法的相关知识。总的来讲,ShiftRows和MixColumns两步骤为这个密码系统提供了扩散性。
左边矩阵中的01,02,03对应的多项式分别是1,x,x+1。假设右边矩阵输入所有是0x11,对应的多项式是x^4+1。使用GF(2^8)中的加法和乘法,计算过程以下:
1
2
3
4
|
C(i) = (01 + 01 + 02 + 03) * 11
=(1 + 1 + x + x + 1) * (x^4 + 1)
= 1 * (x^4+1)
= x^4 + 1
|
乘法结果有两种状况:
在上面的几个步骤中,咱们是对输入对数据进行混淆。AddRoundKey每执行一次叫作一轮加密,这一步会执行屡次。简单来讲就是把密钥和混淆后的结果进行xor运算,但在每一轮使用的密钥都是根据上一轮的密钥变换而来的。
轮密钥是如何生成的?以AES-128为例,密钥一共16个字节,咱们把它们4字节分红一组,计做W0-W3。在每一轮中,经过函数g变换和固定的公式,能够获得这一轮的密钥。一共生成44个W(i),每4个为一组一共生成11个密钥k0-k10,其中k0是原始密钥。轮数依赖于密钥长度,16字节密钥对应10轮,24字节密钥对应12轮,32字节对应14轮。具体的计算过程咱们就再也不描述了,看起来简单,实际上背后有大量的数学知识和研究做为支撑。
对于解密,就是把加密的过程反过来,以下面的参考代码,其中的InvShiftRows,InvSubBytes,InvMixColumns分别是对应算法的逆运算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static
void
InvCipher(state_t* state,
const
uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key
AddRoundKey(Nr, state, RoundKey);
// There will be Nr rounds.
for
(round = (Nr - 1); round > 0; --round)
{
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(round, state, RoundKey);
InvMixColumns(state);
}
// The last round is given below.
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(0, state, RoundKey);
}
|
日常工做中,咱们是不推荐本身手写这类成熟专业的加密算法的,很容易写出漏洞或者错误,最终形成严重的安全问题。
AES只能对固定长度的数据进行加密,对于不定长的数据,咱们须要把它切分红若干定长的数据,再进行加密解密,这就是咱们常说的分组加密。分组加密有ECB,CBC,CFB,OFB这几种加密模式,咱们介绍一下ECB模式和CBC模式,以及Padding的对加密解密的影响。
ECB模式
ECB模式又称电子密码本模式(Electronic codebook):ECB是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分红若干块,以后将每块使用相同的密钥单独加密,解密同理。具体见下图:
(图片来自维基百科)
ECB模式因为每块数据的加密是独立的,因此能够分块进行并行加密或者解密。它的缺点是相同的明文块会被加密成相同的密文块,因此这种方法在某些条件下安全性相对不是很高。
CBC模式
CBC模式又称密码分组连接(Cipher-block chaining):CBC模式对于每一个待加密的密码块在加密前会先与前一个密码块的密文异或而后再用加密器加密,第一个明文块与一个叫初始化向量IV的数据块异或。具体见下图:
(图片来自维基百科)
完成加密或解密后会更新初始化向量IV,CBC模式安全性更高,但因为对每一个数据块的加密依赖前一个数据块的加密,因此加密是没法并行的。
用一张图来比较看一下ECB和CBC加密的效果,能够发现使用CBC模式分散性和安全性更好。
(图片来自网络)
显然,不一样的padding对加密与解密是有影响的,因此在加密和解密的时候须要保证padding的方式是一致的。
理论上大部分的算法都是能够破解的,只是有可能须要很长时间的计算才能破解。AES加密算法在目前的计算能力下,直接破解几乎是不可能的。从咱们保护模型的目的来看,使用AES-128就能够知足咱们的需求。另外,在AES算法被标准化后,不少硬件芯片和软件工具都实现了对AES的支持,使用AES算法来加密解密,性能也很是高。综合考虑了性能和安全性,咱们使用了AES-128/CBC算法来作加密,并在这个基础上结合MD5摘要算法,对文件的完整性作校验。
咱们从如下几个方面来看看AOE的加密组件是如何实现的:
AOE的加密文件结构
咱们设计了一个新的文件结构,加密后的文件结构以下:
1byte | 4bytes | 16bytes | nbytes |
---|---|---|---|
Version Code | File Length | File MD5 | 加密后的模型文件数据 |
咱们在加密后的模型数据前,增长了一个head,head一共21个byte:
AOE初版的加密算法(Version Code为1),加密和解密均可以在本地完成,不须要和服务器进行交互,对应的加密解密过程以下:
AOE的加密过程
1,采用AES-128/CBC/No Padding对模型加密。
2,给文件加上21字节的文件头,Version Code + File Length + File MD5。
3,使用文件MD5和加密后的模型作简单的Swap操做,把MD5的16个byte,分别和模型加密后的前16k数据的第一个字节进行交换。
使用AES方式加密,咱们面临一个问题,密钥很容易泄漏。为了解决这个问题,首先咱们选择了CBC模式,其次咱们对加密后对文件作了一下混淆。这样即便别人知道了咱们的AES KEY,若是不知道咱们加密组件里的混淆方式,由于是CBC模式加密,因此他也是没法解开咱们加密后的文件的。
AOE的解密过程
解密的过程和加密的过程是相反的,具体的算法以下:
1,读取加密文件的前21个字节,获得Version Code,文件长度。
2,读取加密数据的前16k数据的第一个字节,和head里的MD5字段进行swap,通过这一步之后,能够获得文件的MD5和原始的加密后的数据。
3,采用AES-128/CBC/No Padding对模型解密,获得解密文件之后使用文件MD5来检验文件的完整性。
AOE加密组件的使用
AOE加密组件,提供了C版本和JNI封装,JAVA版本和Python版本,在端上咱们更推荐使用C版本,在服务器后台咱们推荐使用JAVA版本或者Python版原本作一些批量的工做。咱们提供了解密到内存和文件两种方式,咱们更推荐直接解密到内存里,这样不会生成临时文件,安全性更高。
密码学对大部分人来讲是很是专业的,须要大量的数学知识,加密和破解也一直是矛和盾的关系。目前AOE SDK站在成熟算法的肩膀上,结合了AES算法对模型进行了加密,后续咱们还会扩展一些新的加密算法,给你们参考和使用。欢迎你们来使用和提建议。
github.com/didi/aoe
(AoE (AI on Edge,终端智能,边缘计算) 是一个终端侧AI集成运行时环境 (IRE),帮助开发者提高效率)
欢迎添加小助手微信进入AOE开源交流群!