【图解AES加密算法】AES算法的Python实现 | Rijndael-128 | 对称加密 | 物联网安全

【图解AES加密算法】AES算法的Python实现 | Rijndael-128 | 对称加密 | 物联网安全,第1张

完整代码已更新

文章目录
    • 一、AES的前世今生
    • 二、AES简介
    • 三、初始变换Initial round
    • 四、字节代换SubBytes
    • 五、行移位ShiftRows
    • 六、列混合MixColumns
    • 七、轮秘钥加AddRoundKey
      • 秘钥扩展
    • 八、流程回顾
    • 九、完整代码
    • 十、实验结果与心得体会

一、AES的前世今生

美国国家标准技术研究所在2001年发布了高级加密标准(AES)。AES是一个对称加密算法,旨在取代DES成为广泛使用的标准,该标准以Rijndael算法为核心。

Rijndael算法是一种对称分组密码体制,采用代替或置换网络,每轮由三层组成:

  1. 线性混合层确保多轮之上的高度扩散;
  2. 非线性层由S盒并置起到混淆的作用;
  3. 密钥加密层将子密钥异或到中间状态。

AES标准规定Rijndael算法的分组长度为128位,而密钥长度可以为128、192或256位,相应的迭代轮数为10轮、12轮或14轮。Rijndael 汇聚了安全性能、效率、可实现性和灵活性等优点。Rijndael 对内存的需求低,使它很适合用于资源受限制的环境中,Rijndael 的 *** 作简单,并可抵御强大和实时的攻击

二、AES简介

AES,Advanced Encryption Standard,属于分组加密算法,明文长度固定位128位(16字节),秘钥长度可以是128,192,256位,分别循环10、12、14轮

其中字节是按照矩阵的形式进行排列

AES加密算法流程:

最终轮和9轮循环运算的区别是没有列混合。

三、初始变换Initial round

将明文转换后的结果和子秘钥矩阵进行异或运算得到处理结果

    def Init_round(self, plaintext, key):
        self.plaintext_16 = np.array([_ for _ in plaintext.to_bytes(16, byteorder='big')]).reshape(4, 4).T
        self.key_16 = np.array([_ for _ in key.to_bytes(16, byteorder='big')]).reshape(4, 4).T
        return self.plaintext_16 ^ self.key_16
四、字节代换SubBytes


字节代替:用一个S盒完成分组的字节到字节的代替;是一个基于S盒的非线性置换,它用于将每一个字节通过一个简单的查表 *** 作映射为另一个字节。映射方法是把输入字节的高4位作为S盒的行值,低4位作为列值,然后取出S盒中对应行和列交叉位的元素作为输出

按照S盒尽心查询替换,这一步还是比较简单的,比如19->d4,是第1行第9列,查表可知结果为d4。

    def SubBytes(self, arr):
        return np.array([self.S_BOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in np.nditer(arr, order='F')]]).reshape(4, 4)
五、行移位ShiftRows

行移位: AES 的行移位也是一个简单的左循环移位 *** 作。当密钥长度为128比特时,状态矩阵的第0行左移0字节,第1行左移1字节,第2行左移2字节,第3行左移3字节

行移位也比较简单,就是第一行向左移动一个字节,以此类推

    def ShiftRows(self, arr):
        S = np.zeros((4, 4), dtype='int')
        for i in range(arr.shape[0]):
            S[i] = np.roll(arr[i], -i)
        return S
六、列混合MixColumns

列混合:列混合变换是通过矩阵相乘来实现的,经行移位后的状态矩阵与固定的矩阵相乘,得到混淆后的状态矩阵。

将输入的4x4矩阵左乘一个给定的4x4矩阵

这里要区别于平常的矩阵乘法,不是线性代数中的相乘再相加

矩阵运算中的加法被替换为了异或 *** 作

乘法运算也不一样,这里需要按字节进行运算,01等于本身,故只需要关注02,03,04,下面分别是其运算公式

    def MixColumns(self, arr):
        M = np.zeros((4, 4), dtype=int)
        for row in range(4):
            for col in range(4):
                for i in range(4):
                    M[row][col] ^= self.Mul(self.MIX_C[row][i], arr[i][col])
        return M
七、轮秘钥加AddRoundKey

轮密钥加:当前分组和扩展密钥的一部分进行按位异或,将输入或中间态S的每一列与一个密钥字ki进行按位异或,即将128位轮密钥 Ki 同状态矩阵中的数据进行逐位异或 *** 作。

    def AddRoundKey(self, arr, i):
        return arr ^ self.roundkey[:, (i-1)*4:(i-1)*4+4]

列混合得到的结果和轮秘钥进行异或运算,轮秘钥是由子秘钥矩阵扩展而来,下面来讲一下轮秘钥是怎么来的:

秘钥扩展

我们初始变换的到的是一个4x4的矩阵,比如我们接下来要求第i列,那么我们要看要求的这一列是否是4的倍数。
(1)如果i不是4的倍数,那么第i列由如下等式确定:
W [ i ] = W [ i − 4 ] ⨁ W [ i − 1 ] W[i]=W[i-4]⨁W[i-1] W[i]=W[i4]W[i1]
(2)如果i是4的倍数,那么第i列由如下等式确定:
W [ i ] = W [ i − 4 ] ⨁ T ( W [ i − 1 ] ) W[i]=W[i-4]⨁T(W[i-1]) W[i]=W[i4]T(W[i1])
比如第5列(列数从0开始计),不是4的倍数,则用(1)中的公式计算即可,即第1列和第4列异或

当i是4的倍数时,要经过T函数的处理,T函数由三部分组成,分别是字循环、字节代换和轮常量异或。

a.字循环

将1个字中的4个字节循环左移1个字节。即将输⼊字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]。

b.字节代换

对字循环的结果使⽤S盒进⾏字节代换。

c.轮常量异或

将前两步的结果同轮常量Rcon[j]进⾏异或,其中j表示轮数。

这里的Rcon轮常量是给定的,与w[i-4]和字节代换后的结果进行异或运算得到最终的w[i]。

最终经过10轮的扩展得到轮秘钥,如下

    def Round_key(self, arr):
        M = np.hstack((arr, np.zeros((4, 40), dtype=int)))
        for i in range(4, 44):
            M[:, i] = M[:, i-4] ^ M[:, i - 1] if i % 4 else M[:,i-4] ^ self.T(M[:, i-1], i//4-1)
        return M[:, 4:]
八、流程回顾
九、完整代码

这里仅给出加密过程,有能力的同学可以对照着写解密

import numpy as np


class AES:
    MIX_C = np.array([[0x2, 0x3, 0x1, 0x1], [0x1, 0x2, 0x3, 0x1], [0x1, 0x1, 0x2, 0x3], [0x3, 0x1, 0x1, 0x2]])

    RCon = np.array([[0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36],
                    [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
                    [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
                    [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]])

    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, 0x1]]

    def __init__(self, plaintext, key):
        self.plaintext = plaintext
        self.key = key
        self.plaintext_16 = None
        self.key_16 = None
        self.roundkey = None

    def Init_round(self, plaintext, key):
        self.plaintext_16 = np.array(
            [_ for _ in plaintext.to_bytes(16, byteorder='big')]).reshape(4, 4).T
        self.key_16 = np.array([_ for _ in key.to_bytes(
            16, byteorder='big')]).reshape(4, 4).T
        return self.plaintext_16 ^ self.key_16

    def SubBytes(self, arr):
        return np.array([self.S_BOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in np.nditer(arr, order='F')]]).reshape(4, 4)

    def ShiftRows(self, arr):
        S = np.zeros((4, 4), dtype='int')
        for i in range(arr.shape[0]):
            S[i] = np.roll(arr[i], -i)
        return S

    def Mul(self, x1, x2):
        if x1 == 0x1:
            x = x2
        elif x1 == 0x2:
            x = ((x2 << 1 & 0xff) ^ 0b00011011) if (
                x2 & 0x80) else (x2 << 1 & 0xff)
        elif x1 == 0x3:
            x = self.Mul(0x2, x2) ^ x2
        elif x1 == 0x4:
            x = self.Mul(Mul(0x2, 0x2), x2)
        return x

    def MixColumns(self, arr):
        M = np.zeros((4, 4), dtype=int)
        for row in range(4):
            for col in range(4):
                for i in range(4):
                    M[row][col] ^= self.Mul(self.MIX_C[row][i], arr[i][col])
        return M

    def T(self, arr, i):
        arr = np.roll(arr, -1)
        M = np.array([self.S_BOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in arr]])
        M = M ^ self.RCon[:, i]
        return M

    def Round_key(self, arr):
        M = np.hstack((arr, np.zeros((4, 40), dtype=int)))
        for i in range(4, 44):
            M[:, i] = M[:, i-4] ^ M[:, i - 1] if i % 4 else M[:,i-4] ^ self.T(M[:, i-1], i//4-1)
        return M[:, 4:]

    def AddRoundKey(self, arr, i):
        return arr ^ self.roundkey[:, (i-1)*4:(i-1)*4+4]

    def aes_encrypt(self):
        State = self.Init_round(self.plaintext, self.key)
        self.roundkey = self.Round_key(self.key_16)
        for r in range(1, 10):
            State = self.SubBytes(State)
            State = self.ShiftRows(State)
            State = self.MixColumns(State)
            State = self.AddRoundKey(State, r)
        State = self.SubBytes(State)
        State = self.ShiftRows(State)
        State = self.AddRoundKey(State, 10)
        return State


if __name__ == '__main__':
    plaintext = 0x00112233445566778899aabbccddeeff
    key = 0x000102030405060708090a0b0c0d0e0f
    aes = AES(plaintext, key)
    print(aes.aes_encrypt())
    # print(arr)
    # print(aes.ShiftRows(arr))
    # print(aes.SubBytes(arr))
    # arr = np.array([[0xd4, 0xe0, 0xb8, 0x1e], [0xbf, 0xb4, 0x41, 0x27], [0x5d, 0x52, 0x11, 0x98], [0x30, 0xae, 0xf1, 0xe5]])
    # print(aes.MixColumns(arr))
    # arr= np.array([[0x2b, 0x28, 0xab, 0x09], [0x7e, 0xae, 0xf7, 0xcf], [0x15, 0xd2, 0x15, 0x4f], [0x16, 0xa6, 0x88, 0x3c]])
    # print(aes.Round_key(arr))

十、实验结果与心得体会


将结果储存在了一个矩阵里,结果用int型表示。(这个实验是这学期第一次写加密算法,所以代码写的比较稚嫩,各位大佬见谅,中间变量没有处理成二进制或16进制,大佬可在此基础上改进)
心得:
不得不说,AES理论理解起来并不是特别难,但当真正去动手复现算法,确是一头雾水,在机房的时候感觉无从下手,回去之后又把课本认真看了看,搞清楚了各个步骤之间的衔接和逻辑,然后开始了代码设计,我选择的是Python,因为了解过一些用于科学计算的库,这里我就使用了numpy。这个实验我真是从0开始一行一行实现各个函数的功能,其间的坎坷让我加深了对AES的理解,先是把各个步骤的代码实现并进行验证,然后再写一个函数将他们串联起来,这个实验我写了两天,虽然中间也有其他事情要处理,不过当最后完整验算过一遍之后,内心十分开心,觉得努力没有白费!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/715475.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存