ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
aes_block.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#ifndef PROTOCORE_AES_BLOCK_H
5#define PROTOCORE_AES_BLOCK_H
6
8#include "memoria_operor/memoria_operor.h"
9#include "protocore_config.h" // the entry point: protocore_types.h for the widths
10
12
13/**
14 * @file aes_block.h
15 * @brief Table-free software AES key schedule and single-block encrypt (FIPS 197).
16 *
17 * The shared software AES primitive for the whole library: the key expansion of FIPS 197 sec 5.2 and
18 * the SubBytes/ShiftRows/MixColumns/AddRoundKey block of sec 5.1, parameterized on @c nk (key words: 4
19 * for AES-128, 8 for AES-256) and @c nr (rounds: 10 or 14), so one implementation serves both key
20 * sizes. The software arms of AES-256-CTR, AES-256-GCM, AES-CCM and AES-CMAC run on it.
21 *
22 * Only the S-box (@c PROTOCORE_AES_SBOX from aes_sbox.h) and the GF(2^8) @c xtime are used, no large
23 * T-tables: a table-indexed S-box and no secret-dependent branch. The five functions below stay inline
24 * in this header, where the per-block loops of those arms call them; ::AesBlock reaches the same key
25 * expansion and the same block through the namespace.
26 *
27 * The round-key schedule and the block are the CALLER's: both entries write into the buffers its args
28 * name and hold neither past the call. @c work arrives goes unread: nothing is carried
29 * from one call to the next, so this module states no borrow and neither takes those bytes, holds
30 * them, releases them, nor wipes them.
31 *
32 * The five functions above are the same key expansion and the same block plus the three one-liners
33 * they are built from, reached without the namespace, for a caller whose per-block loop inlines them.
34 *
35 * @author Douglas Quigg (dstroy0)
36 * @date 2026
37 */
38
39/** @brief GF(2^8) multiply-by-2 (xtime) for the AES MixColumns step. */
41{
42 return (uint8_t)((a << 1) ^ ((a >> 7) ? 0x1bu : 0x00u));
43}
44
45/** @brief AES SubWord (FIPS 197 sec 5.2): apply the S-box to each of the four bytes of a 32-bit word. */
47{
48 return ((uint32_t)PROTOCORE_AES_SBOX[w >> 24] << 24) | ((uint32_t)PROTOCORE_AES_SBOX[(w >> 16) & 0xff] << 16) |
49 ((uint32_t)PROTOCORE_AES_SBOX[(w >> 8) & 0xff] << 8) | (uint32_t)PROTOCORE_AES_SBOX[w & 0xff];
50}
51
52/** @brief AES RotWord (FIPS 197 sec 5.2): cyclically rotate a 32-bit word one byte left. */
54{
55 return (w << 8) | (w >> 24);
56}
57
58/**
59 * @brief AES key expansion (FIPS 197 sec 5.2). @p nk key words (4=AES-128, 8=AES-256); @p rk receives
60 * 4*(@p nk + 7) round-key words (44 for AES-128, 60 for AES-256).
61 */
62PROTOCORE_INLINE void protocore_aes_key_expand(const uint8_t *key, int nk, uint32_t *rk)
63{
64 // Rcon[1..10] (index 0 unused); AES-128 uses up to [10], AES-256 up to [7].
65 static const uint8_t RCON[11] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
66 for (int i = 0; i < nk; i++)
67 {
68 rk[i] = ((uint32_t)key[4 * i] << 24) | ((uint32_t)key[4 * i + 1] << 16) | ((uint32_t)key[4 * i + 2] << 8) |
69 (uint32_t)key[4 * i + 3];
70 }
71
72 int total = 4 * (nk + 7);
73 for (int i = nk; i < total; i++)
74 {
75 uint32_t t = rk[i - 1];
76 if (i % nk == 0)
77 {
78 t = protocore_aes_sub_word(protocore_aes_rot_word(t)) ^ ((uint32_t)RCON[i / nk] << 24);
79 }
80 else if (nk > 6 && i % nk == 4) // AES-256 applies an extra SubWord at the mid-point of each 8-word run.
81 {
83 }
84 rk[i] = rk[i - nk] ^ t;
85 }
86}
87
88/**
89 * @brief AES single-block encrypt (FIPS 197 sec 5.1), @p nr rounds (10=AES-128, 14=AES-256). State is
90 * column-major: s[col*4 + row]. @p rk is the schedule from protocore_aes_key_expand.
91 */
92PROTOCORE_INLINE void protocore_aes_encrypt_block(const uint32_t *rk, int nr, const uint8_t in[16], uint8_t out[16])
93{
94 uint8_t s[16];
95 for (int i = 0; i < 16; i++)
96 {
97 s[i] = in[i] ^ (uint8_t)(rk[i / 4] >> (24 - (i % 4) * 8));
98 }
99
100 for (int r = 1; r < nr; r++)
101 {
102 for (int i = 0; i < 16; i++)
103 {
104 s[i] = PROTOCORE_AES_SBOX[s[i]];
105 }
106
107 uint8_t t;
108 t = s[1]; // row 1 <<< 1
109 s[1] = s[5];
110 s[5] = s[9];
111 s[9] = s[13];
112 s[13] = t;
113 t = s[2]; // row 2 <<< 2
114 s[2] = s[10];
115 s[10] = t;
116 t = s[6];
117 s[6] = s[14];
118 s[14] = t;
119 t = s[15]; // row 3 <<< 3
120 s[15] = s[11];
121 s[11] = s[7];
122 s[7] = s[3];
123 s[3] = t;
124
125 for (int c = 0; c < 4; c++)
126 {
127 uint8_t a = s[c * 4];
128 uint8_t b = s[c * 4 + 1];
129 uint8_t cc = s[c * 4 + 2];
130 uint8_t d = s[c * 4 + 3];
131 uint8_t e = a ^ b ^ cc ^ d;
132 s[c * 4] = a ^ e ^ protocore_aes_xtime(a ^ b);
133 s[c * 4 + 1] = b ^ e ^ protocore_aes_xtime(b ^ cc);
134 s[c * 4 + 2] = cc ^ e ^ protocore_aes_xtime(cc ^ d);
135 s[c * 4 + 3] = d ^ e ^ protocore_aes_xtime(d ^ a);
136 }
137
138 for (int i = 0; i < 16; i++)
139 {
140 s[i] ^= (uint8_t)(rk[r * 4 + i / 4] >> (24 - (i % 4) * 8));
141 }
142 }
143
144 for (int i = 0; i < 16; i++)
145 {
146 s[i] = PROTOCORE_AES_SBOX[s[i]];
147 }
148
149 uint8_t t;
150 t = s[1];
151 s[1] = s[5];
152 s[5] = s[9];
153 s[9] = s[13];
154 s[13] = t;
155 t = s[2];
156 s[2] = s[10];
157 s[10] = t;
158 t = s[6];
159 s[6] = s[14];
160 s[14] = t;
161 t = s[15];
162 s[15] = s[11];
163 s[11] = s[7];
164 s[7] = s[3];
165 s[3] = t;
166
167 for (int i = 0; i < 16; i++)
168 {
169 s[i] ^= (uint8_t)(rk[nr * 4 + i / 4] >> (24 - (i % 4) * 8));
170 }
171
172 EMBED_CALL(memor.cpy, MemoriaCfg, .dst = out, .src = s, .bytes = 16);
173}
174
175/** @brief Dispatch table. Addressed by offset, so the layout is asserted below. */
176typedef struct
177{
178 proto_bool (*key_expand)(uint8_t *, const uint8_t *, int, uint32_t *);
179 proto_bool (*encrypt_block)(uint8_t *, const uint32_t *, int, const uint8_t *, uint8_t *);
180} AesBlockNs;
181PROTOCORE_NS_LAYOUT(AesBlockNs, key_expand, encrypt_block);
182
183/**
184 * @brief Expand a key into 4 * (nk + 7) round-key words.
185 * @param work PROTOCORE_AES_BLOCK_BORROW bytes the caller took. Not held past the call.
186 * @param key 4 * nk key bytes
187 * @param nk key words: 4 for AES-128, 8 for AES-256
188 * @param rk 4 * (nk + 7) round-key words
189 * @return PROTO_TRUE on success.
190 */
191proto_bool protocore_aes_block_key_expand(uint8_t *work, const uint8_t *key, int nk, uint32_t *rk);
192/**
193 * @brief Encrypt one 16-byte block under that schedule.
194 * @param work PROTOCORE_AES_BLOCK_BORROW bytes the caller took. Not held past the call.
195 * @param rk the schedule AesBlockNs::key_expand wrote
196 * @param nr rounds: 10 for AES-128, 14 for AES-256
197 * @param in 16 input bytes
198 * @param out 16 output bytes; may alias in
199 * @return PROTO_TRUE on success.
200 */
201proto_bool protocore_aes_block_encrypt_block(uint8_t *work, const uint32_t *rk, int nr, const uint8_t *in,
202 uint8_t *out);
203
204/** @brief Module namespace. */
207
209
210#endif // PROTOCORE_AES_BLOCK_H
PROTOCORE_INLINE uint32_t protocore_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:53
PROTOCORE_NS AesBlockNs AesBlock PROTOCORE_UNUSED
Module namespace.
Definition aes_block.h:205
PROTOCORE_INLINE void protocore_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:92
PROTOCORE_INLINE uint8_t protocore_aes_xtime(uint8_t a)
GF(2^8) multiply-by-2 (xtime) for the AES MixColumns step.
Definition aes_block.h:40
proto_bool protocore_aes_block_key_expand(uint8_t *work, const uint8_t *key, int nk, uint32_t *rk)
Expand a key into 4 * (nk + 7) round-key words.
PROTOCORE_INLINE uint32_t protocore_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:46
proto_bool protocore_aes_block_encrypt_block(uint8_t *work, const uint32_t *rk, int nr, const uint8_t *in, uint8_t *out)
Encrypt one 16-byte block under that schedule.
PROTOCORE_INLINE void protocore_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:62
The AES forward S-box (FIPS 197 Figure 7) - one shared copy.
#define PROTOCORE_INLINE
Linkage for a leaf primitive whose body is cheaper than the call that reaches it.
#define PROTOCORE_NS_LAYOUT(T,...)
Pin every dispatch slot of a table that is nothing but function pointers.
#define PROTOCORE_NS
Storage for a dispatch table. The const is load bearing.
Dispatch table. Addressed by offset, so the layout is asserted below.
Definition aes_block.h:177
proto_bool(* key_expand)(uint8_t *, const uint8_t *, int, uint32_t *)
Definition aes_block.h:178
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97