ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aes_block.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file aes_block.h
6 * @brief Compact table-free software AES key schedule + single-block encrypt (FIPS 197) - one source of truth.
7 *
8 * The SSH AES-256-GCM, SSH AES-256-CTR, and QUIC/TLS AES-128 modules each carried a byte-for-byte copy of the
9 * same software AES (key expansion + the SubBytes/ShiftRows/MixColumns/AddRoundKey block cipher). They differ
10 * ONLY in the key size: parameterize on @c nk (key words: 4 for AES-128, 8 for AES-256) and @c nr (rounds: 10
11 * or 14) and one implementation serves all three.
12 *
13 * Only the S-box (@c PC_AES_SBOX from aes_sbox.h) and the GF(2^8) @c xtime are used - no large T-tables - so
14 * this is the same "constant-time by structure" shape the copies had (a table-indexed S-box; no secret-
15 * dependent branching introduced here). Header-only and pure (S-box + @c <string.h>), so it is compiled into
16 * only the NATIVE software path of each module; the @c \#ifdef ARDUINO mbedTLS/HW branches are untouched.
17 *
18 * @author Douglas Quigg (dstroy0)
19 * @date 2026
20 */
21
22#ifndef PROTOCORE_AES_BLOCK_H
23#define PROTOCORE_AES_BLOCK_H
24
25#include "crypto/cipher/aes_sbox.h" // PC_AES_SBOX
26#include <stdint.h>
27#include <string.h>
28
29/** @brief GF(2^8) multiply-by-2 (xtime) for the AES MixColumns step. */
30inline uint8_t pc_aes_xtime(uint8_t a)
31{
32 return (uint8_t)((a << 1) ^ ((a >> 7) ? 0x1bu : 0x00u));
33}
34
35/** @brief AES SubWord (FIPS 197 sec 5.2): apply the S-box to each of the four bytes of a 32-bit word. */
36inline uint32_t pc_aes_sub_word(uint32_t w)
37{
38 return ((uint32_t)PC_AES_SBOX[w >> 24] << 24) | ((uint32_t)PC_AES_SBOX[(w >> 16) & 0xff] << 16) |
39 ((uint32_t)PC_AES_SBOX[(w >> 8) & 0xff] << 8) | (uint32_t)PC_AES_SBOX[w & 0xff];
40}
41
42/** @brief AES RotWord (FIPS 197 sec 5.2): cyclically rotate a 32-bit word one byte left. */
43inline uint32_t pc_aes_rot_word(uint32_t w)
44{
45 return (w << 8) | (w >> 24);
46}
47
48/**
49 * @brief AES key expansion (FIPS 197 sec 5.2). @p nk key words (4=AES-128, 8=AES-256); @p rk receives
50 * 4*(@p nk + 7) round-key words (44 for AES-128, 60 for AES-256).
51 */
52inline void pc_aes_key_expand(const uint8_t *key, int nk, uint32_t *rk)
53{
54 // Rcon[1..10] (index 0 unused); AES-128 uses up to [10], AES-256 up to [7].
55 static const uint8_t RCON[11] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
56 for (int i = 0; i < nk; i++)
57 {
58 rk[i] = ((uint32_t)key[4 * i] << 24) | ((uint32_t)key[4 * i + 1] << 16) | ((uint32_t)key[4 * i + 2] << 8) |
59 (uint32_t)key[4 * i + 3];
60 }
61
62 int total = 4 * (nk + 7);
63 for (int i = nk; i < total; i++)
64 {
65 uint32_t t = rk[i - 1];
66 if (i % nk == 0)
67 {
68 t = pc_aes_sub_word(pc_aes_rot_word(t)) ^ ((uint32_t)RCON[i / nk] << 24);
69 }
70 else if (nk > 6 && i % nk == 4) // AES-256 applies an extra SubWord at the mid-point of each 8-word run.
71 {
72 t = pc_aes_sub_word(t);
73 }
74 rk[i] = rk[i - nk] ^ t;
75 }
76}
77
78/**
79 * @brief AES single-block encrypt (FIPS 197 sec 5.1), @p nr rounds (10=AES-128, 14=AES-256). State is
80 * column-major: s[col*4 + row]. @p rk is the schedule from pc_aes_key_expand.
81 */
82inline void pc_aes_encrypt_block(const uint32_t *rk, int nr, const uint8_t in[16], uint8_t out[16])
83{
84 uint8_t s[16];
85 for (int i = 0; i < 16; i++)
86 {
87 s[i] = in[i] ^ (uint8_t)(rk[i / 4] >> (24 - (i % 4) * 8));
88 }
89
90 for (int r = 1; r < nr; r++)
91 {
92 for (int i = 0; i < 16; i++)
93 {
94 s[i] = PC_AES_SBOX[s[i]];
95 }
96
97 uint8_t t;
98 t = s[1]; // row 1 <<< 1
99 s[1] = s[5];
100 s[5] = s[9];
101 s[9] = s[13];
102 s[13] = t;
103 t = s[2]; // row 2 <<< 2
104 s[2] = s[10];
105 s[10] = t;
106 t = s[6];
107 s[6] = s[14];
108 s[14] = t;
109 t = s[15]; // row 3 <<< 3
110 s[15] = s[11];
111 s[11] = s[7];
112 s[7] = s[3];
113 s[3] = t;
114
115 for (int c = 0; c < 4; c++)
116 {
117 uint8_t a = s[c * 4];
118 uint8_t b = s[c * 4 + 1];
119 uint8_t cc = s[c * 4 + 2];
120 uint8_t d = s[c * 4 + 3];
121 uint8_t e = a ^ b ^ cc ^ d;
122 s[c * 4] = a ^ e ^ pc_aes_xtime(a ^ b);
123 s[c * 4 + 1] = b ^ e ^ pc_aes_xtime(b ^ cc);
124 s[c * 4 + 2] = cc ^ e ^ pc_aes_xtime(cc ^ d);
125 s[c * 4 + 3] = d ^ e ^ pc_aes_xtime(d ^ a);
126 }
127
128 for (int i = 0; i < 16; i++)
129 {
130 s[i] ^= (uint8_t)(rk[r * 4 + i / 4] >> (24 - (i % 4) * 8));
131 }
132 }
133
134 for (int i = 0; i < 16; i++)
135 {
136 s[i] = PC_AES_SBOX[s[i]];
137 }
138
139 uint8_t t;
140 t = s[1];
141 s[1] = s[5];
142 s[5] = s[9];
143 s[9] = s[13];
144 s[13] = t;
145 t = s[2];
146 s[2] = s[10];
147 s[10] = t;
148 t = s[6];
149 s[6] = s[14];
150 s[14] = t;
151 t = s[15];
152 s[15] = s[11];
153 s[11] = s[7];
154 s[7] = s[3];
155 s[3] = t;
156
157 for (int i = 0; i < 16; i++)
158 {
159 s[i] ^= (uint8_t)(rk[nr * 4 + i / 4] >> (24 - (i % 4) * 8));
160 }
161
162 memcpy(out, s, 16);
163}
164
165#endif // PROTOCORE_AES_BLOCK_H
uint8_t pc_aes_xtime(uint8_t a)
GF(2^8) multiply-by-2 (xtime) for the AES MixColumns step.
Definition aes_block.h:30
void pc_aes_key_expand(const uint8_t *key, int nk, uint32_t *rk)
AES key expansion (FIPS 197 sec 5.2). nk key words (4=AES-128, 8=AES-256); rk receives 4*(nk + 7) rou...
Definition aes_block.h:52
uint32_t pc_aes_sub_word(uint32_t w)
AES SubWord (FIPS 197 sec 5.2): apply the S-box to each of the four bytes of a 32-bit word.
Definition aes_block.h:36
uint32_t pc_aes_rot_word(uint32_t w)
AES RotWord (FIPS 197 sec 5.2): cyclically rotate a 32-bit word one byte left.
Definition aes_block.h:43
void pc_aes_encrypt_block(const uint32_t *rk, int nr, const uint8_t in[16], uint8_t out[16])
AES single-block encrypt (FIPS 197 sec 5.1), nr rounds (10=AES-128, 14=AES-256). State is column-majo...
Definition aes_block.h:82
The AES forward S-box (FIPS 197 Figure 7) - one shared copy.