3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 **** AES en/decryption *********************************************************
21 Copyright Volker van Nek, 2012 - 2016
24 This file shows the polynomial aspects of the AES algorithm and its functions
25 repeatedly convert bytes to polynomials and vice versa. The additional file
26 gf/aes2.mac directly works at byte level.
28 In this file states are implemented as 4x4 matrices.
30 The examples at the bottom of this file are taken from the AES specification
31 document (URL see below). One of these is also demonstrated in the excellent
32 Rijndael_Animation_v4_en.swf (http://www.formaestudio.com/rijndaelinspector/)
33 which I highly recommend to set next to this code.
36 June, 2015: New user interface.
37 The functions make_state and coerce_state provide flexible support for
38 different data types. States can be build from octet-strings, integers and
39 lists of octets and can be coerced back to these three types.
40 The new interface is based on new functions in share/stringproc. So you
41 should have installed Maxima version 5.37 or higher.
43 March, 2016: mix_columns polynomial version added.
44 The abstract polynomial description of the mix_columns step is added as
45 mix_columns_2 and inv_mix_columns_2. If cipher or inv_cipher should use these
46 alternative versions the code of cipher resp. inv_cipher has to be changed
51 /* sub_bytes **************************************************************** */
53 %_byte_sub[byte] := block([poly, inv, p1, p2],
54 gf_minimal_set(2, x^8+x^4+x^3+x+1),
56 inv : if poly = 0 then 0 else gf_inv(poly),
58 gf_minimal_set(2, x^8+1),
61 poly : gf_add(gf_mult(p1, inv), p2),
64 for byte:0 thru 255. do %_byte_sub[byte]$
66 sub_bytes(state) := matrixmap(lambda([byte], %_byte_sub[byte]), state)$
69 /* inv_sub_bytes ************************************************************ */
71 %_inv_byte_sub[byte] := block([poly, inv, p1, p2],
72 gf_minimal_set(2, x^8+1),
76 poly : gf_div(gf_sub(poly, p2), p1),
78 gf_minimal_set(2, x^8+x^4+x^3+x+1),
79 inv : if poly = 0 then 0 else gf_inv(poly),
82 for byte:0 thru 255. do %_inv_byte_sub[byte]$
84 inv_sub_bytes(state) := matrixmap(lambda([byte], %_inv_byte_sub[byte]), state)$
87 /* shift_rows *************************************************************** */
89 rotate(row, i) := append(rest(row, i), rest(row, i-4))$
91 shift_rows(state) := apply('matrix, create_list(rotate(state[i], i-1), i,1,4))$
94 /* inv_shift_rows *********************************************************** */
96 inv_rotate(row, i) := append(rest(row, 4-i), rest(row, -i))$
98 inv_shift_rows(M) := apply('matrix, create_list(inv_rotate(M[i], i-1), i,1,4))$
101 /* mix_columns ************************************************************** */
103 gf_set_data(2, x^8+x^4+x^3+x+1)$ /* gf_invert_by_lu (see below) needs a field */
105 mat_n2p(num_mat) := matrixmap('gf_n2p, num_mat)$
106 mat_p2n(poly_mat) := matrixmap('gf_p2n, poly_mat)$
108 %_MIX_COLUMNS : mat_n2p( matrix(
114 mix_columns(state) := block([mixed],
115 state : mat_n2p(state),
118 mixed : addcol(mixed, gf_matmult(%_MIX_COLUMNS, col(state, i))),
122 /* mix_columns (polynomial version) ***************************************** */
124 ef_minimal_set(x^4+1)$ /* needs gf_set_data(2, x^8+x^4+x^3+x+1)$ */
126 aes_p2c(p) := transpose(reverse(ef_p2l(p,4)))$ /* poly to column vector */
127 aes_c2p(c) := ef_l2p(reverse(first(transpose(c))))$ /* column vector to poly */
129 %_p3 : 3*x^3+x^2+x+2$
131 mix_columns_2(state) := block([mixed],
134 mixed : addcol(mixed, aes_p2c(ef_mult(%_p3, aes_c2p(col(state, i))))),
138 /* inv_mix_columns ********************************************************** */
140 %_INV_MIX_COLUMNS : gf_invert_by_lu(%_MIX_COLUMNS)$
142 inv_mix_columns(state) := block([mixed],
143 state : mat_n2p(state),
146 mixed : addcol(mixed, gf_matmult(%_INV_MIX_COLUMNS, col(state, i))),
150 /* inv_mix_columns (polynomial version) ************************************* */
152 %_p3_inv : ef_inv(%_p3)$
154 inv_mix_columns_2(state) := block([mixed],
157 mixed : addcol(mixed, aes_p2c(ef_mult(%_p3_inv, aes_c2p(col(state, i))))),
161 /* add_round_key ************************************************************ */
163 add_round_key(state, key) :=
164 matrixmap('?logxor, state, key)$
165 /* with polynomial addition:
166 mat_p2n( gf_matadd( mat_n2p(state), mat_n2p(key) ))$
170 /* key_expansion ************************************************************ */
173 matrix([1, 2, 4, 8, 16., 32., 64., 128., 27., 54.]),
174 zeromatrix(3, 10.) )$
176 rot_word(col) := addrow(submatrix(1, col), col[1])$
178 key_expansion1(col1, col4, i) := block([rcon_col],
179 col1 : mat_n2p(col1),
180 col4 : matrixmap(lambda([byte], %_byte_sub[byte]), rot_word(col4)),
181 col4 : mat_n2p(col4),
182 rcon_col : mat_n2p(col(%_rcon, i)),
183 mat_p2n( gf_matadd(col1, col4, rcon_col) ))$
185 key_expansion2(col_i, col_j) :=
186 mat_p2n( gf_matadd( mat_n2p(col_i), mat_n2p(col_j) ))$
188 next_round_key(old, n) := block([new],
189 new : key_expansion1(col(old, 1), col(old, 4), n),
191 new : addcol(new, key_expansion2(col(old, i), col(new, i-1))),
194 %_round_key : make_array(any, 11.)$
196 key_expansion(key) := (
198 %_round_key[0] : key,
200 %_round_key[n] : next_round_key(%_round_key[n-1], n) )$
203 /* cipher ******************************************************************* */
206 cipher and inv_cipher both return a new state.
209 state : add_round_key(state, %_round_key[0]),
212 state : shift_rows( sub_bytes(state) ),
213 state : mix_columns(state), /* or mix_columns_2 */
214 state : add_round_key(state, %_round_key[i]) ),
216 state : shift_rows( sub_bytes(state) ),
217 add_round_key(state, %_round_key[10.]) )$
220 /* inv_cipher *************************************************************** */
222 inv_cipher(state) := (
223 state : add_round_key(state, %_round_key[10.]),
225 for n:9 step -1 thru 1 do (
226 state : inv_shift_rows( inv_sub_bytes(state) ),
227 state : add_round_key(state, %_round_key[n]),
228 state : inv_mix_columns(state) ), /* or inv_mix_columns_2 */
230 state : inv_shift_rows( inv_sub_bytes(state) ),
231 add_round_key(state, %_round_key[0]) )$
234 /* user interface *********************************************************** */
236 print_block(block) :=
237 printf(true, "~{~{~2,'0x ~}~%~}", block)$
240 Setting ibase causes SBCL not to compile make_state.
242 arg can be an octet-string, an integer or a list of octets.
243 The return value is a 4x4 matrix.
245 make_state(arg) := block([state, c:1, r:1, ibase:16.],
246 if stringp(arg) then arg : parse_string(sconcat(0, arg)),
247 if integerp(arg) then arg : number_to_octets(arg),
248 while length(arg) < 16. do arg : cons(0, arg),
249 state : zeromatrix(4,4),
250 for octet in arg do (
252 if (r : r+1) = 5 then (r : 1, c : c+1) ),
256 The first argument must be the state, a 4x4 matrix.
257 The second optional argument can be 'list (the default), 'number or 'string.
258 Accordingly the return value is a list of octets, an integer or an octet-string.
260 coerce_state([args]) := block([state:args[1], type, res],
261 res : flatten(args(transpose(state))),
262 type : if length(args) = 2 then args[2],
263 if type = 'number then res : octets_to_number(res),
264 if type = 'string then res : printf(false, "~{~2,'0x~}", res),
268 /* compilation ************************************************************** */
271 If speed matters it is recommended to compile the functions rather than to
272 compile the file. When compiling use input base 10.
274 SBCL doesn't want to compile make_state.
279 sub_bytes, inv_sub_bytes,
280 rotate, shift_rows, inv_rotate, inv_shift_rows,
281 mat_n2p, mat_p2n, mix_columns, inv_mix_columns,
282 aes_p2c, aes_c2p, mix_columns_2, inv_mix_columns_2,
284 rot_word, key_expansion1, key_expansion2, next_round_key, key_expansion,
286 print_block, coerce_state )$
290 /* examples ***************************************************************** */
293 http://csrc.nist.gov/publications/fips/fips197/fips-197.pdf
294 (Nov 26, 2001, AES specification)
299 (%i2) ibase: obase: 16.$
301 (%i3) state: make_state("00112233445566778899aabbccddeeff")$
303 (%i4) key: make_state("000102030405060708090a0b0c0d0e0f")$
305 (%i5) key_expansion(key)$
307 (%i6) state: cipher(state)$
309 (%i6) coerce_state(state);
310 (%o7) [69,0C4,0E0,0D8,6A,7B,4,30,0D8,0CD,0B7,80,70,0B4,0C5,5A]
311 (%i8) state: inv_cipher(state)$
313 (%i9) coerce_state(state, 'string);
314 (%o9) 00112233445566778899AABBCCDDEEFF
317 fips-197.pdf, page 33/34:
319 (%i0A) state: make_state("3243f6a8885a308d313198a2e0370734")$
321 (%i0B) key: make_state("2b7e151628aed2a6abf7158809cf4f3c")$
323 (%i0C) key_expansion(key)$
341 (%i0F) state: cipher(state)$
343 (%i10) print_block(state)$
349 (%i11) state: inv_cipher(state)$
351 (%i12) print_block(state)$