块加密概述/DES/AES/分组模式
块加密
所谓块加密就是每次加密一块明文与之对应的是流密码一般逐字节或者逐比特处理信息。
块加密常见的加密算法有
- IDEA 加密
- DES 加密
- AES 加密
块加密也是对称加密。
DES加密
DES(Data Encryption Standard)是目前最为流行的加密算法之一。DES是对称的,也就是说它使用同一个密钥来加密和解密数据。
从本质上来说,DES的安全性依赖于虚假表象,从密码学的术语来讲就是依赖于“混乱和扩散”的原则。混乱的目的是为隐藏任何明文同密文、或者密钥之间的关系,而扩散的目的是使明文中的有效位和密钥一起组成尽可能多的密文。两者结合到一起就使得安全性变得相对较高。
DES算法具体通过对明文进行一系列的排列和替换操作来将其加密。过程的关键就是从给定的初始密钥中得到16个子密钥的函数。要加密一组明文,每个子密钥按照顺序(1-16)以一系列的位操作施加于数据上,每个子密钥一次,一共重复16次。每一次迭代称之为一轮。要对密文进行解密可以采用同样的步骤,只是子密钥是按照逆向的顺序(16-1)对密文进行处理。
明文固定长度为64位(8字节)。密钥长度为64位,其中56位参与运算,其余8位为校验位。(8,16,24,32,40,48,56,64).加密得到64位密文。
初始/终止/P盒置换(Initial/Final permutation)
按照置换表进行置换,输入64位输出64位。如对于一64位的二进制数据,假设其第一位为0
,则将0
填入表中01
所在位置;假设其第五十位为1
,则将1
填入表中50
所在位置;以此类推得到置换后的数据。
假设对数据0x40004000000000c1
进行初始置换,先将其转化为二进制:0100000000000000010000000000000000000000000000000000000011000001
,发现其第2
,18
,57
, 58
,64
位为1
,将1
填入对应的初始置换表的位置,其余位置补0
即可得到置换后的数据。
扩展置换
经过初始置换后得到了64位数据,将这64位数据分为左32位数据
和右32位数据
,需要对右32位数据
执行扩展置换得到48位数据。
简而言之,将数据以4位为一组分为8组,将后一组第一位作为前一组最后一位(最后一组的最后一位作为第一组第一位),前一组最后一位作为后一组第一位(第一组第一位作为最后一组最后一位)最后合并得到48位数据。
S盒压缩处理
输入48位位数据,以6位为一组分为8组分别对应8个S盒。
抽出6位数据的首位两位转化为10进制为对应行号,将中间4位转化为10进制为对应列号,取对应S盒中对应的数作为改组数据压缩后的数据。最终将输入数据压缩为32位。
AES加密
密码学家和计算机科学家们继续为安全加密算法的设计做出不懈的努力。他们设计了多种安全可靠的加密算法。比较著名的有1977年1月由美国国家标准局公布的数据加密标准(Data Encryption Standard,DES)。DES的出现是现代密码学的标志之一,意味着密码学已经上升为数学和计算机科学的交叉学科。DES被使用了将近20年。为了适应现今的安全要求,1998年密码学家们推出了一系列替代 DES的新型算法,如MARS、RC6、Sperpent、Twofish等。2000年10月2日,美国国家标准和技术协会(National Institution of Standards and Technology,NIST)宣布,经过3年多的筛选和测评,比利时密码学家Daemen和Rijmen共同设计的Rijndael加密算法成为了新的加密标准。这个新的算法被命名为高级加密标准(Advanced Encryption Standard,AES)。现今,AES已经被广泛使用。密码学家们仍然没有发现AES的致命缺陷。我们可以认为,到现在为止,AES仍然是安全的加密算法。
明文固定长度为128位(16字节),密钥长度可以为128/192/256位(循环轮数不同,分别为10/12/14轮),加密输出结果为128位。
AES-128
加密部分操作
初始变换(Initial round)
将加密的信息填入4*4矩阵中,同4*4的密钥矩阵对应位置按位异或。
字节代换(SubBytes)
配合一个16*16的S-box食用。假设矩阵内数据为\(5f\),则将\(5f\)替换为S-box中第5航第f列中的数据。
行移位(ShiftRows)
- 第一行不变
- 第二行向左移动一位
- 第三行向左移动两位
- 第四行向左移动三行
列混合(MixColumns)
将数据矩阵左乘一个固定的4*4矩阵。这里矩阵运算的是基于有限域\(GF(2^8)\)上的,其中涉及到的乘法与加法并不是普通意义上的乘法与加法。由于本人暂时对数论方面知识的缺失,这里将其公式化以便理解。
以计算\(a_{11}\)为例:
这里将矩阵的加法替换为了异或操作。
乘法时分以下几类:
- \(0x01*(i_0\,i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7)=(i_0\,i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7)\)
- \(\begin{equation} 0x02*(i_0\,i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7)=\left\{ \begin{aligned} (i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7\,0) & , & i_0=0 \\ (i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7\,0)\oplus(00011011) & , & i_0=1 \end{aligned} \right. \end{equation}\)
- \(0x03*(i_0\,i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7)=[0x02*(i_0\,i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7)]\oplus (i_0\,i_1\,i_2\,i_3\,i_4\,i_5\,i_6\,i_7)\)
轮密钥加(AddRoundKey)
将数据矩阵同该轮的4*4子秘钥密钥矩阵对应位置按位异或。
密钥扩展
将密钥按列分组
-
如果i不是4的倍数,则\(W_i=W_{i-4} \oplus W_{i-1}\)(对应位置按位异或)
-
如果i是4的倍数,则\(W_i=W_{i-4} \oplus T(W_{i-1})\)
\(T(\ )\)函数由三部分组成:字循环,字节代换,轮常量异或
字循环
将四个字节循环左移一位
字节代换
将字循环后的结果同S-box进行数据代换。
轮常量异或
将字节代换后的结果同给定的轮常量异或后得到的结果即为\(T(\ )\)函数的结果。
分组模式
ECB电码本模式(Electronic Codebook)
2016 ABCTF aes-mess-75
题目描述:
We encrypted a flag with AES-ECB encryption using a secret key, and got the hash: e220eb994c8fc16388dbd60a969d4953f042fc0bce25dbef573cf522636a1ba3fafa1a7c21ff824a5824c5dc4a376e75 However, we lost our plaintext flag and also lost our key and we can't seem to decrypt the hash back :(. Luckily we encrypted a bunch of other flags with the same key. Can you recover the lost flag using this?
[HINT] There has to be some way to work backwards, right?
另附.txt文件一份。内有2000个明文以及其加密结果:
由题可知,对flag的加密方式为AES加密,采用ECB的分组方式,会以16字节为一组将flag分组,然后执行AES加密。对于相同的16字节内容,其加密结果是相同的,相同的密文其对应的明文也是相同的。则将此题给出的密文e220eb994c8fc16388dbd60a969d4953f042fc0bce25dbef573cf522636a1ba3fafa1a7c21ff824a5824c5dc4a376e75
以32个字符为一组分割(32位hex=16 bytes)然后去.txt文件中比对即可。
e220eb994c8fc16388dbd60a969d4953
f042fc0bce25dbef573cf522636a1ba3
fafa1a7c21ff824a5824c5dc4a376e75
abctf{looks_like
_you_can_break_a
es}
(很明显最后一块加密时填充了数据以完成AES加密)
CBC密码分组链接模式(Cipher Block Chaining)
字节反转攻击
例题:Flipping Cookie (cryptohack.org)
from Crypto.Cipher import AES
import os
from Crypto.Util.Padding import pad, unpad
from datetime import datetime, timedelta
KEY = ?
FLAG = ?
@chal.route('/flipping_cookie/check_admin/<cookie>/<iv>/')
def check_admin(cookie, iv):
cookie = bytes.fromhex(cookie)
iv = bytes.fromhex(iv)
try:
cipher = AES.new(KEY, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(cookie)
unpadded = unpad(decrypted, 16)
except ValueError as e:
return {"error": str(e)}
if b"admin=True" in unpadded.split(b";"):
return {"flag": FLAG}
else:
return {"error": "Only admin can read the flag"}
@chal.route('/flipping_cookie/get_cookie/')
def get_cookie():
expires_at = (datetime.today() + timedelta(days=1)).strftime("%s")
cookie = f"admin=False;expiry={expires_at}".encode()
iv = os.urandom(16)
padded = pad(cookie, 16)
cipher = AES.new(KEY, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(padded)
ciphertext = iv.hex() + encrypted.hex()
return {"cookie": ciphertext}
题目给出了两个函数check_admin(cookie, iv)
和get_cookie()
。
第一个函数将输入的cookie
和IV
进行AES-BCB模式解密
,当解密得到的字符串满足以;
为分割后存在admin=True
的字符串的时候就会把flag输出出来。
if b"admin=True" in unpadded.split(b";"):
return {"flag": FLAG}
第二个函数会将 cookie
这个字符串和一个IV
经过AES-BCB模式加密
后的结果同加密所用到的IV
输出出来。
expires_at = (datetime.today() + timedelta(days=1)).strftime("%s")
cookie = f"admin=False;expiry={expires_at}".encode()
ciphertext = iv.hex() + encrypted.hex()
return {"cookie": ciphertext}
我们可以通过构造新的IV
同原来的密文进行解密从而更改解密后的内容最终拿到flag。
由上面的推导:\(IV\_new=IV\oplus New\oplus Old\),所以新的IV应该为:
from Crypto.Util.number import bytes_to_long, long_to_bytes
old = bytes_to_long(b'admin=False;expi')
new = bytes_to_long(b'admin=True;00000')
iv = 0x5737b90e7e9b8a03c53337b0121645a4
print('iv_new\t'+hex(iv^old^new))
#iv_new 0x5737b90e7e9b9810dc2569bb475e05fd
得到flag:
本题需要修改的内容刚好在第一组里,如果修改内容在其他组,如第二组则需要构造新的第一组的密文,构造规则与推导相同:
\(新的密文=前一组的密文\oplus 原来的明文\oplus 新的明文\),只不过如果有必要的话可能需要同时构造新的IV
,使得新的密文同新的IV
解密结果同以前相同。