Skip to content

AES-128

加密模式处理

ECB

The Electronic Codebook Mode

CBC

The Cipher Block Chaining Mode

CFB

The Cipher Feedback Mode

这里只需要考虑 CFB-128bit,也就是 \(s=b\),所以上述过程可以简化为如下:

\[ \begin{aligned} &\text{CFB Encryption:} &&C_1=P_1\oplus CIPH_K(IV)\\ &&&C_j=P_j\oplus CIPH_K(C_{j-1}) && \text{for}\ j=2\cdots n;\\\\ &\text{CFB Decryption:} &&P_1=C_1\oplus CIPH_K(IV)\\ &&&P_j=C_j\oplus CIPH_K(C_{j-1}) && \text{for}\ j=2\cdots n; \end{aligned} \]

OFB

The Output Feedback Mode

最后一个分组 block 长度可以小于 128 bit,这里假定其长度为 \(u\)

我消去 \(I\),仅用 \(O\) 表示密钥流的变化,更加直观:

\[ \begin{aligned} &\text{OFB Encryption:} &&O_1=CIPH_K(IV)\\ &&&O_j=CIPH_K(O_{j-1}) && \text{for}\ j=2\cdots n;\\ &&&C_j=P_j\oplus O_{j} && \text{for}\ j=1,2\cdots n-1;\\ &&&C_n^*=P_n^*\oplus (O_{j}>>(b-u))\\ \\ &\text{OFB Decryption:} &&O_1=CIPH_K(IV)\\ &&&O_j=CIPH_K(O_{j-1}) && \text{for}\ j=2\cdots n;\\ &&&P_j=C_j\oplus O_{j} && \text{for}\ j=1,2\cdots n-1;\\ &&&P_n^*=C_n^*\oplus (O_{j}>>(b-u))\\ \end{aligned} \]

\(b\) 是一个 block 分组的大小,这里就是 128 bit。

CTR

The Counter Mode

AES Cipher & Inverse

下图展现了实现加密和解密 AES 的过程,将每一轮每一个变换函数建立了一一对应关系。

其中除了 Add 操作之外(直接异或即可),包含主要 3 对变换和反变换。另外还需要进行 Expand Key,在 AES-128 中就是将 128 bits 输入 key 计算得到规模为 128x11 bits 的 11 个 key。

SubBytes & Inverse

这一变换数学上的功能是对每一个字节实现如下变换:

  • 将八位字节视为有限域 \(GF(2^8)\) 上的元素,进行取逆

  • 然后进行如下仿射变换:(\(b_i\) 表示一个字节从低位开始数的第 \(i+1\) 位)

image-20241121133756106

从结果上来说,上述变换过程及其反变换完全可以通过查表来完成。所以对于 SubBytesInvSubBytes 变换,就是分别将每一个字节经过预先计算好的 AES S-Box 和 AES Inv-S-Box 进行查表置换。

对于一个字节,有 \(2^8=256\) 种取值,所以两个 Box 的大小都是 256,内容如下:

const uint8_t Sbox[256] = {
    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,
};

const uint8_t InvSbox[256] = {
    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,
};

ShiftRows & Inverse

这一变换比较简单,就是对表示为 4x4 矩阵的 16 字节数据,对第 \(i\) 行进行 \(i\) 字节轮转。(\(0\leq i\leq 3\)

所以显然第一行,也就是第 \(0\) 行进行 \(0\) 字节轮转是没有实际作用可以省略的。其过程如下图所示:

逆变换则如下所示:

cpp 使用冒泡的方式处理如下:

// uint8_t c[16]

inline void ShiftRows(uint8_t * c){
    swap(c[1], c[5]);
    swap(c[5], c[9]);
    swap(c[9], c[13]);
    swap(c[2], c[10]);
    swap(c[6], c[14]);
    swap(c[15], c[11]);
    swap(c[11], c[7]);
    swap(c[7], c[3]);
}

inline void InvShiftRows(uint8_t * c){
    swap(c[9], c[13]);
    swap(c[5], c[9]);
    swap(c[1], c[5]);
    swap(c[2], c[10]);
    swap(c[6], c[14]);
    swap(c[7], c[3]);
    swap(c[11], c[7]);
    swap(c[15], c[11]);
}

MixColumns & Inverse

对表示为 4x4 矩阵的 16 字节数据,每一列 4 字节向量,视为一个 \(x=2^8\) 并且系数来自 \(GF(2^8)\) 的多项式

\[ a(x)=a_3x+a_2x+a_1x+a_0\\a_i\in GF(2^8),\ \ x=2^8 \]

AES 使用了一对可逆的多项式,通过对这两个多项式进行模乘实现变换和逆变换:

\[ b(x)=\{\text{0x03}\}x^3+\{\text{0x01}\}x^2+\{\text{0x01}\}x+\{\text{0x02}\}\\ b^{-1}(x)=\{\text{0x0B}\}x^3+\{\text{0x0D}\}x^2+\{\text{0x09}\}x+\{\text{0x0E}\} \]

于是 \(a(x)\otimes b(x)\) 就可以表示为有限域 \(GF(2^8)\) 上的向量与可逆矩阵乘法:

同理,\(a(x)\otimes b^{-1}(x)\) 也表示为有限域 \(GF(2^8)\) 上的向量与可逆矩阵乘法:

这里有限域 \(GF(2^8)\) 上的模乘运算如果再通过查表算,就需要 \(7\times256\) 大小的表,因为两个矩阵总共出现了 7 种不同的系数。在 AES 标准中没有使用查表,而是使用有限域 \(GF(2^8)\) 上位运算实现的乘 2 模运算 xtime 实现。这个函数运算非常简单,cpp 实现如下:

auto xtime = [](uint8_t x) -> uint8_t {
    return (x & 0x80) ? ((x << 1) ^ 0x1B) : (x << 1);
};

对于一般的有限域 \(GF(2^8)\) 上模乘运算 \(a\otimes b\),就能用 xtime 表示如下:

\[ a\otimes b=b_7\cdot\text{xtime}^{7}(a)+b_6\cdot\text{xtime}^{6}(a)+\cdots+b_1\cdot\text{xtime}(a)+b_0\cdot a\ \ \ ,\ \ \ b_i\in\{0,1\} \]

然后矩阵运算还可以技巧性的使用中间变量,避免进行重复相同的运算,cpp 实现单独对一个列变换和反变换如下:

inline void MixColumn(uint8_t * a){
    uint8_t t, u;
    t = a[0] ^ a[1] ^ a[2] ^ a[3];
    u = a[0];
    a[0] ^= t ^ xtime(a[i] ^ a[1]);
    a[1] ^= t ^ xtime(a[1] ^ a[2]);
    a[2] ^= t ^ xtime(a[2] ^ a[3]);
    a[3] ^= t ^ xtime(a[3] ^ u);
}

inline void InvMixColumn(uint8_t * a){
    uint8_t u, v;
    u = xtime(xtime(c[i] ^ c[i + 2]));
    v = xtime(xtime(c[i + 1] ^ c[i + 3]));
    c[i] ^= u;
    c[i + 1] ^= v;
    c[i + 2] ^= u;
    c[i + 3] ^= v;
    MixColumn(c);
}

Key Expansion

过程大致如图所示:

NIST 的 AES 标准文档 FIPS 197 给出的伪代码如下:

这里关键的几个步骤在于如下 2 个函数:

  • SubWord,对一个 word 的 4 个字节分别进行和 SubBytes 变换一样的操作

  • RotWord,对一个 word 的 4 个字节进行和 ShiftRows 变换第二行一样的操作

还有一个数组 Rcon,定义为:

uint8_t Rcon[32] = {
    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
    0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
    0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
    0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
}

在 AES-128 中只需要用到前 11 个。

思考题

问题 1

除了课堂上介绍的五种模式以外,分组加密还有一些其他的工作模式。考虑下面的两个场景,使用什么工作模式可以满足它们的需求?这个工作模式的原理是什么?

  • 你需要检查密文是否被篡改(不需要单独使用 Hash 函数)。
  • 你需要加密一块磁盘。磁盘上的数据是以扇区(通常为 512 Bytes)为单位读写的,操作系统可以通过扇区号随机访问扇区。磁盘加密的密文长度必须要和明文相同,不能使用额外的空间。(有兴趣的话,可以尝试使用一下磁盘加密软件,例如开源的 VeraCrypt)

回答

检查密文是否被篡改,就需要认证功能,分组密码中支持认证的模式被称为 AE 模式,例如:

有的模式也允许为未加密的关联数据进行认证,因此被称为 AEAD(Authenticated-Encryption with Associated-Data。

对于磁盘加密,要求密文长度与明文相同且不能使用额外的空间,可以使用 ECB、CBC、CTR 、XTS(XEX Tweakable Block Cipher with ciphertext stealing)模式。根据我查阅到资料, XTS 模式是专门设计用于磁盘加密的工作模式,例如 Kingston 等厂商就会在其产品中使用。

问题 2

尝试在实验 3-2 中实现全部五种工作模式:ECB、CBC、CFB、OFB、CTR。

  • 只要实现了对单个分组的加密和解密,实现其他的工作模式也是很简单的!
  • 选做,不会对分数产生影响。

在前面有各个模式分析,当时我没时间搞这个

问题 3

尝试使用 x64 CPU 的 AES 指令集(AES-NI)实现 AES-256。

  • 参考资料:x86/x64 SIMD 命令による AES 暗号処理
  • 理解 AES-NI 的各个指令的用途。
  • 上面的链接中有 C++ 编写的示例代码,仅供参考,不要直接提交或使用 GPT 格式化重命名后提交。
  • 请先用自己实现的版本 AC 后再编写使用 AES-NI 的版本,并在实验报告中附上两个版本的提交 ID。
  • 不计入思考题分数,但是可以影响性能分。

见附录

Reference

附录

当时忙着一个写数据库的比赛(Mini OceanBase )没来得及弄密码学了

没来得及完全完成 x64 CPU 的 AES 指令集(AES-NI)实现 AES-256,实际上我在自己实现的时候就已开始想使用 __m128i 一步到位,然后发现这是思考题附加内容,并且使用 AES 指令是真的“一步到位”,非常简单。所以我自己的实现只好退而求其次使用 uint32_tuint8_t 来实现。

已经初步写好,但好像还是错的,没来得及 debug 了,如下:

#include <cstdint>
#include <iostream>
#include <vector>
#include <algorithm>
#include <stdint.h>
#include <cstring>
#include <emmintrin.h>
#include <immintrin.h>


// Number of columns (32-bit words) comprising the State. For this standard, Nb = 4
#define Nb 4

// Number of 32-bit words comprising the Cipher Key. For this standard, Nk = 4, 6, or 8
#define Nk 4

// Number of rounds, which is a function of Nk and Nb (which is fixed). For this standard, Nr = 10, 12, or 14
#define Nr 10

using namespace std;

void ecb_en(__m128i & KEY, uint32_t & len);
void ecb_de(__m128i & KEY, uint32_t & len);
void cbc_en(__m128i & KEY, __m128i & IV, uint32_t & len);
void cbc_de(__m128i & KEY, __m128i & IV, uint32_t & len);
void cfb_en(__m128i & KEY, __m128i & IV, uint32_t & len);
void cfb_de(__m128i & KEY, __m128i & IV, uint32_t & len);
void ofb_en(__m128i & KEY, __m128i & IV, uint32_t & len);
void ofb_de(__m128i & KEY, __m128i & IV, uint32_t & len);
void ctr_en(__m128i & KEY, __m128i & IV, uint32_t & len);
void ctr_de(__m128i & KEY, __m128i & IV, uint32_t & len);


enum MODE : uint8_t {
    ECB_EN = 0x00,
    ECB_DE = 0x80,
    CBC_EN = 0x01,
    CBC_DE = 0x81,
    CFB_EN = 0x02,
    CFB_DE = 0x82,
    OFB_EN = 0x03,
    OFB_DE = 0x83,
    CTR_EN = 0x04,
    CTR_DE = 0x84,
};



#ifndef ONLINE_JUDGE
#include <chrono>
#include <iomanip>
FILE *output, *input;
chrono::time_point<chrono::high_resolution_clock> start_time, end_time;
#endif


int main(){
    #ifndef ONLINE_JUDGE
    output = freopen("output.bin", "wb", stdout);
    input = freopen("input.bin", "rb", stdin);
    start_time = chrono::high_resolution_clock::now();
    #endif

    uint8_t mode;
    __m128i KEY;
    __m128i IV;
    uint32_t len;
    fread(&mode, sizeof(mode), 1, stdin);
    fread(&KEY, sizeof(KEY), 1, stdin);
    fread(&IV, sizeof(IV), 1, stdin);
    fread(&len, sizeof(len), 1, stdin);

    switch(mode){
        // case ECB_EN:{ecb_en(KEY, len);}break;
        // case ECB_DE:{ecb_de(KEY, len);}break;
        case CBC_EN:{cbc_en(KEY, IV, len);} break;
        case CBC_DE:{cbc_de(KEY, IV, len);} break;
        // case CFB_EN:{cfb_en(KEY, IV, len);}break;
        // case CFB_DE:{cfb_de(KEY, IV, len);}break;
        // case OFB_EN:{ofb_en(KEY, IV, len);}break;
        // case OFB_DE:{ofb_de(KEY, IV, len);}break;
        // case CTR_EN:{ctr_en(KEY, IV, len);}break;
        // case CTR_DE:{ctr_de(KEY, IV, len);}break;
    }

    #ifndef ONLINE_JUDGE
    fclose(input);
    fclose(output);
    output = freopen("time", "w", stdout);
    end_time = chrono::high_resolution_clock::now();
    auto ns = chrono::duration_cast<chrono::nanoseconds>(end_time - start_time);
    auto us = chrono::duration_cast<chrono::microseconds>(end_time - start_time);
    auto ms = chrono::duration_cast<chrono::milliseconds>(end_time - start_time);
    cout << ns.count() << " ns\n"
         << us.count() << " us\n"
         << ms.count() << " ms";
    fclose(output);
    #endif

    return 0;
}


// uint32_t key[Nk] ---expand---> __m128i w128[Nr+1]
void KeyExpansion(__m128i & key, __m128i * w128, bool decrypting){
    w128[0] = key;
    __m128i t, t2;

    #define EXPAND(n, RCON) \
    t  = _mm_slli_si128(key, 4);\
    t  = _mm_xor_si128(key, t);\
    t2 = _mm_slli_si128(t, 8);\
    t  = _mm_xor_si128(t, t2);\
    key = _mm_aeskeygenassist_si128(key, RCON);\
    key = _mm_shuffle_epi32(key, 0xFF);\
    key = _mm_xor_si128(t, key);\
    w128[n] = key;

    EXPAND(1, 0x01);

    // Go on...
    EXPAND(2, 0x02);
    EXPAND(3, 0x04);
    EXPAND(4, 0x08);
    EXPAND(5, 0x10);
    EXPAND(6, 0x20);
    EXPAND(7, 0x40);
    EXPAND(8, 0x80);
    EXPAND(9, 0x1b);
    EXPAND(10, 0x36);

    if (decrypting) {
        w128[0] = w128[0];
        w128[1] = _mm_aesimc_si128(w128[1]);
        w128[2] = _mm_aesimc_si128(w128[2]);
        w128[3] = _mm_aesimc_si128(w128[3]);
        w128[4] = _mm_aesimc_si128(w128[4]);
        w128[5] = _mm_aesimc_si128(w128[5]);
        w128[6] = _mm_aesimc_si128(w128[6]);
        w128[7] = _mm_aesimc_si128(w128[7]);
        w128[8] = _mm_aesimc_si128(w128[8]);
        w128[9] = _mm_aesimc_si128(w128[9]);
        w128[Nr] = w128[Nr];
    }
}


// Add 128-bit k to c
inline void AddRoundKey(__m128i & c, __m128i & k){
    c = _mm_xor_si128(c, k);
}


// AES Cipher
inline void Ciph(__m128i c, __m128i * w128){
    __m128i state = _mm_loadu_si128(&c);
    state = _mm_xor_si128(state, w128[0]);
    state = _mm_aesenc_si128(state, w128[1]);
    state = _mm_aesenc_si128(state, w128[2]);
    state = _mm_aesenc_si128(state, w128[3]);
    state = _mm_aesenc_si128(state, w128[4]);
    state = _mm_aesenc_si128(state, w128[5]);
    state = _mm_aesenc_si128(state, w128[6]);
    state = _mm_aesenc_si128(state, w128[7]);
    state = _mm_aesenc_si128(state, w128[8]);
    state = _mm_aesenc_si128(state, w128[9]);
    state = _mm_aesenclast_si128(state, w128[Nr]);
    _mm_storeu_si128(reinterpret_cast<__m128i *>(&c), state);
}



// AES Inverse Cipher
inline void EqInvCiph(__m128i & c, __m128i * w128){
    __m128i state = _mm_loadu_si128(&c);
    state = _mm_xor_si128(state, w128[Nr]);
    state = _mm_aesdec_si128(state, w128[9]);
    state = _mm_aesdec_si128(state, w128[8]);
    state = _mm_aesdec_si128(state, w128[7]);
    state = _mm_aesdec_si128(state, w128[6]);
    state = _mm_aesdec_si128(state, w128[5]);
    state = _mm_aesdec_si128(state, w128[4]);
    state = _mm_aesdec_si128(state, w128[3]);
    state = _mm_aesdec_si128(state, w128[2]);
    state = _mm_aesdec_si128(state, w128[1]);
    state = _mm_aesdeclast_si128(state, w128[0]);
    _mm_storeu_si128(reinterpret_cast<__m128i *>(&c), state);
}


// CBC mode encryption
void cbc_en(__m128i & KEY, __m128i & IV, uint32_t & len){
    __m128i p, c, w[Nr+1];
    uint8_t p_bytes[16];
    uint8_t c_bytes[16];
    c = IV;
    KeyExpansion(KEY, w, 0);
    while(len >= Nb * 4){
        len -= Nb * 4;
        fread(&p, sizeof(p), 1, stdin);
        AddRoundKey(c, p);
        Ciph(c, w);
        fwrite(&c, sizeof(c), 1, stdout);
    }
    memset(p_bytes + len, Nb * 4 - len, Nb * 4 - len);
    fread(&p, sizeof(uint8_t), len, stdin);
    AddRoundKey(c, p);
    Ciph(c, w);
    fwrite(&c, sizeof(c), 1, stdout);
}


// CBC mode decryption, input must be multiple of 128 bits
void cbc_de(__m128i & KEY, __m128i & IV, uint32_t & len){
    __m128i p, c, last_c, w[Nr+1];
    uint8_t p_bytes[16];
    memcpy(&last_c, &IV, Nb * 4);
    KeyExpansion(KEY, w, 1);
    while(len > 0){
        fread(&c, sizeof(c), 1, stdin);
        p = c;
        EqInvCiph(p, w);
        AddRoundKey(p, last_c);
        last_c = c;
        if(len ** Nb * 4){
            _mm_storeu_si128((__m128i*)p_bytes, p);
            int l = p_bytes[Nb * 4 - 1];
            for(int i = 1; i < l; ++i){
                if(p_bytes[Nb * 4 - 1 - i] != l) {
                    l = 0;
                    break;
                }
            }
            fwrite(&p, sizeof(uint8_t), Nb * 4 - l, stdout);
        } else {
            fwrite(&p, sizeof(p), 1, stdout);
        }
        len -= Nb * 4;
    }
}