AES全称Advanced Encryption Standard,是DES算法的替代者,也是当今最流行的对称加密算法之一。
对比:
-
与摘要加密算法(MD5、SHA)对比
-
AES算法是属于真正意义上的解密算法,不同于MD5、SHA这些不可逆的摘要加密算法。
区别:
- 摘要加密算法是不可逆的,主要作用是对信息一致性和完整性的校验。
- 而对称加密算法是可逆的,主要作用是保证私密信息不被泄露。
-
-
与非对称加密算法(RSA)对比:
-
对称加密算法
- 特性:加密和解密采用相同密钥 优点:算法公开、计算量小、加密速度快、加密效率高 缺点:密钥的管理(密文的传输无论是速率还是安全性都不是问题,但攻击者一旦获得密钥便可由对称加密算法的优点:算法公开、计算量小解密获得明文,导致每次传输必须使用其他人无法获取的唯一密钥,这会使得发收信双方所拥有的密钥数量呈几何级数增长,密钥的数量和密钥的传输是密钥的管理的两大痛点)
-
非对称
- 特性:公钥和密钥的加密解密的映射性 优点:安全性高、不可抵赖性、简洁性(体现在摘要上,这里略去) 缺点:加密解密的速度慢、密钥对的获取难度、非绝对安全(公钥的可冒充性:验证签名的公钥必须是真正属于发送者,第三方的冒充公钥会使得通信产生难以预估的安全威胁)
-
注意
- AES加解密必须使用的是同一种填充方式和工作模式
- AES密钥长度越长,安全性越高,性能越低
- 我们在调用封装好的AES算法时,表面上使用的Key并不是真正用于AES加密解密的密钥,而是用于生成真正密钥的“种子”。
- 填充明文时,如果明文长度原本就是16字节的整数倍,那么除了NoPadding以外,其他的填充方式都会填充一组额外的16字节明文块。
基本概念
密钥
密钥是AES算法实现加密和解密的根本。对称加密算法之所以对称,是因为这类算法对明文的加密和解密需要使用同一个密钥(而非对称加密比如RSA,加解密使用的是两个不同的密钥)。
AES支持三种长度的密钥:128位,192位,256位
平时所说的AES128,AES192,AES256,实际上就是指的AES算法对不同长度密钥的使用。
| AES方案 | 密钥长度(32位比特字) | 分组长度(32位比特字) | 加密轮数 |
|---|---|---|---|
| AES-128 | 4 | 4 | 10 |
| AES-192 | 6 | 4 | 12 |
| AES-256 | 8 | 4 | 14 |
区别:密钥越长,需要加密处理的轮数越多,安全性越高,性能越低。
填充
要想了解填充的概念,我们先要了解AES的分组加密特性。
什么是分组加密呢?我们来看看下面这张图:
AES算法在对明文加密的时候,并不是把整个明文一股脑加密成一整段密文,而是把明文拆分成一个个独立的明文块,每一个明文块长度128bit。 这些明文块经过AES加密器的复杂处理,生成一个个独立的密文块,这些密文块拼接在一起,就是最终的AES加密结果。
但是这里涉及到一个问题: 假如一段明文长度是192bit,如果按每128bit一个明文块来拆分的话,第二个明文块只有64bit,不足128bit。这时候怎么办呢?就需要对明文块进行填充(Padding)。
典型的填充方式:
-
NoPadding:不做任何填充,但是要求明文必须是16字节的整数倍。
-
PKCS5Padding(默认):如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字符,且每个字节的值等于缺少的字符数。
比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个字节,则补全为{1,2,3,4,5,a,b,c,d,e,6,6,6,6,6,6}
-
PKCS7Padding:缺n个,则填充n个二进制数n。对于AES来说PKCS5Padding和PKCS7Padding是完全一样的,不同在于PKCS5限定了块大小为8bytes而PKCS7没有限定。因此对于AES来说两者完全相同,但是对于Rijndael就不一样了。AES是Rijndael在块大小为8bytes时的特例,对于使用其他信息块大小的Rijndael算法只能使用PKCS7。(在AES加密当中严格来说是不能使用pkcs5的,因为AES的块大小是16bytes而pkcs5只能用于8bytes,通常我们在AES加密中所说的pkcs5指的就是pkcs7){1,2,3,4,5,a,b,c,d,e,3,3,3}
-
ISO10126Padding:如果明文块少于16个字节(128bit),在明文块末尾补足相应数量的字节,最后一个字符值等于缺少的字符数,其他字符填充随机数。
比如明文:{1,2,3,4,5,a,b,c,d,e},缺少6个字节,则可能补全为{1,2,3,4,5,a,b,c,d,e,5,c,3,G,$,6}
-
ZerosPadding:全部填充0x00,无论缺多少全部填充0x00,已经是128bits倍数仍要填充。
注意:AES加密的时候使用了某一种填充方式,解密的时候必须也采用同样的填充方式。
模式
注意:OCB是迄今为止最好的模式,因为它允许在一次通过中进行加密和验证。但是在美国有专利,所以不在此介绍。
AES有多种工作模式,主要有以下模式:
ECB
-
电码本模式(Electronic Codebook Book (ECB))
-
ECB是最简单的工作模式,在该模式下,每一个明文块的加密都是完全独立,互不干涉的。
-
优缺点
- 好处:简单、有利于并行计算
- 缺点:相同的明文块经过加密会变成相同的密文块,因此安全性较差。
CBC
-
密码分组链接模式(Cipher Block Chaining (CBC))
-
CBC模式(Cipher Block Chaining)引入了一个新的概念:初始向量IV(Initialization Vector)。
它的作用和MD5的“加盐”有些类似,目的是防止同样的明文块始终加密成同样的密文块。
-
流程图:
从图中可以看出,CBC模式在每一个明文块加密前会让明文块和一个值先做异或操作。IV作为初始化变量,参与第一个明文块的异或,后续的每一个明文块和它前一个明文块所加密出的密文块相异或。
这种模式是先将明文切分成若干小段,然后每一小段与初始块IV或者上一段的密文段进行异或运算后,再与密钥进行加密。这样以来,相同的明文块加密出的密文块显然是不一样的。
-
优缺点:
- 好处:安全性更高
- 坏处:无法并行计算,性能上不如ECB;引入初始化向量IV,增加复杂度。
CFB
-
密码反馈模式(Cipher FeedBack (CFB));
-
这种模式较复杂。
-
优缺点
-
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
-
缺点:
1.不利于并行计算;
2.误差传送:一个明文单元损坏影响多个单元;
3.唯一的IV;
-
OFB
-
输出反馈模式(Output FeedBack (OFB))
-
这种模式较复杂。
-
优缺点:
-
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
-
缺点:
1.不利于并行计算;
2.对明文的主动攻击是可能的;
3.误差传送:一个明文单元损坏影响多个单元;
-
CTR
-
计算器模式(Counter (CTR))
-
计算器模式不常见,在CTR模式中, 有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。CTR的示意图如下所示:
-
如果使用CTR,则必须为每条消息使用不同的IV,否则最终攻击者可以获取两个密文并获得未加密的明文组合。原因是CTR模式实质上将块密码转换为流密码,流密码的第一个规则是永远不要使用相同的Key + IV两次。
GCM
- CTR
OBC
- 有专利限制,不展开
注意:所有工作模式差别体现在宏观上,即明文块与明文块之间的关联。AES加密器的内部处理流程都是一样的。
AES加密的时候使用了某一种工作模式,解密的时候必须也采用同样的工作模式。
具体加密流程
AES加密大体流程图:
解释:
- 把明文按照128bit拆分成若干个明文块
- 按照所选填充方式来填充最后一个明文块(如果明文长度原本就是16字节的整数倍,那么除了NoPadding以外,其他的填充方式都会填充一组额外的16字节明文块。)
- 每一个明文块利用AES加密器和密钥,加密成密文块
- 拼接所有密文块,成为最终的密文结果
AES加密器具体详情图:
具体名词会在后面用到的地方作详细解释。
AES加密不是一次把明文变成密文,而是先后经过很多轮加密。具体分为:
| 初始轮 | 1次 | |
|---|---|---|
| 普通轮 | N次 | AES128:9轮,AES192:11轮,AES256:13轮 |
| 最终轮 | 1次 |
不同阶段的Round有不同的处理步骤。
-
初始轮只有一个步骤:
- 加轮密钥(AddRoundKey)
-
普通轮有四个步骤:
- 字节代替(SubBytes)
- 行移位(ShiftRows)
- 列混淆(MixColumns)
- 加轮密钥(AddRoundKey)
-
最终轮有三个步骤:
- 字节代替(SubBytes)
- 行移位(ShiftRows)
- 加轮密钥(AddRoundKey)
字节代替(SubBytes)
简单叙述: 把明文块相关位置的内容用S盒相关位置的内容替代
前面说AES会将明文分成若干个16字节的明文块,划分后的明文块在每个步骤中都被排列成一个4X4的二维数组。
所谓字节替代,就是把明文块的每一个字节都替代成另外一个字节。替代的依据是一个被称为S盒(Subtitution Box)的16X16大小的二维常量数组。
S盒:
假设明文块当中a[2,2] = 5B(一个字节是两位16进制),那么输出值b[2,2] = S[5][B] = 39(解密的时候同样可以通过找到S盒中39所在位置,其下标为5B,即为初始值)
行移位(ShiftRows)
简单叙述: 将字节替换后的明文块的第i行循环左移i位(0 <= i <= 3)
这一步很简单,就像图中所描述的:
- 第一行不变
- 第二行循环左移1个字节
- 第三行循环左移2个字节
- 第四行循环左移3个字节
列混淆(MixColumns)
简单叙述: 将修补矩阵与行移位后的明文块相乘得到混淆后的状态矩阵。(注意:矩阵元素的乘法和加法都是定义在基于GF(2^8)上的二元运算,并不是通常意义上的乘法和加法。 )
这一步,输入数组的每一列要和一个名为修补矩阵(fixed matrix)的二维常量数组做矩阵相乘,得到对应的输出列。【举例:b[0][1] = (a[0][0] * M[0][1]) xor (a[0][1] * M[1][1]) xor (a[0][2] * M[2][1]) xor (a[0][3] * M[3][1]) ,M为修补矩阵,xor为异或操作】
修补矩阵:
计算公式:
计算方式详情:AES加密算法之列混合变换白马长枪儒雅将的博客-CSDN博客aes列混合
加轮密钥(AddRoundKey)
简单叙述: 轮密钥加操作是将128位轮密钥Ki同状态矩阵(行移位或者列混淆的结果矩阵)中的数据进行逐位异或操作。
这一步是唯一利用到密钥的一步,128bit的密钥也同样被排列成4X4的矩阵。
让输入数组的每一个字节a[i,j]与密钥对应位置的字节k[i,j]异或一次,就生成了输出值b[i,j]。
需要补充一点,加密的每一轮所用到的密钥并不是相同的。这里涉及到一个概念:扩展密钥(KeyExpansions)。
扩展密钥(KeyExpansions):
AES源代码中用长度 4 * 4 *(10+1) 字节的数组W来存储所有轮的密钥。W{0-15}的值等同于原始密钥的值,用于为初始轮做处理。
后续每一个元素W[i]都是由W[i-4]和W[i-1]计算而来,直到数组W的所有元素都赋值完成。
具体流程图:
AES首先将初始密钥输入到一个4*4的状态矩阵中,如上图所示。
这个4*4矩阵的每一列的4个字节组成一个字,矩阵4列的4个字依次命名为W[0]、W[1]、W[2]和W[3],它们构成一个以字为单位的数组W。例如,设密钥K为”abcdefghijklmnop”,则K0 = ‘a’,K1 = ‘b’, K2 = ‘c’,K3 = ‘d’,W[0] = “abcd”。 接着,对W数组扩充40个新列,构成总共44列的扩展密钥数组。新列以如下的递归方式产生:
-
如果i不是4的倍数,那么第i列由如下等式确定: W[i]=W[i-4]⨁W[i-1]
-
如果i是4的倍数,那么第i列由如下等式确定: W[i]=W[i-4]⨁T(W[i-1]) 其中,T是一个有点复杂的函数。 函数T由3部分组成:字循环、字节代换和轮常量异或,这3部分的作用分别如下。
-
字循环:将1个字中的4个字节循环左移1个字节。即将输入字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]。
-
字节代换:对字循环的结果使用S盒进行字节代换。
-
轮常量异或:将前两步的结果同轮常量Rcon[j]进行异或,其中j表示轮数。 轮常量Rcon[j]是一个字,其值见下表。
j 1 2 3 4 5 Rcon[j] 01000000 02000000 04000000 08000000 10000000 j 6 7 8 9 10 Rcon[j] 20000000 40000000 80000000 1B000000 36000000
-
示例:
设初始的128位密钥为:
3C A1 0B 21 57 F0 19 16 90 2E 13 80 AC C1 07 BD
那么4个初始值为:
W[0] = 3C A1 0B 21
W[1] = 57 F0 19 16
W[2] = 90 2E 13 80
W[3] = AC C1 07 BD
下面求扩展的第1轮的子密钥(W[4],W[5],W[6],W[7])。
由于4是4的倍数,所以:
W[4] = W[0] ⨁ T(W[3])
T(W[3])的计算步骤如下:
循环地将W[3]的元素移位:AC C1 07 BD变成C1 07 BD AC;
将 C1 07 BD AC 作为S盒的输入,输出为78 C5 7A 91;
将78 C5 7A 91与第一轮轮常量Rcon[1]进行异或运算,将得到79 C5 7A 91,因此,T(W[3])=79 C5 7A 91,故
W[4] = 3C A1 0B 21 ⨁ 79 C5 7A 91 = 45 64 71 B0
其余的3个子密钥段的计算如下:
W[5] = W[1] ⨁ W[4] = 57 F0 19 16 ⨁ 45 64 71 B0 = 12 94 68 A6
W[6] = W[2] ⨁ W[5] =90 2E 13 80 ⨁ 12 94 68 A6 = 82 BA 7B 26
W[7] = W[3] ⨁ W[6] = AC C1 07 BD ⨁ 82 BA 7B 26 = 2E 7B 7C 9B
所以,第一轮的密钥为 45 64 71 B0 12 94 68 A6 82 BA 7B 26 2E 7B 7C 9B
W数组当中,W{0-15}用于初始轮的处理,W{16-31}用于第1轮的处理,W{32-47}用于第2轮的处理 ……一直到W{160-175}用于最终轮(第10轮)的处理。
解密流程
加密流程倒置过来,顺序变为 最终轮—>普通轮—>初始轮。扩展密钥的使用顺序也和加密相反。
逆字节代换
逆字节代换就是查逆S盒来变换,逆S盒如下:
逆行移位
逆行移位即行移位的逆变换。 行移位的逆变换是将状态矩阵中的每一行执行相反的移位操作,例如AES-128中,状态矩阵的第0行右移0字节,第1行右移1字节,第2行右移2字节,第3行右移3字节。
逆列混合
逆列混合即列混合逆运算。公式:
加解密概要!
测试
基于pycryptodome密码库AES模块,利用timeit模块对ECB、OFB、CFB、CBC、GCM五种模式在不同密钥长度下进行性能测试。 加密数据data = 'secret datasadjasdoapjf wafj awjfwa么v哦怕v吗v到屏幕'.encode() 相关测试函数执行次数count=100000 测试结果数据:单位(秒)
-
不含base64编码解码性能损耗:
test_aes128_ECB 3.3816415 test_aes192_ECB 3.3811248999999997 test_aes256_ECB 3.135126799999999 test_aes128_OFB 3.8089359 test_aes192_OFB 4.018084600000002 test_aes256_OFB 3.997190400000001 test_aes128_CFB 4.436713000000001 test_aes192_CFB 4.482157100000002 test_aes256_CFB 4.7518030999999965 test_aes128_CBC 4.1842319 test_aes192_CBC 4.153839099999999 test_aes256_CBC 4.0824912 test_aes128_GCM 16.680618000000003 test_aes192_GCM 16.57688379999999 test_aes256_GCM 17.128724899999995 -
包含base64编码解码:
test_aes128_ECB 3.817687 test_aes192_ECB 3.5313921 test_aes256_ECB 3.4777353 test_aes128_OFB 4.327306 test_aes192_OFB 4.428335499999999 test_aes256_OFB 4.413377799999999 test_aes128_CFB 5.015321400000001 test_aes192_CFB 5.014899999999997 test_aes256_CFB 5.144274800000005 test_aes128_CBC 4.2879145000000065 test_aes192_CBC 4.250506799999997 test_aes256_CBC 4.360604699999996 test_aes128_GCM 17.045861699999996 test_aes192_GCM 17.04790270000001 test_aes256_GCM 17.686628900000002
根据结果可以发现:
- base64有性能损耗,在ECB模式下大概有10%的性能损耗,在其他模式下只有百分之几的性能损耗,所以用不用影响不大,用了安全性可能更好一点点。
- ECB性能最好,但不安全,遂不考虑。
- GCM是基于CTR模式的改进,但测试结果性能太低,也不考虑。
- 由CFB工作原理可知,它不是直接对明文加密,而是将iv加密然后将结果与明文进行异或操作得到密文
- OFB同上,OFB与CFB的区别仅仅在于密码算法的输入不一样。
- 综上,用CBC,并且CBC是对明文直接加密。
Python密码库pycryptodome源码AES部分解读
加密模式
AES模块提供了11种模式
MODE_ECB = 1
MODE_CBC = 2
MODE_CFB = 3
MODE_OFB = 5
MODE_CTR = 6
MODE_OPENPGP = 7
MODE_CCM = 8
MODE_EAX = 9
MODE_SIV = 10
MODE_GCM = 11
MODE_OCB = 12
# 每种模式对应一种创建函数
_modes = { 1:_create_ecb_cipher,
2:_create_cbc_cipher,
3:_create_cfb_cipher,
5:_create_ofb_cipher,
6:_create_ctr_cipher,
7:_create_openpgp_cipher,
9:_create_eax_cipher
}
_extra_modes = { 8:_create_ccm_cipher,
10:_create_siv_cipher,
11:_create_gcm_cipher,
12:_create_ocb_cipher
}
填充方法
加密时的填充方法pad源码:
def pad(data_to_pad, block_size, style='pkcs7'):
"""
data_to_pad:bytes类型的明文数据
block_size:将明文数据划分后的明文块的长度,传AES.block_size就行了,或者16
style:填充方式,一般使用默认的即可。他这里只支持pkcs7、iso7816、x923三种填充方式。pkcs7上面有解释,其他两种未了解。
"""
padding_len = block_size-len(data_to_pad)%block_size # 计算需要的填充长度
if style == 'pkcs7':
padding = bchr(padding_len)*padding_len # 计算填充部分
elif style == 'x923':
padding = bchr(0)*(padding_len-1) + bchr(padding_len)
elif style == 'iso7816':
padding = bchr(128) + bchr(0)*(padding_len-1)
else:
raise ValueError("Unknown padding style")
return data_to_pad + padding # 返回填充后内容
# 以pkcs7为例
padding = bchr(padding_len)*padding_len
bchr将传入的数字n转为bytes类型,然后填充n个
解密所需的方法unpad,用于移除填充的内容:
def unpad(padded_data, block_size, style='pkcs7'):
pdata_len = len(padded_data)
if pdata_len == 0:
raise ValueError("Zero-length input cannot be unpadded")
if pdata_len % block_size:
raise ValueError("Input data is not padded")
if style in ('pkcs7', 'x923'):
padding_len = bord(padded_data[-1])
if padding_len<1 or padding_len>min(block_size, pdata_len):
raise ValueError("Padding is incorrect.")
if style == 'pkcs7':
if padded_data[-padding_len:]!=bchr(padding_len)*padding_len:
raise ValueError("PKCS#7 padding is incorrect.")
else:
if padded_data[-padding_len:-1]!=bchr(0)*(padding_len-1):
raise ValueError("ANSI X.923 padding is incorrect.")
elif style == 'iso7816':
padding_len = pdata_len - padded_data.rfind(bchr(128))
if padding_len<1 or padding_len>min(block_size, pdata_len):
raise ValueError("Padding is incorrect.")
if padding_len>1 and padded_data[1-padding_len:]!=bchr(0)*(padding_len-1):
raise ValueError("ISO 7816-4 padding is incorrect.")
else:
raise ValueError("Unknown padding style")
return padded_data[:-padding_len]
生成密钥
AES模块提供了生成密钥的函数方法:from Crypto.Random import get_random_bytes,需要传入需要生成的密钥长度。密钥长度只支持16、24、32,单位:字节。同时如果该模式需要IV但没传入,也会通过这个函数生成一个长度为block_size字节的IV。
将明文按照16字节划分成明文块;密钥长度支持16、24、32字节
# Size of a data block (in bytes)
block_size = 16
# Size of a key (in bytes)
key_size = (16, 24, 32) # 长度不在这里面会抛出异常
创建对象
通过new函数创建一个AES对象,需要传入密钥和模式
def new(key, mode, *args, **kwargs):
kwargs["add_aes_modes"] = True
return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
def _create_cipher(factory, key, mode, *args, **kwargs):
kwargs["key"] = key
modes = dict(_modes)
if kwargs.pop("add_aes_modes", False): # 由上面的new函数可知恒为True
modes.update(_extra_modes) # 即把8-12四种模式也添加进来
if not mode in modes:
raise ValueError("Mode not supported")
if args:
if mode in (8, 9, 10, 11, 12):
if len(args) > 1:
raise TypeError("Too many arguments for this mode")
kwargs["nonce"] = args[0]
elif mode in (2, 3, 5, 7):
if len(args) > 1:
raise TypeError("Too many arguments for this mode")
kwargs["IV"] = args[0]
elif mode == 6:
if len(args) > 0:
raise TypeError("Too many arguments for this mode")
elif mode == 1:
# 只有ECB模式不需要额外变量
raise TypeError("IV is not meaningful for the ECB mode")
return modes[mode](factory, **kwargs)
# 以CBC为例,会返回一个CbcMode对象,其提供了加密和解密方法
def _create_cbc_cipher(factory, **kwargs):
"""Instantiate a cipher object that performs CBC encryption/decryption.
:Parameters:
factory : module
The underlying block cipher, a module from ``Crypto.Cipher``.
:Keywords:
iv : bytes/bytearray/memoryview
The IV to use for CBC.
IV : bytes/bytearray/memoryview
Alias for ``iv``.
Any other keyword will be passed to the underlying block cipher.
See the relevant documentation for details (at least ``key`` will need
to be present).
"""
cipher_state = factory._create_base_cipher(kwargs)
iv = kwargs.pop("IV", None)
IV = kwargs.pop("iv", None)
if (None, None) == (iv, IV):
iv = get_random_bytes(factory.block_size)
if iv is not None:
if IV is not None:
raise TypeError("You must either use 'iv' or 'IV', not both")
else:
iv = IV
if len(iv) != factory.block_size:
raise ValueError("Incorrect IV length (it must be %d bytes long)" %
factory.block_size)
if kwargs:
raise TypeError("Unknown parameters for CBC: %s" % str(kwargs))
return CbcMode(cipher_state, iv)
class CbcMode(object):
def __init__(self, block_cipher, iv):
self._state = VoidPointer()
result = raw_cbc_lib.CBC_start_operation(block_cipher.get(),
c_uint8_ptr(iv),
c_size_t(len(iv)),
self._state.address_of())
if result:
raise ValueError("Error %d while instantiating the CBC mode"
% result)
self._state = SmartPointer(self._state.get(),
raw_cbc_lib.CBC_stop_operation)
block_cipher.release()
self.block_size = len(iv)
self.iv = _copy_bytes(None, None, iv)
self.IV = self.iv
self._next = [ self.encrypt, self.decrypt ]
def encrypt(self, plaintext, output=None):
if self.encrypt not in self._next:
raise TypeError("encrypt() cannot be called after decrypt()")
self._next = [ self.encrypt ]
if output is None:
ciphertext = create_string_buffer(len(plaintext))
else:
ciphertext = output
if not is_writeable_buffer(output):
raise TypeError("output must be a bytearray or a writeable memoryview")
if len(plaintext) != len(output):
raise ValueError("output must have the same length as the input"
" (%d bytes)" % len(plaintext))
result = raw_cbc_lib.CBC_encrypt(self._state.get(),
c_uint8_ptr(plaintext),
c_uint8_ptr(ciphertext),
c_size_t(len(plaintext)))
if result:
if result == 3:
raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
raise ValueError("Error %d while encrypting in CBC mode" % result)
if output is None:
return get_raw_buffer(ciphertext)
else:
return None
def decrypt(self, ciphertext, output=None):
if self.decrypt not in self._next:
raise TypeError("decrypt() cannot be called after encrypt()")
self._next = [ self.decrypt ]
if output is None:
plaintext = create_string_buffer(len(ciphertext))
else:
plaintext = output
if not is_writeable_buffer(output):
raise TypeError("output must be a bytearray or a writeable memoryview")
if len(ciphertext) != len(output):
raise ValueError("output must have the same length as the input"
" (%d bytes)" % len(plaintext))
result = raw_cbc_lib.CBC_decrypt(self._state.get(),
c_uint8_ptr(ciphertext),
c_uint8_ptr(plaintext),
c_size_t(len(ciphertext)))
if result:
if result == 3:
raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
raise ValueError("Error %d while decrypting in CBC mode" % result)
if output is None:
return get_raw_buffer(plaintext)
else:
return None