AES 加密流程概述

401 阅读6分钟

1.概念

  • AES(Advanced Encryption Standard)高级加密标准,是由两位比利时密码学家Joan Daemen和Vincent Rijmen开发的 Rijndael分组密码的变体。
    AES选择了Rijndael系列的三个成员。其加密的块大小都是128bit但密钥长度分别是128bit(16bytes),192bit(24bytes),256bit(32bytes)三种长度;密钥的长度实际并不影响整体的加密流程,只是在扩展密钥(生成每轮加密的密钥)以及加密的轮数上有所改变。
  • AES是一种对称加密的方法既加解密共用同一密钥
  • AES加密基本原理是将原文分组(分成大小为128bit的块,可以看做是4X4bytes的矩阵数组)然后根据生成的密钥按规定次数重复对每块内容进行替换,移位,混淆等操作。由于对每个块的操作是独立的,因此AES加密有如下特性:AES.encrypt(str1)+AES.encrypt(str2)=AES.encrypt(str1+str2)
    当然这只针对对块没有关联的情况下(AES.mode.ECB 模型),像CBC这种当前块加密前需要与前一个块加密结果先进行异或,如果需要达成像ECB模型那样的结合律就需要考虑更新下一个密文子串加密的iv值了。像下面这样:
from Crypto.Cipher import AES  
s=b"mpaosfnmsoiahjsdgfkaskjfhjsa;lfsfsdfkjsjsdsfjssdsbgsjgsssbjshgshdsadadsdaddhdhdhdhsdflsdhfskajijsssaksssassshsjd"
iv=b"skajshsjshsgshsj"
cipher = AES.new(b"46cc793c53dc451bshagshaj",AES.MODE_CBC,iv=iv)
data = cipher.encrypt(s[:32])
#这里取第一次加密结果的后16位作为下一次加密的iv值,这与CBC模型的处理方法有关下面会讲到
cipher = AES.new(b"46cc793c53dc451bshagshaj",AES.MODE_CBC,iv=data[-16:])
data1=cipher.encrypt(s[32:])
res1=bytes([*data,*data1])
cipher = AES.new(b"46cc793c53dc451bshagshaj",AES.MODE_CBC,iv=iv)
res2 = cipher.encrypt(s)
print(res1==res2)

2.AES几种常见的加密模式:

  • ECB:将明文分成若干小块,分别对块进行加密

image.png

  • CBC:将明文分成若干小块,加密时先与上一块加密得到的密文进行异或,再进行加密,得到当前块结果(这也是上文为何需要将第一次加密结果的后16位当作第二次加密的IV的原因了)

image.png

  • OFB:将明文分成若干小块,重复将IV加密并将当前块的明文与之异或,得到当前块加密结果

image.png

  • CFB:将明文分成若干小块,将上一次加密的结果进行加密与当前块异或,得到当前块加密结果

image.png

  • CTR:将明文分成若干小块,将一个自增的算子进行加密,然后将当前块与算子加密的结果异或得到当前块加密结果。

ctr算子(16bytes)的组成,其中15-8bytes由用户提供或者随机生成,7-0bytes为计数器一个计数器,每加密一个块计数器加一:
e.g: [0x70,0x26,0xea,0x89,0x90,0x34,0x22,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01]

image.png

3.加密原理

注:

  • AES加密过程中将明文数据以4x4数组的形式进行分块,然后对块进行shiftrow,mixcolumn的操作。数组与原始数据的对应关系:

原始数据(bytes array):

[a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31...]

对应数组:

  |a0,a4,a8 ,a12|   |a16,a20,a24,a28|  
  |a1,a5,a9 ,a13|   |a17,a21,a25,a29|  
  |a2,a6,a10,a14|   |a18,a22,a26,a30|  
  |a3,a7,a11,a15|,  |a19,a23,a27,a31|  ...
  • AES密钥长度128 192 256位 分别对应加密轮数11,13,15轮

加密基本可以分为扩展密钥(KeyExpansion),行移位(ShiftRows),列混淆(MixColumns),字节替换(SubBytes),与当前密钥异或(AddRoundKey)五步

  • 扩展密钥(KeyExpansion):

    根据提供的初始密钥生成每轮加密的密钥,初始密钥作为第一轮加密的密钥 密钥是4字节一组(1words/N)则一轮密钥就会有4words(4x4 16bytes)。对于密钥长度128bit需要加密11轮因此需要生成10个密钥(40words) 首先我们定义:

    1. N为初始密钥长度(以words为单位),AES-128(4N),AES-192(6N),AES-256(8N)
    2. K[0]-K[N-1]可以看作是初始密钥以words划分形成的数组
    3. R为需要加密的轮数
    4. W[0]-W[4*R-1]可以看作是初始密钥经扩展后形成的以words划分的密钥数组
    5. RotWord(word)定义为1字节左循环移位[1,2,3,4]->[2,3,4,1]
    6. SubWord(word)定义为一次对1word根据s_box进行替换SubWord([1,2,3,4])=[SubByte(1),SubByte(2),SubByte(3),SubByte(4)]
    7. rcon[i]密钥扩展的第i轮的轮常数是大小是4bytes 其生成规则如下:
        #rcon 数组如下
        rcon[i]=[rci,0x00,0x00,0x00]
        #关于rci的生成规则如下
        rci=1             #if i=1
        rci=2*rc(i-1)     #if i>1 and rc(i-1)<0x80
        rci=2*rc(i-1)^0x11B #if i>1 and rc(i-1)>=0x80
    

    密钥的生成规则:

        对于i属于[0,4*R)
        W[i]=K[i]                        #i<N
        W[i-N] xor SubWord(RotWord(W[i-1])) xor rcon[i/N] #i>=N and i%N==0
        W[i-N] xor SubWord(W[i-1])       #i>=N and N>6 and i%N==4   
        W[i-N] xor W[i-1]                #otherwise
    

    附代码:

    def sub_bytes(byte_arr:list):
        for i in range(len(byte_arr)):byte_arr[i]= __s_box(byte_arr[i])
    
    def __get_rcon(num):
        if num==1:
            return 1
        else:
            pre_rci=__get_rcon(num-1)
            if pre_rci<0x80:
                return 2*pre_rci
            elif pre_rci>=0x80:
                return (2*pre_rci)^(0x11b)
                
    def __rotword(byte_arr:list,start):
        """
            @note   :left shift one position
        """
        t1=byte_arr[start]
        byte_arr[start],byte_arr[start+1],byte_arr[start+2],byte_arr[start+3]=\
        byte_arr[start+1],byte_arr[start+2],byte_arr[start+3],t1
    
    def key_expansion(key:bytes,N:int,R:int):
        w=[*key]
        for i in range(4*N,16*R,4):
            tmp=w[i-4:i]
            key_exp=[]
            pre=i-N*4
            r_time=i//4
            if r_time>= N and r_time%N==0:
                rcon=[__get_rcon(r_time//N),0x00,0x00,0x00]
                __rotword(tmp,0)
                sub_bytes(tmp)
                tmp=__xor(w[pre:pre+4],tmp)
                key_exp=__xor(tmp,rcon)
            elif r_time>=N and N>6 and r_time%4==0:
                sub_bytes(tmp)
                key_exp=__xor(w[pre:pre+4],tmp)
            else:
                key_exp=__xor(w[pre:pre+4],tmp)
    
            w.extend(key_exp)
    
        return w
    
    
  • 字节替换 (SubBytes):

Rijndael S-box
以byte为单位将原始数据在有限域GF(28)GF(2^8)上关于多项式x8+x4+x3+x+1x^8+x^4+x^3+x+1求乘法逆元,再将所求结果经以下仿射变换得到替换的值。

image.png

关于有限域求乘法逆元涉及到多项式四则运算以及Euclidean 扩展算法这里不再赘述。贴出一份求逆元的demo代码



def euclidean_extend(a,b):
    r0,r1=a,b
    s0,s1=1,0
    t0,t1=0,1
    r2=-1
    while r2!=0:
        q,r2=__div(r0,r1)
        s2=s0^__multi(q,s1)
        t2=t0^__multi(t1,q)
        r0,r1=r1,r2
        s0,s1=s1,s2
        t0,t1=t1,t2
    return s0

def __div(x,y):
    """
        Polynomial division
    """
    a=x#&0xff
    b=y#&0xff
    q=0
    while True:
        h_a=__get_MSB(a)
        h_b=__get_MSB(b)
        shift=h_a-h_b
        if shift<0 or a==0:
            return q,a

        q|=1<<shift
        a^=(b<<shift)
    


def __get_MSB(x):
    """
        get the most significant bit
    """
    for i in range(8,-1,-1):
        if x&(1<<i)>0:
            return i
    return 0


def __multi(a,b):
    """
        Polynomial multiplication
    """
    h_b=__get_MSB(b)
    q=0
    for i in range(h_b,-1,-1):
        if b&(1<<i)>0:
            q^=(a<<i)
    return q
# print(__div(0b100011011,0b1010011))
if __name__=='__main__':
    #x^8+x^4+x^3+x+1 上 2的逆元
    #计算与计算结果均取多项式系数
    res=euclidean_extend(0b10,0b100011011)
    print('{:08b}'.format(res))


当然除了自己算意外也可以使用已经计算出来的转换映射

S_BOX=[
    [0X63,0X7C,0X77,0X7B,0XF2,0X6B,0X6F,0XC5,0X30,0X01,0X67,0X2B,0XFE,0XD7,0XAB,0X76],
    [0XCA,0X82,0XC9,0X7D,0XFA,0X59,0X47,0XF0,0XAD,0XD4,0XA2,0XAF,0X9C,0XA4,0X72,0XC0],
    [0XB7,0XFD,0X93,0X26,0X36,0X3F,0XF7,0XCC,0X34,0XA5,0XE5,0XF1,0X71,0XD8,0X31,0X15],
    [0X04,0XC7,0X23,0XC3,0X18,0X96,0X05,0X9A,0X07,0X12,0X80,0XE2,0XEB,0X27,0XB2,0X75],
    [0X09,0X83,0X2C,0X1A,0X1B,0X6E,0X5A,0XA0,0X52,0X3B,0XD6,0XB3,0X29,0XE3,0X2F,0X84],
    [0X53,0XD1,0X00,0XED,0X20,0XFC,0XB1,0X5B,0X6A,0XCB,0XBE,0X39,0X4A,0X4C,0X58,0XCF],
    [0XD0,0XEF,0XAA,0XFB,0X43,0X4D,0X33,0X85,0X45,0XF9,0X02,0X7F,0X50,0X3C,0X9F,0XA8],
    [0X51,0XA3,0X40,0X8F,0X92,0X9D,0X38,0XF5,0XBC,0XB6,0XDA,0X21,0X10,0XFF,0XF3,0XD2],
    [0XCD,0X0C,0X13,0XEC,0X5F,0X97,0X44,0X17,0XC4,0XA7,0X7E,0X3D,0X64,0X5D,0X19,0X73],
    [0X60,0X81,0X4F,0XDC,0X22,0X2A,0X90,0X88,0X46,0XEE,0XB8,0X14,0XDE,0X5E,0X0B,0XDB],
    [0XE0,0X32,0X3A,0X0A,0X49,0X06,0X24,0X5C,0XC2,0XD3,0XAC,0X62,0X91,0X95,0XE4,0X79],
    [0XE7,0XC8,0X37,0X6D,0X8D,0XD5,0X4E,0XA9,0X6C,0X56,0XF4,0XEA,0X65,0X7A,0XAE,0X08],
    [0XBA,0X78,0X25,0X2E,0X1C,0XA6,0XB4,0XC6,0XE8,0XDD,0X74,0X1F,0X4B,0XBD,0X8B,0X8A],
    [0X70,0X3E,0XB5,0X66,0X48,0X03,0XF6,0X0E,0X61,0X35,0X57,0XB9,0X86,0XC1,0X1D,0X9E],
    [0XE1,0XF8,0X98,0X11,0X69,0XD9,0X8E,0X94,0X9B,0X1E,0X87,0XE9,0XCE,0X55,0X28,0XDF],
    [0X8C,0XA1,0X89,0X0D,0XBF,0XE6,0X42,0X68,0X41,0X99,0X2D,0X0F,0XB0,0X54,0XBB,0X16],
]
def __s_box(in_byte):
    """
    """
    return S_BOX[(in_byte&0xF0)>>4][in_byte&0x0F]
  • 行移位(ShiftRows):

对于4x4数组每一行循环向左移位,第一行不移位,第二行移一位,第三行向左移两位,第四行向左移三位。

def __shift_rows_core(byte_arr:list,start:int):
    """
        @note   :left shift each row  4x4 matrix(end-start>=16) 
        row1<<shift 0
        row2<<shift 1
        row3<<shift 2
        row4<<shift 3
    """
    t1=byte_arr[start+1]
    byte_arr[start+1],byte_arr[start+5],byte_arr[start+9],byte_arr[start+13]=\
    byte_arr[start+5],byte_arr[start+9],byte_arr[start+13],t1

    t1,t2=byte_arr[start+2],byte_arr[start+6]
    byte_arr[start+2],byte_arr[start+6],byte_arr[start+10],byte_arr[start+14]=\
    byte_arr[start+10],byte_arr[start+14],t1,t2

    t1,t2,t3=byte_arr[start+3],byte_arr[start+7],byte_arr[start+11]
    byte_arr[start+3],byte_arr[start+7],byte_arr[start+11],byte_arr[start+15]=\
    byte_arr[start+15],t1,t2,t3
  • 列混淆(MixColumns)

Rijndael MixColumns
也是在有限域GF(28)GF(2^8)上的操作。具体细节不再赘述

def __gmix_column(r:list):
    """
        @param r    : column of matrix
    """
    a=[0,0,0,0]
    b=[0,0,0,0]
    for c in range(0,4):
        a[c] = r[c]
        #/* h is 0xff if the high bit of r[c] is set, 0 otherwise */
        h = (r[c] >> 7)&1 #/* arithmetic right shift, thus shifting in either zeros or ones */
        b[c] = (r[c]<<1)&0xff #/* implicitly removes high bit because b[c] is an 8-bit char, so we xor by 0x1b and not 0x11b in the next line */
        b[c] ^= (h * 0x1B) #/* Rijndael's Galois field */
    r[0] = b[0] ^ a[3] ^ a[2] ^ b[1] ^ a[1]; #* 2 * a0 + a3 + a2 + 3 * a1 */
    r[1] = b[1] ^ a[0] ^ a[3] ^ b[2] ^ a[2]; #* 2 * a1 + a0 + a3 + 3 * a2 */
    r[2] = b[2] ^ a[1] ^ a[0] ^ b[3] ^ a[3]; #* 2 * a2 + a1 + a0 + 3 * a3 */
    r[3] = b[3] ^ a[2] ^ a[1] ^ b[0] ^ a[0]; #* 2 * a3 + a2 + a1 + 3 * a0 */
  • 与当前密钥异或(AddRoundKey)

将密钥与原文经过各种变换移位操作的结果进行异或处理

def add_round_key(byte_arr,key_arr,round):
    for i in range(0,len(byte_arr),16):
        for j in range(16):
            byte_arr[i+j]^=key_arr[round*16+j]

4.加密实现

以ECB模式以及密钥长度为128bit为例,加密前会先进行密钥扩展,128位密钥会加密11轮需要扩展40words的密钥。
第一轮加密只需要与密钥异或,
1-10轮加密也就是除了最后一轮加密的加密操作顺序是:

  1. 字节替换(SubBytes)
  2. 行移位(ShiftRows)
  3. 列混淆(MixColumns)
  4. 与当前轮密钥异或(AddRoundKey)

最后一轮加密顺序是:

  1. 字节替换(SubBytes)
  2. 行移位(ShiftRows)
  3. 与当前轮密钥异或(AddRoundKey)

也就是最后一轮加密不去做列混淆。

def encrypt_block(byte_arr,key_arr,en_round):
    add_round_key(byte_arr,key_arr,0)
    for i in range(1,en_round-1):
        sub_bytes(byte_arr)
        shift_rows(byte_arr)
        mix_columns(byte_arr)
        add_round_key(byte_arr,key_arr,i)
    sub_bytes(byte_arr)
    shift_rows(byte_arr)
    add_round_key(byte_arr,key_arr,en_round-1)
    return byte_arr
def encrypt_ECB(byte_arr,byte_key):
    key_len=len(byte_key)
    aes=_AES.get(key_len)
    if not aes:raise KeyLengthError(f"Key length dosent match:[{len(byte_key)}] except:[16,24,32]")
    en_round=aes['en_round']
    key_words=aes['key_words']
    byte_arr=list(byte_arr)
    key_arr=key_expansion(byte_key,key_words,en_round)
    res=encrypt_block(byte_arr,key_arr,en_round) 
    return bytes(res)

其余密钥长度处理也类似于此,其余加密模式的加密原理相同只是在密文加密后秘文的组织处理上有些许差别。

完整demo代码 github.com/thedrugsdon…