STM32: 实现AES 128-bit加密算法 - 标准实现

在之前的文章“STM32: 实现Advanced Encryption Standard(AES) – 128-bit加密算法”中实现的加密算法只是为了解密特定的文本,目的性比较强,用此算法加密过的文本,无法使用openssl或者是网上提供的在线AES工具进行加密或者解密,这就相对地增加了破译的难度。

STM32: 实现Advanced Encryption Standard(AES) – 128-bit加密算法

那么当时实现的加密算法与标准算法有什么区别呢,我们这就来看一下。

  • AES 标准算法

AES 128-bit算法中包含:

- SubBytes

- Shift Rows

- Mix Columns

- Add Round Key

对于每16字节的文本,会执行11次的Add Round Key,  10次的SubBytes/ShiftRows和9次的Mix Columns:

2016_10_23_aes_std

 

对于之前的AES 128-bit算法的非标准实现,个人觉得比较让人耳目一新的地方在于只用三行的代码就实现了4个字节的Mix Columns和Add Round Key的操作:

  v17 = ROTATE(v11, 16);
  v18 = ROTATE(v11, 24);
  v1 = key->rd_key[4 * v20 + 0] ^ 0x1B * ((v11 >> 7) & 0x1010101) ^ 2 * (v11 & 0xFF7F7F7F) ^
                ((2 * (v11 & 0x7F000000) ^ 0x1B * ((v11 >> 7) & 0x1010101) ^ v11) >> 24 | (0x1B * ((v11 >> 7) & 0x10101) ^ 2 * (v11 & 0xFFFF7F7F) ^ v11) << 8) ^ v18 ^ v17;
  • 之前实现算法与标准算法的不同点

下面再来看一下之前实现的算法与标准算法倒底有哪些不同。

1. 生成的Round Key

首先看一下之前生成的176字节的Round Keys:

Key:

0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f

生成的Round Keys:

 --------------------- AES 128 ENC EXPANDED KEY -------------------------
00000000: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f  ................
00000010: 76 ff d5 a9 72 fa d3 ae 7a f3 d9 a5 76 fe d7 aa  v...r...z...v...
00000020: da c7 6e a5 a8 3d bd 0b d2 ce 64 ae a4 30 b3 04  ..n..=....d..0..
00000030: 28 8e 6a cc 80 b3 d7 c7 52 7d b3 69 f6 4d 00 6d  (.j.....R}.i.M.m
00000040: 14 cc 89 a7 94 7f 5e 60 c6 02 ed 09 30 4f ed 64  ......^`....0O.d
00000050: 57 c8 0d e2 c3 b7 53 82 05 b5 be 8b 35 fa 53 ef  W.....S.....5.S.
00000060: 88 5e 20 2f 4b e9 73 ad 4e 5c cd 26 7b a6 9e c9  .^ /K.s.N\.&{...
00000070: 55 7f 04 64 1e 96 77 c9 50 ca ba ef 2b 6c 24 26  U..d..w.P...+l$&
00000080: a2 8e 54 d2 bc 18 23 1b ec d2 99 f4 c7 be bd d2  ..T...#.........
00000090: 17 48 fa b3 ab 50 d9 a8 47 82 40 5c 80 3c fd 8e  .H...P..G.@\.<..
000000a0: 0e 85 11 d1 a5 d5 c8 79 e2 57 88 25 62 6b 75 ab  .......y.W.%bku.

标准算法生成的Round Keys(http://www.samiam.org/key-schedule.html):

For the key 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f, the expanded key is:
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 
d6 aa 74 fd d2 af 72 fa da a6 78 f1 d6 ab 76 fe 
b6 92 cf 0b 64 3d bd f1 be 9b c5 00 68 30 b3 fe 
b6 ff 74 4e d2 c2 c9 bf 6c 59 0c bf 04 69 bf 41 
47 f7 f7 bc 95 35 3e 03 f9 6c 32 bc fd 05 8d fd 
3c aa a3 e8 a9 9f 9d eb 50 f3 af 57 ad f6 22 aa 
5e 39 0f 7d f7 a6 92 96 a7 55 3d c1 0a a3 1f 6b 
14 f9 70 1a e3 5f e2 8c 44 0a df 4d 4e a9 c0 26 
47 43 87 35 a4 1c 65 b9 e0 16 ba f4 ae bf 7a d2 
54 99 32 d1 f0 85 57 68 10 93 ed 9c be 2c 97 4e 
13 11 1d 7f e3 94 4a 17 f3 07 a7 8b 4d 2b 30 c5

由此可见这两种算法生成的Round Key也不一样。为了使得这两种算法生成的key基本一致,我们需要修改相关的算法代码:

#define SWAP(x)                            __builtin_bswap32(x)
int AES_set_encrypt_key(const uint8_t *userKey, const uint32_t bits, AES_KEY *key)
{
    uint32_t i, v1, v2, v3, v4, v5;

    if (bits != 128) return -1;

    key->rounds = 10;
    initialize_aes_sbox(key->sbox);

    v1 = key->rd_key[0] = SWAP(*(uint32_t *)(userKey +  0));
    v2 = key->rd_key[1] = SWAP(*(uint32_t *)(userKey +  4));
    v3 = key->rd_key[2] = SWAP(*(uint32_t *)(userKey +  8));
    v4 = key->rd_key[3] = SWAP(*(uint32_t *)(userKey + 12));

    uint8_t *sbox = key->sbox;

    for (i = 1; i <= key->rounds; i++) {
        v5 = sbox[(v4 >> 24) & 0xff] <<  0 |
                sbox[(v4 >> 16) & 0xff] << 24 |
                sbox[(v4 >>  8) & 0xff] << 16 |
                sbox[(v4 >>  0) & 0xff] <<  8;
        v1 = RCON(i) ^ v5 ^ v1;
        v2 = v1 ^ v2;
        v3 = v2 ^ v3;
        v4 = v3 ^ v4;

        key->rd_key[4 * i + 0] = v1;
        key->rd_key[4 * i + 1] = v2;
        key->rd_key[4 * i + 2] = v3;
        key->rd_key[4 * i + 3] = v4;
    }

    return 0;
}

这里我们可以看到代码中实际上是对user key 每四个字节为一组,进行了字节交换,于是user key变成了:

0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0b, 0x0a, 0x09, 0x08, 0x0f, 0x0e, 0x0d, 0x0c

由此生成的Round Key就变成了:

 --------------------- AES 128 ENC EXPANDED KEY -------------------------
00000000: 03 02 01 00 07 06 05 04 0b 0a 09 08 0f 0e 0d 0c  ................
00000010: fd 74 aa d6 fa 72 af d2 f1 78 a6 da fe 76 ab d6  .t...r...x...v..
00000020: 0b cf 92 b6 f1 bd 3d 64 00 c5 9b be fe b3 30 68  ......=d......0h
00000030: 4e 74 ff b6 bf c9 c2 d2 bf 0c 59 6c 41 bf 69 04  Nt........YlA.i.
00000040: bc f7 f7 47 03 3e 35 95 bc 32 6c f9 fd 8d 05 fd  ...G.>5..2l.....
00000050: e8 a3 aa 3c eb 9d 9f a9 57 af f3 50 aa 22 f6 ad  ...<....W..P."..
00000060: 7d 0f 39 5e 96 92 a6 f7 c1 3d 55 a7 6b 1f a3 0a  }.9^.....=U.k...
00000070: 1a 70 f9 14 8c e2 5f e3 4d df 0a 44 26 c0 a9 4e  .p...._.M..D&..N
00000080: 35 87 43 47 b9 65 1c a4 f4 ba 16 e0 d2 7a bf ae  5.CG.e.......z..
00000090: d1 32 99 54 68 57 85 f0 9c ed 93 10 4e 97 2c be  .2.ThW......N.,.
000000a0: 7f 1d 11 13 17 4a 94 e3 8b a7 07 f3 c5 30 2b 4d  .....J.......0+M

仔细看的话,可以发现这176字节的Round Key也是每四个字节为一组,进行的字节交换。

NOTE:

关于Round Keys生成算法,可以查看下面的参考文档:

a. https://en.wikipedia.org/wiki/Rijndael_key_schedule

b. http://www.samiam.org/key-schedule.html

2. 加密过程

请先看一下下面这张图:

2016_10_23_aes

 

图的左半部分是原加密算法的实现,而图的右半部分是修改后能得到与标准算法结果一样的算法实现,可以看到这两个算法的不同在于输入的文本与Round Key的顺序发生的改变(可以看到它们也是每四个字节为一组,进行了字节交换):

 

2016_10_23_aes_mark

 

而最终在Add Round Key之后的结果,在进行每四个字节为一组进行字节交换之后,就与标准算法的结果一致了。对应相关代码的改动就是在进行加密算法之前及之后对文本先进行字节替换:

void AES_encrypt(const uint8_t *text, uint8_t *cipher, const AES_KEY *key)
{
    uint32_t v1, v2, v3, v4;
    uint32_t v11, v12, v13, v14;
    uint32_t v17, v18, v20;

    v1 = key->rd_key[0] ^ SWAP(*(uint32_t *)(text +  0));
    v2 = key->rd_key[1] ^ SWAP(*(uint32_t *)(text +  4));
    v3 = key->rd_key[2] ^ SWAP(*(uint32_t *)(text +  8));
    v4 = key->rd_key[3] ^ SWAP(*(uint32_t *)(text + 12));

    const uint8_t *sbox = key->sbox;

    for (v20 = 1; v20 < 10; v20++) {
        // ...
    }
    // ...
    *(uint32_t *)(cipher +  0) = SWAP(key->rd_key[4 * v20 + 0] ^
            (sbox[(v1 >> 24) & 0xFF] << 24 | sbox[(v2 >> 16) & 0xFF] << 16 | sbox[(v3 >>  8) & 0xFF] <<  8 | sbox[(v4 >>  0) & 0xFF] <<  0));

    *(uint32_t *)(cipher +  4) = SWAP(key->rd_key[4 * v20 + 1] ^
            (sbox[(v1 >>  0) & 0xFF] <<  0 | sbox[(v2 >> 24) & 0xFF] << 24 | sbox[(v3 >> 16) & 0xFF] << 16 | sbox[(v4 >>  8) & 0xFF] <<  8));

    *(uint32_t *)(cipher +  8) = SWAP(key->rd_key[4 * v20 + 2] ^
            (sbox[(v1 >>  8) & 0xFF] <<  8 | sbox[(v2 >>  0) & 0xFF] <<  0 | sbox[(v3 >> 24) & 0xFF] << 24 | sbox[(v4 >> 16) & 0xFF] << 16));

    *(uint32_t *)(cipher + 12) = SWAP(key->rd_key[4 * v20 + 3] ^
            (sbox[(v1 >> 16) & 0xFF] << 16 | sbox[(v2 >>  8) & 0xFF] <<  8 | sbox[(v3 >>  0) & 0xFF] <<  0 | sbox[(v4 >> 24) & 0xFF] << 24));
}

虽然生成的Round Key与标准算法有些差异,但是整个算法得到的结果是一致的。

解密算法不说了,想了解的看代码。

最后我们只需要一个简单的宏,就可以选择是否使用标准算法:

#if STANDARD_AS_OPENSSL == 1
#define SWAP(x)                            __builtin_bswap32(x)
#else
#define SWAP(x)                            (x)
#endif

NOTE:

由于使用了gcc built-in function, windows中可能编译不过,但在Mac OS下可使用GCC正常编译。SWAP(x)可使用如下代码:

static inline uint32_t SWAP(uint32_t x)
{
    return ((x << 24) & 0xff000000) |
           ((x <<  8) & 0x00ff0000) |
           ((x >>  8) & 0x0000ff00) |
           ((x >> 24) & 0x000000ff);
}

相关代码已经提交到github.com/brobwind/bro_aes中:

$ git clone https://github.com/brobwind/bro_aes.git

编译时,要生成标准算法的结果执行如下命令:

$ STANDARD_AS_OPENSSL=1 make
  • 相关的参考文档
  1. https://en.wikipedia.org/wiki/Rijndael_key_schedule
  2. http://www.samiam.org/key-schedule.html

《STM32: 实现AES 128-bit加密算法 - 标准实现》有4个想法

    1. 可以的,从http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489g/Cihjgdid.html可以看到:
      REV
      converts 32-bit big-endian data into little-endian data or 32-bit little-endian data into big-endian data.

      __builtin_bswap32(x)就是要实现这样的功能。
      实际上gcc的__built_bswap32(x)也是由rev指令实现的:gcc-arm-none-eabi-5_4-2016q3-20160926/src/gcc/gcc/config/arm/arm.md:
      (define_insn "*arm_rev"
      [(set (match_operand:SI 0 "s_register_operand" "=l,l,r")
      (bswap:SI (match_operand:SI 1 "s_register_operand" "l,l,r")))]
      "arm_arch6"
      "@
      rev\t%0, %1
      rev%?\t%0, %1
      rev%?\t%0, %1"
      [(set_attr "arch" "t1,t2,32")
      (set_attr "length" "2,2,4")
      (set_attr "predicable" "no,yes,yes")
      (set_attr "predicable_short_it" "no")
      (set_attr "type" "rev")]
      )

  1. “用此算法加密过的文本,无法使用openssl或者是网上提供的在线AES工具进行加密或者解密,这就相对地增加了破译的难度”

    这句话是没学过密码学吧。 现代密码学的一个原则就是 要使用标准的加密算法,密码的安全性是靠密钥来保证的。 因为标准算法是经过大量的使用检验过的,是可以认为强度很高的,而自己自作聪明的修改算法是有可能降低加密算法的强度的。

    1. 这里只是从一般人的角度或者说从我自身的角度来看待这个问题的。当分析到可执行文件中存在AES S-Box(63,7c,77,7b…)这些数据的时候,很容易就会想到用到了aes加密,那么,我们只需要再找到它加密所需的key, 就可能会用现有的标准的aes加密算法去验证这个加解密算法。
      同时我们可以看到,这种非标准的aes加密算法,只是对输入的数据进行了处理,并没有改变算法本身。

发表评论

电子邮件地址不会被公开。 必填项已用*标注