AES-128-CBC-PKCS5PADDING 加解密实现

Copyright © 2018 Joyce_BY
All rights reserved.
Contact by [email protected]


本文目的及实验环境

1、实现128-bit的AES加解密过程
2、python3.7.0,windows10

原理及代码

整体原理图

AES-128-CBC-PKCS5PADDING 加解密实现_第1张图片
注意:最后一轮变换没有mixcolumn过程。
CODE

ef aes_encrypt(text,key): #ok
    # input text is a block
    state = create_block(text)
    # k0 xor plaintext 
    state = xor(state,create_block(key[0]))
    # 10 rounds
    for i in range(1,11): 
        state = aes_encrypt_round(i,state,create_block(key[i]))
    # return ciphertext in orginal order:
return create_block(state)
def aes_decrypt(text,key): #ok
    # input text is a block
    state = create_block(text)
    # k0 xor plaintext 
    state = xor(state,create_block(key[10])) 
    # 10 rounds 
    for i in range(1,11): 
        state = aes_decrypt_round(i,state,create_block(key[10-i]))
    # return plaintext
    return create_block(state)

 

每一轮的算法

(0)此处明文和密钥长度都为128比特,则根据AES算法知道应该加密10轮;
(1)数据分组
将一开始传入AES算法模块的数据,以BYTE为单位如下图顺序排列为4*4的矩阵

AES-128-CBC-PKCS5PADDING 加解密实现_第2张图片
编排函数如下

def create_block(message): #ok
    # arrange byte stream into a matrix
    # message is a list of hex values
    create = [] 
    seq = [0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15]
    # 16 bytes: 
    for i in range(16): 
        create.append(message[seq[i]])
    return create

 

AES加密轮的算法

(1)初始轮:第0轮,我们将明文矩阵与初始密钥k0做异或运算,结果传入第一轮加密模块。
第1-10轮:对第i轮按顺序执行字节替换、字节移位、列混淆(除第10轮)、轮密钥相加;
CODE

def aes_encrypt_round(i,state,roundkey): #ok
    # byte substitution 
    state = substitution(state,0)
    # byte rotation
    state = rotation(state,0) 
    # if not final round: mix column
    if i != 10:    
        state = mixcolumn(state,0)
    # round key xor
    state = xor(state,roundkey)
    return state
AES解密轮的算法

过程相同,顺序不同。
初始轮:第0轮,我们将明文矩阵与初始密钥k0做异或运算,结果传入第一轮加密模块。
第1-10轮:对第i轮按顺序执行字节移位、字节替换、轮密钥相加、列混淆(除第10轮);
CODE

def aes_decrypt_round(i,state, roundkey): #ok 
    # inv byte rotation
    state = rotation(state,1)
    # inv byte substitution
    state = substitution(state,1)
    # round key xor
    state = xor(state,roundkey)
    # if not first round: inv mix column
    if i != 10:
        state = mixcolumn(state,1) 
    return state

A、字节替代 Byte Substitution
非线性的字节替代,独立对每个字节进行运算,包括两个具体过程:
(a)对字节x在有限域GF(2^8)上求乘法逆,得到x-1,0x00的逆为其本身;
(b)在GF(2)上进行affine transform:y = Ax^-1+b,A为矩阵,b为向量,定值。
在实现的时候利用S盒打表实现,状态矩阵中的元素按照下面的方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,取出S盒中对应的行的元素作为输出。
解密通过逆S盒实现。

CODE

def substitution(state,mode): #ok
    sbox = [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]

    inv_sbox = [0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
                0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
                0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
                0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
                0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
                0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
                0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
                0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
                0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
                0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
                0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
                0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
                0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
                0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
                0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
                0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d]
    ans = []
    for i in range(len(state)): 
        # mode 0 for encryption, 1 for decryption:
        if mode == 0:
            ans.append(sbox[state[i]])
        else: 
            ans.append(inv_sbox[state[i]])
    return ans

 
B、字节移位 ByteRotation
如下图所示,第0-3行分别左移0-3个字节;
则解密过程将0-3行分别右移0-3个字节。
AES-128-CBC-PKCS5PADDING 加解密实现_第3张图片

CODE

def rotation(state,mode): #ok
    # shift left 0-3 bytes for each row
    seq = [0,1,2,3,5,6,7,4,10,11,8,9,15,12,13,14]
    # shift right 0-3 bytes for each row
    inv_seq = [0,1,2,3,7,4,5,6,10,11,8,9,13,14,15,12]
    ans = [] 
    for i in range(16): 
        # 0 for enc, 1 for dec:
        if mode == 0:
            ans.append(state[seq[i]])
        else: 
            ans.append(state[inv_seq[i]])
    return ans

 
C、列混淆 MixColumns
在第1-9轮(第10轮不做!),用(逆)混淆矩阵左乘状态矩阵,加密混合矩阵如下:
AES-128-CBC-PKCS5PADDING 加解密实现_第4张图片

解密逆混淆矩阵如下:
AES-128-CBC-PKCS5PADDING 加解密实现_第5张图片
CODE

def mixcolumn(state,mode): #ok
    mix = [ 0x02,0x03,0x01,0x01,
            0x01,0x02,0x03,0x01,
            0x01,0x01,0x02,0x03,
            0x03,0x01,0x01,0x02]
    inv_mix = [ 0x0E,0x0B,0x0D,0x09,
                0x09,0x0E,0x0B,0x0D,
                0x0D,0x09,0x0E,0x0B,
                0x0B,0x0D,0x09,0x0E]
    # for each col, do matrix multiplication: 
    if mode == 0: 
        ans = matmul(mix,state) 
    else: 
        ans = matmul(inv_mix,state) 
    return ans

 
D、轮密钥相加 AddRoundKey
将第i轮的密钥与C中得到的矩阵做异或,传入下一轮。这里是将两个矩阵对应的比特分别做异或。
CODE

# 矩阵异或
def xor(state,key): #ok
    # state xor key
    ans = []
    for i in range(len(key)):
        ans.append(state[i] ^ key[i])
    return ans 
10轮迭代之后

将一开始进行了转置的数据矩阵转置回来,输出数据流,即得到最终的密文/明文。

 

密钥调度

密钥长度

密钥总bit长度 = 分组长度 * (圈数+1),此实验计算得1408bits。
因为有10轮迭代,所以我们需要扩展获取另外10个轮密钥。

密钥扩展

将初始密钥进行编排(同明文数据矩阵编排方法),每一列作为一个单位,则可得到k0-k3。利用k(i-1)和k(i-4)得到k(i),如果i是4的倍数 ,则需要将k(i-1)先做变换T,再与k(i-4)相加,否则直接将两列异或相加。
T变换包含两个操作:S盒变换和左移一个字节。
Rcon是预先指定的一组值。
流程如图:
AES-128-CBC-PKCS5PADDING 加解密实现_第6张图片
AES-128-CBC-PKCS5PADDING 加解密实现_第7张图片

CODE

def key_expansion(init_key): #ok
    #key expansion
    # use each col of initial key to get 4 32-bit keys   
    w = []
    w.append(init_key)
    # expand 10 more groups:
    for group in range(10):
        nextg = []
        # every group has 4 cols:
        # first col in the group:
        trans = key_trans(w[group][12:16],group)
        col0 = xor(w[group][0:4],trans)
        nextg.extend(col0)
        # 2,3,4 col: 
        col1 = xor(w[group][4:8],nextg[0:4])
        nextg.extend(col1)
        col2 = xor(w[group][8:12],nextg[4:8])
        nextg.extend(col2)
        col3 = xor(w[group][12:16],nextg[8:12])
        nextg.extend(col3)
        w.append(nextg)
    return w
# 密钥变换
def key_trans(col,i): #ok
    rcon = [ 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1B,0x36]
    # key rotation, shift left by 1 byte 
    temp = col[0]
    col[0] = col[1]
    col[1] = col[2]
    col[2] = col[3] 
    col[3] = temp
    # key substitution:  
    col = substitution(col,0)
    col[0] = col[0] ^ rcon[i]
    return col

 

密钥选取

获得扩展密钥之后,依次选取相邻的4列作为密钥。
AES-128-CBC-PKCS5PADDING 加解密实现_第8张图片

CODE
代码中,我将扩展的密钥存储在w[]这个list中,每个密钥存在一个list中,即w[]中存的是11个list,每个list就是一个密钥,所以只需要用下标取所需密钥即可。

 

AES加法和乘法运算

AES是基于有限域GF(2^8)上的运算,具体关于有限域,这里不多讲,有空我会补上一个关于有限域上的运算及代码实现。
AES算法中需要用到的是有限域上的加法和乘法。

AES加法

因为是二元域,所以AES的加法实际上是对两个数字进行异或运算,使用位运算符(^)即可。

AES乘法

我们在二进制的基础上进行乘法操作。我们仿照手算过程,例如a*b,对b从低位开始取,每次将a左移,即乘2之后与b该位相乘,得到一个临时结果,最后将所有临时结果相加即得到答案。
可以发现每个临时结果都是迭代多次乘00000010(2)之后的结果,所以其实只要实现有限域上乘2的过程即可。
CODE

def mul(a,b): #ok
    # aes multiplication on GF(2^8):  
    temp = a
    mod = 1
    ans = 0
    for i in range(8): 
        bi = b & mod
        if bi == mod:
            ans = ans ^ temp
        temp = mul2(temp)
        mod = mod * 2
    return ans

在GF(2^8)上,乘2操作可以简化为以下过程:
AES 乘2
CODE

# num * 2
def mul2(num): #ok
    # return 2 * num
    # shift left:
    h = num & 0x80
    if h == 128: 
        return (num-128) * 2 ^ 0x1B 
    else: 
        return num * 2

 

CBC mode

原理如图
AES-128-CBC-PKCS5PADDING 加解密实现_第9张图片

加密和解密都是从第一个数据块开始操作。
CODE

def aes_enc_cbc(m_list,key,iv): #ok
    c = []
    temp_iv = iv
    while m_list:
        text = m_list[:16]
        m_list = m_list[16:]
        text = xor(temp_iv,text)
        temp_iv = aes_encrypt(text,key)
        c.extend(temp_iv)
    return c

def aes_dec_cbc(c_list,key,iv):
    m = []
    temp_iv = iv
    while c_list:
        cipher = c_list[:16]
        c_list = c_list[16:]
        dec = aes_decrypt(cipher,key)
        message = xor(temp_iv,dec)
        temp_iv = cipher
        m.extend(message)
    return m

 

PADDING

1)对于数据流采用PKCS5 PADDING模式。 PKCS5 Padding模式将数据流长度补足成128bit的倍数。最后一个数据块缺x个字节,就在其后面补x个x,如果原数据流长度为128bit倍数,则添加16个字节的16。
CODE

def PKCS5Padding(s):
    test = 16 - (len(s) % 16)
    ans = s
    if test != 0:  
        ans.extend([test]*test)
    else: 
        ans.extend([16]*16)
    return ans

2)对于密钥和iv,将不足的字节添加0
CODE

def zeroPadding(s):
    test = 16 - (len(s) % 16)
    ans = s
    if test != 0: 
        ans.extend([0]*test)
    return ans

 

BASE64编码

用于显示的string和运算的数字list的转换

Base64编码将字符转换为可见字符,将原数据流每6个bit为一组进行转换。实际上是将原本的3个8bit转化为4个6bit,如果不足3的倍数则添加0,最后的0转化为‘=‘
CODE

ef list2base64(li):
    l = li
    ans = []
    lens = 3- len(li) % 3
    if lens != 3: 
        l.extend([0]*lens)
    table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    while l:
        temp = l[:3]
        l = l[3:]
        a = (temp[0] << 16) + (temp[1] << 8) + temp[2]
        a0 = (a >> 18) & 0x3F 
        a1 = (a >> 12) & 0x3F 
        a2 = (a >> 6) & 0x3F
        a3 = a & 0x3F
        ans.extend([table[a0],table[a1],table[a2],table[a3]])
    ans = ans[:len(ans)-lens]
    ans.extend(['=']*lens)
    return ''.join(ans)
# change base64 to list:
def base642list(ss): 
    index = 0
    for ch in ss: 
        if ch == '=': 
            index += 1 
    ans = []
    s = ss
    table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    while s: 
        temp = s[:4]
        s = s[4:]
        pos = []
        for ch in temp:
            pos.append(table.find(ch))
        a1_1 = (pos[1] >> 4) & 0x03
        a1_2 = pos[1] & 0x0F
        a2_1 = (pos[2] >> 2) & 0x0F
        a2_2 = (pos[2]) & 0x03
        b0 = (pos[0] << 2) + a1_1
        b1 = (a1_2 << 4) + a2_1 
        b2 = (a2_2 << 6) + pos[3]
        ans.extend([b0,b1,b2])
    ans = ans[:len(ans)-index]
    return ans

至此,全部加解密工作完成,AES完成!

你可能感兴趣的:(Cryptography)