1<?php
2
3if (class_exists('ParagonIE_Sodium_Core_AES', false)) {
4 return;
5}
6
7/**
8 * Bitsliced implementation of the AES block cipher.
9 *
10 * Based on the implementation provided by BearSSL.
11 *
12 * @internal This should only be used by sodium_compat
13 */
14class ParagonIE_Sodium_Core_AES extends ParagonIE_Sodium_Core_Util
15{
16 /**
17 * @var int[] AES round constants
18 */
19 private static $Rcon = array(
20 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36
21 );
22
23 /**
24 * Mutates the values of $q!
25 *
26 * @param ParagonIE_Sodium_Core_AES_Block $q
27 * @return void
28 */
29 public static function sbox(ParagonIE_Sodium_Core_AES_Block $q)
30 {
31 /**
32 * @var int $x0
33 * @var int $x1
34 * @var int $x2
35 * @var int $x3
36 * @var int $x4
37 * @var int $x5
38 * @var int $x6
39 * @var int $x7
40 */
41 $x0 = $q[7] & self::U32_MAX;
42 $x1 = $q[6] & self::U32_MAX;
43 $x2 = $q[5] & self::U32_MAX;
44 $x3 = $q[4] & self::U32_MAX;
45 $x4 = $q[3] & self::U32_MAX;
46 $x5 = $q[2] & self::U32_MAX;
47 $x6 = $q[1] & self::U32_MAX;
48 $x7 = $q[0] & self::U32_MAX;
49
50 $y14 = $x3 ^ $x5;
51 $y13 = $x0 ^ $x6;
52 $y9 = $x0 ^ $x3;
53 $y8 = $x0 ^ $x5;
54 $t0 = $x1 ^ $x2;
55 $y1 = $t0 ^ $x7;
56 $y4 = $y1 ^ $x3;
57 $y12 = $y13 ^ $y14;
58 $y2 = $y1 ^ $x0;
59 $y5 = $y1 ^ $x6;
60 $y3 = $y5 ^ $y8;
61 $t1 = $x4 ^ $y12;
62 $y15 = $t1 ^ $x5;
63 $y20 = $t1 ^ $x1;
64 $y6 = $y15 ^ $x7;
65 $y10 = $y15 ^ $t0;
66 $y11 = $y20 ^ $y9;
67 $y7 = $x7 ^ $y11;
68 $y17 = $y10 ^ $y11;
69 $y19 = $y10 ^ $y8;
70 $y16 = $t0 ^ $y11;
71 $y21 = $y13 ^ $y16;
72 $y18 = $x0 ^ $y16;
73
74 /*
75 * Non-linear section.
76 */
77 $t2 = $y12 & $y15;
78 $t3 = $y3 & $y6;
79 $t4 = $t3 ^ $t2;
80 $t5 = $y4 & $x7;
81 $t6 = $t5 ^ $t2;
82 $t7 = $y13 & $y16;
83 $t8 = $y5 & $y1;
84 $t9 = $t8 ^ $t7;
85 $t10 = $y2 & $y7;
86 $t11 = $t10 ^ $t7;
87 $t12 = $y9 & $y11;
88 $t13 = $y14 & $y17;
89 $t14 = $t13 ^ $t12;
90 $t15 = $y8 & $y10;
91 $t16 = $t15 ^ $t12;
92 $t17 = $t4 ^ $t14;
93 $t18 = $t6 ^ $t16;
94 $t19 = $t9 ^ $t14;
95 $t20 = $t11 ^ $t16;
96 $t21 = $t17 ^ $y20;
97 $t22 = $t18 ^ $y19;
98 $t23 = $t19 ^ $y21;
99 $t24 = $t20 ^ $y18;
100
101 $t25 = $t21 ^ $t22;
102 $t26 = $t21 & $t23;
103 $t27 = $t24 ^ $t26;
104 $t28 = $t25 & $t27;
105 $t29 = $t28 ^ $t22;
106 $t30 = $t23 ^ $t24;
107 $t31 = $t22 ^ $t26;
108 $t32 = $t31 & $t30;
109 $t33 = $t32 ^ $t24;
110 $t34 = $t23 ^ $t33;
111 $t35 = $t27 ^ $t33;
112 $t36 = $t24 & $t35;
113 $t37 = $t36 ^ $t34;
114 $t38 = $t27 ^ $t36;
115 $t39 = $t29 & $t38;
116 $t40 = $t25 ^ $t39;
117
118 $t41 = $t40 ^ $t37;
119 $t42 = $t29 ^ $t33;
120 $t43 = $t29 ^ $t40;
121 $t44 = $t33 ^ $t37;
122 $t45 = $t42 ^ $t41;
123 $z0 = $t44 & $y15;
124 $z1 = $t37 & $y6;
125 $z2 = $t33 & $x7;
126 $z3 = $t43 & $y16;
127 $z4 = $t40 & $y1;
128 $z5 = $t29 & $y7;
129 $z6 = $t42 & $y11;
130 $z7 = $t45 & $y17;
131 $z8 = $t41 & $y10;
132 $z9 = $t44 & $y12;
133 $z10 = $t37 & $y3;
134 $z11 = $t33 & $y4;
135 $z12 = $t43 & $y13;
136 $z13 = $t40 & $y5;
137 $z14 = $t29 & $y2;
138 $z15 = $t42 & $y9;
139 $z16 = $t45 & $y14;
140 $z17 = $t41 & $y8;
141
142 /*
143 * Bottom linear transformation.
144 */
145 $t46 = $z15 ^ $z16;
146 $t47 = $z10 ^ $z11;
147 $t48 = $z5 ^ $z13;
148 $t49 = $z9 ^ $z10;
149 $t50 = $z2 ^ $z12;
150 $t51 = $z2 ^ $z5;
151 $t52 = $z7 ^ $z8;
152 $t53 = $z0 ^ $z3;
153 $t54 = $z6 ^ $z7;
154 $t55 = $z16 ^ $z17;
155 $t56 = $z12 ^ $t48;
156 $t57 = $t50 ^ $t53;
157 $t58 = $z4 ^ $t46;
158 $t59 = $z3 ^ $t54;
159 $t60 = $t46 ^ $t57;
160 $t61 = $z14 ^ $t57;
161 $t62 = $t52 ^ $t58;
162 $t63 = $t49 ^ $t58;
163 $t64 = $z4 ^ $t59;
164 $t65 = $t61 ^ $t62;
165 $t66 = $z1 ^ $t63;
166 $s0 = $t59 ^ $t63;
167 $s6 = $t56 ^ ~$t62;
168 $s7 = $t48 ^ ~$t60;
169 $t67 = $t64 ^ $t65;
170 $s3 = $t53 ^ $t66;
171 $s4 = $t51 ^ $t66;
172 $s5 = $t47 ^ $t65;
173 $s1 = $t64 ^ ~$s3;
174 $s2 = $t55 ^ ~$t67;
175
176 $q[7] = $s0 & self::U32_MAX;
177 $q[6] = $s1 & self::U32_MAX;
178 $q[5] = $s2 & self::U32_MAX;
179 $q[4] = $s3 & self::U32_MAX;
180 $q[3] = $s4 & self::U32_MAX;
181 $q[2] = $s5 & self::U32_MAX;
182 $q[1] = $s6 & self::U32_MAX;
183 $q[0] = $s7 & self::U32_MAX;
184 }
185
186 /**
187 * Mutates the values of $q!
188 *
189 * @param ParagonIE_Sodium_Core_AES_Block $q
190 * @return void
191 */
192 public static function invSbox(ParagonIE_Sodium_Core_AES_Block $q)
193 {
194 self::processInversion($q);
195 self::sbox($q);
196 self::processInversion($q);
197 }
198
199 /**
200 * This is some boilerplate code needed to invert an S-box. Rather than repeat the code
201 * twice, I moved it to a protected method.
202 *
203 * Mutates $q
204 *
205 * @param ParagonIE_Sodium_Core_AES_Block $q
206 * @return void
207 */
208 protected static function processInversion(ParagonIE_Sodium_Core_AES_Block $q)
209 {
210 $q0 = (~$q[0]) & self::U32_MAX;
211 $q1 = (~$q[1]) & self::U32_MAX;
212 $q2 = $q[2] & self::U32_MAX;
213 $q3 = $q[3] & self::U32_MAX;
214 $q4 = $q[4] & self::U32_MAX;
215 $q5 = (~$q[5]) & self::U32_MAX;
216 $q6 = (~$q[6]) & self::U32_MAX;
217 $q7 = $q[7] & self::U32_MAX;
218 $q[7] = ($q1 ^ $q4 ^ $q6) & self::U32_MAX;
219 $q[6] = ($q0 ^ $q3 ^ $q5) & self::U32_MAX;
220 $q[5] = ($q7 ^ $q2 ^ $q4) & self::U32_MAX;
221 $q[4] = ($q6 ^ $q1 ^ $q3) & self::U32_MAX;
222 $q[3] = ($q5 ^ $q0 ^ $q2) & self::U32_MAX;
223 $q[2] = ($q4 ^ $q7 ^ $q1) & self::U32_MAX;
224 $q[1] = ($q3 ^ $q6 ^ $q0) & self::U32_MAX;
225 $q[0] = ($q2 ^ $q5 ^ $q7) & self::U32_MAX;
226 }
227
228 /**
229 * @param int $x
230 * @return int
231 */
232 public static function subWord($x)
233 {
234 $q = ParagonIE_Sodium_Core_AES_Block::fromArray(
235 array($x, $x, $x, $x, $x, $x, $x, $x)
236 );
237 $q->orthogonalize();
238 self::sbox($q);
239 $q->orthogonalize();
240 return $q[0] & self::U32_MAX;
241 }
242
243 /**
244 * Calculate the key schedule from a given random key
245 *
246 * @param string $key
247 * @return ParagonIE_Sodium_Core_AES_KeySchedule
248 * @throws SodiumException
249 */
250 public static function keySchedule($key)
251 {
252 $key_len = self::strlen($key);
253 switch ($key_len) {
254 case 16:
255 $num_rounds = 10;
256 break;
257 case 24:
258 $num_rounds = 12;
259 break;
260 case 32:
261 $num_rounds = 14;
262 break;
263 default:
264 throw new SodiumException('Invalid key length: ' . $key_len);
265 }
266 $skey = array();
267 $comp_skey = array();
268 $nk = $key_len >> 2;
269 $nkf = ($num_rounds + 1) << 2;
270 $tmp = 0;
271
272 for ($i = 0; $i < $nk; ++$i) {
273 $tmp = self::load_4(self::substr($key, $i << 2, 4));
274 $skey[($i << 1)] = $tmp;
275 $skey[($i << 1) + 1] = $tmp;
276 }
277
278 for ($i = $nk, $j = 0, $k = 0; $i < $nkf; ++$i) {
279 if ($j === 0) {
280 $tmp = (($tmp & 0xff) << 24) | ($tmp >> 8);
281 $tmp = (self::subWord($tmp) ^ self::$Rcon[$k]) & self::U32_MAX;
282 } elseif ($nk > 6 && $j === 4) {
283 $tmp = self::subWord($tmp);
284 }
285 $tmp ^= $skey[($i - $nk) << 1];
286 $skey[($i << 1)] = $tmp & self::U32_MAX;
287 $skey[($i << 1) + 1] = $tmp & self::U32_MAX;
288 if (++$j === $nk) {
289 /** @psalm-suppress LoopInvalidation */
290 $j = 0;
291 ++$k;
292 }
293 }
294 for ($i = 0; $i < $nkf; $i += 4) {
295 $q = ParagonIE_Sodium_Core_AES_Block::fromArray(
296 array_slice($skey, $i << 1, 8)
297 );
298 $q->orthogonalize();
299 // We have to overwrite $skey since we're not using C pointers like BearSSL did
300 for ($j = 0; $j < 8; ++$j) {
301 $skey[($i << 1) + $j] = $q[$j];
302 }
303 }
304 for ($i = 0, $j = 0; $i < $nkf; ++$i, $j += 2) {
305 $comp_skey[$i] = ($skey[$j] & 0x55555555)
306 | ($skey[$j + 1] & 0xAAAAAAAA);
307 }
308 return new ParagonIE_Sodium_Core_AES_KeySchedule($comp_skey, $num_rounds);
309 }
310
311 /**
312 * Mutates $q
313 *
314 * @param ParagonIE_Sodium_Core_AES_KeySchedule $skey
315 * @param ParagonIE_Sodium_Core_AES_Block $q
316 * @param int $offset
317 * @return void
318 */
319 public static function addRoundKey(
320 ParagonIE_Sodium_Core_AES_Block $q,
321 ParagonIE_Sodium_Core_AES_KeySchedule $skey,
322 $offset = 0
323 ) {
324 $block = $skey->getRoundKey($offset);
325 for ($j = 0; $j < 8; ++$j) {
326 $q[$j] = ($q[$j] ^ $block[$j]) & ParagonIE_Sodium_Core_Util::U32_MAX;
327 }
328 }
329
330 /**
331 * This mainly exists for testing, as we need the round key features for AEGIS.
332 *
333 * @param string $message
334 * @param string $key
335 * @return string
336 * @throws SodiumException
337 */
338 public static function decryptBlockECB($message, $key)
339 {
340 if (self::strlen($message) !== 16) {
341 throw new SodiumException('decryptBlockECB() expects a 16 byte message');
342 }
343 $skey = self::keySchedule($key)->expand();
344 $q = ParagonIE_Sodium_Core_AES_Block::init();
345 $q[0] = self::load_4(self::substr($message, 0, 4));
346 $q[2] = self::load_4(self::substr($message, 4, 4));
347 $q[4] = self::load_4(self::substr($message, 8, 4));
348 $q[6] = self::load_4(self::substr($message, 12, 4));
349
350 $q->orthogonalize();
351 self::bitsliceDecryptBlock($skey, $q);
352 $q->orthogonalize();
353
354 return self::store32_le($q[0]) .
355 self::store32_le($q[2]) .
356 self::store32_le($q[4]) .
357 self::store32_le($q[6]);
358 }
359
360 /**
361 * This mainly exists for testing, as we need the round key features for AEGIS.
362 *
363 * @param string $message
364 * @param string $key
365 * @return string
366 * @throws SodiumException
367 */
368 public static function encryptBlockECB($message, $key)
369 {
370 if (self::strlen($message) !== 16) {
371 throw new SodiumException('encryptBlockECB() expects a 16 byte message');
372 }
373 $comp_skey = self::keySchedule($key);
374 $skey = $comp_skey->expand();
375 $q = ParagonIE_Sodium_Core_AES_Block::init();
376 $q[0] = self::load_4(self::substr($message, 0, 4));
377 $q[2] = self::load_4(self::substr($message, 4, 4));
378 $q[4] = self::load_4(self::substr($message, 8, 4));
379 $q[6] = self::load_4(self::substr($message, 12, 4));
380
381 $q->orthogonalize();
382 self::bitsliceEncryptBlock($skey, $q);
383 $q->orthogonalize();
384
385 return self::store32_le($q[0]) .
386 self::store32_le($q[2]) .
387 self::store32_le($q[4]) .
388 self::store32_le($q[6]);
389 }
390
391 /**
392 * Mutates $q
393 *
394 * @param ParagonIE_Sodium_Core_AES_Expanded $skey
395 * @param ParagonIE_Sodium_Core_AES_Block $q
396 * @return void
397 */
398 public static function bitsliceEncryptBlock(
399 ParagonIE_Sodium_Core_AES_Expanded $skey,
400 ParagonIE_Sodium_Core_AES_Block $q
401 ) {
402 self::addRoundKey($q, $skey);
403 for ($u = 1; $u < $skey->getNumRounds(); ++$u) {
404 self::sbox($q);
405 $q->shiftRows();
406 $q->mixColumns();
407 self::addRoundKey($q, $skey, ($u << 3));
408 }
409 self::sbox($q);
410 $q->shiftRows();
411 self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3));
412 }
413
414 /**
415 * @param string $x
416 * @param string $y
417 * @return string
418 */
419 public static function aesRound($x, $y)
420 {
421 $q = ParagonIE_Sodium_Core_AES_Block::init();
422 $q[0] = self::load_4(self::substr($x, 0, 4));
423 $q[2] = self::load_4(self::substr($x, 4, 4));
424 $q[4] = self::load_4(self::substr($x, 8, 4));
425 $q[6] = self::load_4(self::substr($x, 12, 4));
426
427 $rk = ParagonIE_Sodium_Core_AES_Block::init();
428 $rk[0] = $rk[1] = self::load_4(self::substr($y, 0, 4));
429 $rk[2] = $rk[3] = self::load_4(self::substr($y, 4, 4));
430 $rk[4] = $rk[5] = self::load_4(self::substr($y, 8, 4));
431 $rk[6] = $rk[7] = self::load_4(self::substr($y, 12, 4));
432
433 $q->orthogonalize();
434 self::sbox($q);
435 $q->shiftRows();
436 $q->mixColumns();
437 $q->orthogonalize();
438 // add round key without key schedule:
439 for ($i = 0; $i < 8; ++$i) {
440 $q[$i] ^= $rk[$i];
441 }
442 return self::store32_le($q[0]) .
443 self::store32_le($q[2]) .
444 self::store32_le($q[4]) .
445 self::store32_le($q[6]);
446 }
447
448 /**
449 * Process two AES blocks in one shot.
450 *
451 * @param string $b0 First AES block
452 * @param string $rk0 First round key
453 * @param string $b1 Second AES block
454 * @param string $rk1 Second round key
455 * @return string[]
456 */
457 public static function doubleRound($b0, $rk0, $b1, $rk1)
458 {
459 $q = ParagonIE_Sodium_Core_AES_Block::init();
460 // First block
461 $q[0] = self::load_4(self::substr($b0, 0, 4));
462 $q[2] = self::load_4(self::substr($b0, 4, 4));
463 $q[4] = self::load_4(self::substr($b0, 8, 4));
464 $q[6] = self::load_4(self::substr($b0, 12, 4));
465 // Second block
466 $q[1] = self::load_4(self::substr($b1, 0, 4));
467 $q[3] = self::load_4(self::substr($b1, 4, 4));
468 $q[5] = self::load_4(self::substr($b1, 8, 4));
469 $q[7] = self::load_4(self::substr($b1, 12, 4));;
470
471 $rk = ParagonIE_Sodium_Core_AES_Block::init();
472 // First round key
473 $rk[0] = self::load_4(self::substr($rk0, 0, 4));
474 $rk[2] = self::load_4(self::substr($rk0, 4, 4));
475 $rk[4] = self::load_4(self::substr($rk0, 8, 4));
476 $rk[6] = self::load_4(self::substr($rk0, 12, 4));
477 // Second round key
478 $rk[1] = self::load_4(self::substr($rk1, 0, 4));
479 $rk[3] = self::load_4(self::substr($rk1, 4, 4));
480 $rk[5] = self::load_4(self::substr($rk1, 8, 4));
481 $rk[7] = self::load_4(self::substr($rk1, 12, 4));
482
483 $q->orthogonalize();
484 self::sbox($q);
485 $q->shiftRows();
486 $q->mixColumns();
487 $q->orthogonalize();
488 // add round key without key schedule:
489 for ($i = 0; $i < 8; ++$i) {
490 $q[$i] ^= $rk[$i];
491 }
492 return array(
493 self::store32_le($q[0]) . self::store32_le($q[2]) . self::store32_le($q[4]) . self::store32_le($q[6]),
494 self::store32_le($q[1]) . self::store32_le($q[3]) . self::store32_le($q[5]) . self::store32_le($q[7]),
495 );
496 }
497
498 /**
499 * @param ParagonIE_Sodium_Core_AES_Expanded $skey
500 * @param ParagonIE_Sodium_Core_AES_Block $q
501 * @return void
502 */
503 public static function bitsliceDecryptBlock(
504 ParagonIE_Sodium_Core_AES_Expanded $skey,
505 ParagonIE_Sodium_Core_AES_Block $q
506 ) {
507 self::addRoundKey($q, $skey, ($skey->getNumRounds() << 3));
508 for ($u = $skey->getNumRounds() - 1; $u > 0; --$u) {
509 $q->inverseShiftRows();
510 self::invSbox($q);
511 self::addRoundKey($q, $skey, ($u << 3));
512 $q->inverseMixColumns();
513 }
514 $q->inverseShiftRows();
515 self::invSbox($q);
516 self::addRoundKey($q, $skey, ($u << 3));
517 }
518}
519