AES-DH Implementation
Loading...
Searching...
No Matches
aes.h
1#pragma once
2
3#include <vector> // For a collection of states
4#include <cstdint> // For fixed width integers
5#include <sstream> // For stringstream construction.
6#include <bitset> // For raw bit access.
7#include <bit> // For rotl
8#include <array> // For the shared key array.
9
34namespace aes {
35
39 namespace gf {
40
49 uint8_t mult(uint8_t a, uint8_t b) {
50 // This function looks confusing, and that's because it is, but here's the rundown:
51 // Addition in a finite field of characteristic 2 (GF(2**X)) is simply XOR.
52 // This is nice, because we avoid carries and XOR is fast.
53 // Multiplication operates identically to normal mutiplication (Meaning repeated addition,
54 // which in our case means XOR), except we mod each stage with a "Reducing Polynomial"
55 // See all of Section 4 of the Reference for more details, but in essence we treat each byte as a
56 // polynomial (IE 0b11000001 = x**8 + x**7 + x**0).
57 // The reducing polynomial for GF(256) is 100011011:
58 // https://en.wikipedia.org/wiki/Finite_field_arithmetic#Rijndael's_(AES)_finite_field
59 // And this whole function is in essence just long multiplication, how you would do it
60 // by hand, where we slowly move through the b value by shifting it to 0, check that shifted
61 // first bit and "add" it to the result via an xor of A, and then preemptively check
62 // for an overflow condition on the final bit of a (0x80 = 128), which then applies our
63 // modulus to keep it bounded.
64 //
65 // However, the main question that arises is why do we need to use this at all. The Reference makes
66 // no attempt to explain WHY our operations must be performed in a Galois Field, and why that's useful.
67 // From what I've gathered: it's perfomance. Both addition and multiplication do not have
68 // to deal with carries, and the former is reduced to the blazing fast XOR, rather than the
69 // (relatively) complicated ADD.
70 //
71 // Finite Field Arithmetic is complicated, and I can't begin to scratch the surface of it
72 // in this comment block (Which is already monsterous), so here's some resources if you're interested:
73 // https://www.samiam.org/galois.html
74 // https://web.eecs.utk.edu/~jplank/plank/papers/CS-07-593/
75 // https://archive.org/details/finitefields0000lidl_a8r3
76 //
77
78 // The running result.
79 uint8_t res = 0;
80
81 // Iterate through every bit of b until it's been zeroed.
82 for (; b; b >>= 1) {
83
84 // If our current bit in b is a 1, then "add" a copy of a to the result.
85 if (b & 1) res ^= a;
86
87 // If a is about to overflow (IE there's a bit in 128), then "mod" it by the reducing
88 // polynomial pre-shift.
89 if (a & 0x80) a = (a << 1) ^ 0b100011011;
90
91 // Otherwise, just shift a.
92 else a <<= 1;
93 }
94 return res;
95 }
96
97
106 uint8_t inverse(uint8_t a) {
107 for (size_t x = 0; x < 256; ++x) {
108 if (mult(a, x) == 1) return x;
109 }
110 return 0;
111 }
112 }
113
114
118 namespace key {
119
130 uint32_t Rcon[10] = {
131 0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
132 0x20000000, 0x40000000, 0x80000000, 0x1b000000, 0x36000000
133 };
134
135
142 uint32_t RotWord(const uint32_t& word) {return std::rotl(word, 8);}
143
152 uint32_t SubWord(const uint32_t& word) {
153 uint8_t dest[4] = {0};
154
155 // Get the bytes
156 auto* bytes = reinterpret_cast<const uint8_t*>(&word);
157
158 for (size_t x = 0; x < 4; ++x) {
159 // See state_array::SubBytes for an explanation of what this does.
160 const uint8_t byte = bytes[x];
161 std::bitset<8> i = gf::inverse(byte), c = 0b01100011, result = 0;
162 for (int x = 0; x < 8; ++x) {
163 result[x] = i[x] ^ i[(x + 4) % 8] ^ i[(x + 5) % 8] ^ i[(x + 6) % 8] ^ i[(x + 7) % 8] ^ c[x];
164 }
165 dest[x] = uint8_t(result.to_ulong());
166 }
167
168 // Bundle our bytes back into a word.
169 return *reinterpret_cast<uint32_t*>(&dest[0]);
170 }
171
193 std::vector<uint32_t> Expansion(const std::array<uint64_t, 4>& key, const uint64_t& Nk) {
194 size_t i = 0;
195
196 // Break each 64 bit key into two 32 bit words.
197 std::vector<uint32_t> words;
198 for (size_t x = 0; x < 4; ++x) {
199 words.emplace_back(key[x] & 0xffffffff);
200 words.emplace_back(key[x] >> 32);
201 }
202
203 // The number of rounds depends on the size of the key.
204 // Nk = 4,6,8 if AES 128,192,256.
205 uint64_t Nr = Nk == 4 ? 10 : Nk == 6 ? 12 : 14;
206 auto w = std::vector<uint32_t>(4*Nr + 4, 0);
207
208 // The first Nk words of the expanded key are the key itself.
209 for (; i < Nk; ++i)
210 w[i] = words[i];
211
212 // This part of the algorithm is where things get confusing.
213 // In essence, AES mutates the key by taking the last wprd,
214 // And then performing a Subtitution step (SubWord like SubBytes),
215 // and then a transposition step (RotWord, like ShiftRows). By using the
216 // previous word, (Alongside An XOR with i - Nk), we're generating enough
217 // words to perform all the rounds AES from the original 4/6/8 that all
218 // depend on the original key.
219 //
220 for(; i < 4*Nr + 3; ++i) {
221
222 // Store the last word into temp.
223 uint32_t temp = w[i - 1];
224
225 // If our current iteration falls within the round constants, XOR it.
226 if (i % Nk == 0)
227 temp = SubWord(RotWord(temp)) ^ Rcon[i / Nk];
228
229 // This only applies for AES-256.
230 else if (Nk > 6 && i % Nk == 4) {
231 temp = SubWord(temp);
232 }
233 w[i] = w[i - Nk] ^ temp;
234 }
235 return w;
236 }
237 }
238
239
249 private:
250
251 // The state array is fixed in size; usually, we can assume
252 // that char is a byte, but we'll use the explicit, fixed width
253 // uint8 to ensure that each entry in the array is 8 bits.
254 std::array<std::array<uint8_t, 4>, 4> array;
255
256 public:
257
263 state_array(const std::string& in, size_t& x) {
264 auto length = in.length();
265
266 uint8_t i = 0;
267
268 // The syntax here might look a little weird, but this maps the 16 bytes to the row/col scheme:
269 // 0: array[0 / 4][0 % 4] = array[0][0]
270 // 1: array[1 / 4][1 % 4] = array[0][1]
271 // 2: array[2 / 4][2 % 4] = array[0][2]
272 // 3: array[3 / 4][3 % 4] = array[0][3]
273 // 4: array[4 / 4][4 % 4] = array[1][0]
274 // ...
275 // 12: array[12 / 4][12 % 4] = array[3][0]
276 // 13: array[13 / 4][13 % 4] = array[3][1]
277 // 14: array[14 / 4][14 % 4] = array[3][2]
278 // 15: array[15 / 4][15 % 4] = array[3][3]
279
280 // Populate the array with bytes from the string.
281 for (; x < length && i < 16; ++i, ++x) array[i / 4][i % 4] = in[x];
282
283 // Initialize the remainder of the array should the string be exhausted.
284 for (; i < 16; ++i) array[i / 4][i % 4] = 0;
285 }
286
287
288 // Initialize from a string, taking 16 bytes or the length.
289 state_array(const std::string& in) {
290 auto length = in.length();
291 for (uint8_t i = 0; i < 16; ++i)
292 array[i / 4][i % 4] = i < length ? in[i] : 0;
293 }
294
295
296 // Copy constructor.
297 state_array(const state_array& arr) {
298 for (uint8_t row = 0; row < 4; ++row) {
299 for (uint8_t col = 0; col < 4; ++col) {
300 array[row][col] = arr.array[row][col];
301 }
302 }
303 }
304
305
306 // Default constructor, populated with 0s.
307 state_array() {
308 for (uint8_t row = 0; row < 4; ++row) {
309 for (uint8_t col = 0; col < 4; ++col) {
310 array[row][col] = 0;
311 }
312 }
313 }
314
315
316 // Getter.
317 auto& get() {return array;}
318 const auto& get() const {return array;}
319
320
321 // Helper function for GCM to XOR two blocks together.
322 void xor_arr(const state_array& arr) {
323 for (uint8_t row = 0; row < 4; ++row) {
324 for (uint8_t col = 0; col < 4; ++col) {
325 array[col][row] ^= arr.array[col][row];
326 }
327 }
328 }
329
330
331 // Helper function for GCM to Shift a block
332 void shift_r(const size_t& bits) {
333 if (bits == 0) return;
334
335 // Basically, we iterate through each block,
336 // We then shift it by one, and append the
337 // carry to the end (As the shift put a 0 at the
338 // last bit position).
339 //
340 // Before we do the shift, we see what value we're shifting
341 // out (The bit in position 1). And if it's a 1, we set
342 // Carry to 0b10000000, so that when we shift the next byte,
343 // we are adding that bit onto the end.
344 //
345 uint8_t carry = 0;
346 for (uint8_t row = 0; row < 4; ++row) {
347 for (uint8_t col = 0; col < 4; ++col) {
348
349 // Get our new value.
350 uint8_t value = (array[col][row] >> 1) | carry;
351
352 // Update the carry flag depending on what we shift off.
353 if (array[col][row] & 1)
354 carry = 0b10000000;
355 else carry = 0;
356
357 // Replace.
358 array[col][row] = value;
359 }
360 }
361
362 // Is this inefficient? Yes. Very. Because the goal was making
363 // AES easier to understand, the 2D array makes it easy to
364 // follow what AES is doing (IF we just had a 16 byte array,
365 // ShiftRows and MixColumns would look a little strange).
366 // However, since we aren't dealing with a string of bytes, and
367 // can't necessarily trust that std::array is contigious,
368 // dealing with the block as a single value, which is what GCM
369 // likes to do, is tedious. Fortunately, we only ever shift
370 // By 1 bit, this is just for completeness.
371 shift_r(bits - 1);
372 }
373
374
379 std::string unravel() const {
380 std::stringstream out;
381 for (uint8_t row = 0; row < 4; ++row) {
382 for (uint8_t col = 0; col < 4; ++col) {
383 out << array[row][col];
384 }
385 }
386 return out.str();
387 }
388
389
396 void AddRoundKey(const uint64_t& round, const std::vector<uint32_t>& keys) {
397 for (size_t col = 0; col < 4; ++col) {
398 const auto key = keys[(4 * round) + col];
399
400 // We can just cast the number into a byte array.
401 auto* bytes = reinterpret_cast<const uint8_t*>(&key);
402
403 for (size_t row = 0; row < 4; ++row) {
404 array[row][col] ^= bytes[row];
405 }
406 }
407 }
408
409
419 void SubBytes() {
420 for (uint8_t row = 0; row < 4; ++row) {
421 for (uint8_t col = 0; col < 4; ++col) {
422
423 const uint8_t byte = array[col][row];
424
425 // Here, we find the multiplicative inverse of the byte in
426 // In a Galois Field of GF(2**8). We can use the extended Euclidean
427 // Algorithm: b(x)a(x) + m(x)c(x) = 1.
428 // See Section 4.4 of the Reference for details.
429 // The multiplicative inverse over this field has good non-linearity properties.
430 // The constant value is chosen in a similar fashion to the Round Constants in
431 // Expansion: It eliminates symmetries.
432 std::bitset<8> i = gf::inverse(byte), c = 0b01100011, result = 0;
433
434 // Now, we perform the affine transformation. This
435 // is done on a bit-by-bit level, performing 5 XOR
436 // operations per bit, or 40 XOR operations per byte.
437 // Again, this shows the value of the lookup table.
438 for (int x = 0; x < 8; ++x) {
439
440 // The entire expression is:
441 // b_i = b_i ^ b_i+4%8 ^ b_i+5%8 ^ b_i+6%8 ^ b_i+7%8 ^ c_i
442 // This gets applied for each bit, so eight times for a single value.
443 result[x] = i[x] ^ i[(x + 4) % 8] ^ i[(x + 5) % 8] ^ i[(x + 6) % 8] ^ i[(x + 7) % 8] ^ c[x];
444 }
445
446 // Finally, collapse the bitset back into an actual number we can store.
447 array[col][row] = result.to_ulong();
448 }
449 }
450 }
451
452
459 void InvSubBytes() {
460 for (uint8_t row = 0; row < 4; ++row) {
461 for (uint8_t col = 0; col < 4; ++col) {
462 const uint8_t byte = array[col][row];
463
464 // This may be confusing, but this is the inverse of the affine transformation we did in SubBytes.
465 // https://en.wikipedia.org/wiki/Rijndael_S-box#Inverse_S-box
466 // The math isn't particularly important, all that you need to understand is this reverses what we
467 // did prior.
468 // std::rotl is a rotate shift (IE bits shifted out are added to the front)
469 uint8_t i = byte, c = 0b00000101, result = 0;
470 result = std::rotl(i, 1) ^ std::rotl(i, 3) ^ std::rotl(i, 6) ^ c;
471
472 // Once we've undone the transformation, get the multiplicative inverse, which is our original.
473 array[col][row] = gf::inverse(result);
474 }
475 }
476 }
477
478
488 void ShiftRows() {
489
490 // Create a buffer to place the values at new positions.
491 std::array<std::array<uint8_t, 4>, 4> buffer;
492
493 // Shift each of the based on the scheme described
494 // in 5.5 of the Reference.
495 // Note that while there is no explicit statement
496 // Excluding the first row as mentioned in the Reference.
497 // The math here works out such that the first row
498 // Is copied in place:
499 // 0,0 = 0+0 % 4 = 0
500 // 0,1 = 0+1 % 4 = 1 ...
501 // And since the row itself remains constant,
502 // The top row remains unchanged.
503 // I could not find a reason for WHY the first row is excluded,
504 // Which makes me think it was just a quirk of how this algorithm
505 // Works. Because this step is to prevent each COLUMN from being
506 // Independent, the top row being unchanged doesn't actually
507 // present any weakness, since the columns are still being shuffled
508 for (size_t row = 0; row < 4; ++row) {
509 for (size_t col = 0; col < 4; ++col) {
510 buffer[col][row] = array[(col + row) % 4][row];
511 }
512 }
513
514 // Replace with the new values. There is probably a more efficient
515 // Way of doing this, perhaps mutating the array in place, rather
516 // Than creating a copy, but this allows us to better shows what
517 // shift is being performed.
518 for (size_t row = 0; row < 4; ++row) {
519 for (size_t col = 0; col < 4; ++col) {
520 array[col][row] = buffer[col][row];
521 }
522 }
523 }
524
525
534
535 // Create a buffer to place the values at new positions.
536 std::array<std::array<uint8_t, 4>, 4> buffer;
537
538 // Exact same loop as ShiftRows, but invert the index.
539 for (size_t row = 0; row < 4; ++row) {
540 for (size_t col = 0; col < 4; ++col) {
541 buffer[col][row] = array[(col - row) % 4][row];
542 }
543 }
544
545 // Update the state.
546 for (size_t row = 0; row < 4; ++row) {
547 for (size_t col = 0; col < 4; ++col) {
548 array[col][row] = buffer[col][row];
549 }
550 }
551 }
552
553
560 void MixColumns() {
561 // These operations are equivalent to multiplying the columns
562 // Against a matrix, specifically:
563 // [ 02 03 01 01 ][s0c]
564 // [ 01 02 03 01 ][s1c]
565 // [ 01 01 02 03 ][s2c]
566 // [ 03 01 01 02 ][s3c]
567 // For each column c.
568 // Our multiplications are done in GF(256)
569
570 uint8_t buffer[4];
571 const uint8_t set[4] = {0x02, 0x01, 0x01, 0x03};
572 for (size_t col = 0; col < 4; ++col) {
573 buffer[0] = gf::mult(0x2, array[col][0]) ^ gf::mult(0x3, array[col][1]) ^ array[col][2] ^ array[col][3];
574 buffer[1] = array[col][0] ^ gf::mult(0x2, array[col][1]) ^ gf::mult(0x3, array[col][2]) ^ array[col][3];
575 buffer[2] = array[col][0] ^ array[col][1] ^ gf::mult(0x2, array[col][2]) ^ gf::mult(0x3, array[col][3]);
576 buffer[3] = gf::mult(0x3, array[col][0]) ^ array[col][1] ^ array[col][2] ^ gf::mult(0x2, array[col][3]);
577
578 for (size_t row = 0; row < 4; ++row) array[col][row] = buffer[row];
579 }
580 }
581
582
588 // These operations are equivalent to multiplying the columns
589 // Against a matrix, specifically:
590 // [ 0e 0b 0d 09 ][s0c]
591 // [ 09 0e 0b 0d ][s1c]
592 // [ 0d 09 0e 0b ][s2c]
593 // [ 0b 0d 09 0e ][s3c]
594 // For each column c.
595 // Our multiplications are done in GF(256)
596 // This matrix reverses the matrix we multiplied with in MixColumns()
597
598 uint8_t buffer[4];
599 const uint8_t set[4] = {0x0e, 0x09, 0x0d, 0x0b};
600 for (size_t col = 0; col < 4; ++col) {
601 buffer[0] = gf::mult(0xe, array[col][0]) ^ gf::mult(0xb, array[col][1]) ^ gf::mult(0xd, array[col][2]) ^ gf::mult(0x9, array[col][3]);
602 buffer[1] = gf::mult(0x9, array[col][0]) ^ gf::mult(0xe, array[col][1]) ^ gf::mult(0xb, array[col][2]) ^ gf::mult(0xd, array[col][3]);
603 buffer[2] = gf::mult(0xd, array[col][0]) ^ gf::mult(0x9, array[col][1]) ^ gf::mult(0xe, array[col][2]) ^ gf::mult(0xb, array[col][3]);
604 buffer[3] = gf::mult(0xb, array[col][0]) ^ gf::mult(0xd, array[col][1]) ^ gf::mult(0x9, array[col][2]) ^ gf::mult(0xe, array[col][3]);
605
606 for (size_t row = 0; row < 4; ++row) array[col][row] = buffer[row];
607 }
608 }
609 };
610
611
615 class state {
616 private:
617 std::vector<state_array> arrays;
618 std::vector<uint32_t> expanded;
619 std::array<uint64_t, 4> key = {0};
620 uint64_t rounds = 0;
621
622
629 void Schedule(const std::array<uint64_t, 4>& k, const uint64_t& Nr) {
630 switch (Nr) {
631 case 10: expanded = key::Expansion(k, 4); break;
632 case 12: expanded = key::Expansion(k, 6); break;
633 case 14: expanded = key::Expansion(k, 8); break;
634 default: throw std::runtime_error("Invalid key size:" + std::to_string(Nr));
635 }
636 }
637
638
639 public:
640
641 // Construct a state from a input string.
642 state(const std::string& in, const std::array<uint64_t, 4>& k, const uint64_t& Nr) {
643
644 // Get our key schedule.
645 Schedule(k, Nr);
646 size_t x = 0;
647
648 // The state_array constructor takes a mutable reference to x,
649 // and will increment it automatically. Therefore, we just need
650 // to repeatedly construct state_arrays until the string has been exhausted.
651 while (x < in.length()) {arrays.emplace_back(state_array(in, x));}
652
653 key = k;
654 rounds = Nr;
655 }
656
657
658 // Construct a state from a collection of state_arrays.
659 state(const std::vector<state_array>& arrs, const std::array<uint64_t, 4>& k, const uint64_t& Nr) {
660 Schedule(k, Nr);
661 arrays = arrs;
662 key = k;
663 rounds = Nr;
664 }
665
666
667 // Getters
668 auto& get_arrays() {return arrays;}
669 const auto& get_arrays() const {return arrays;}
670 const auto& get_key() {return key;}
671 const auto& get_rounds() {return rounds;}
672
673
678 std::string unravel() const {
679 std::stringstream out;
680 for (const auto& array : arrays)
681 out << array.unravel();
682 return out.str();
683 }
684
685
690 // AddRoundKey
691 void AddRoundKey(const uint64_t& round) {for (auto& array: arrays) array.AddRoundKey(round, expanded);}
692
693 // SubBytes.
694 void SubBytes() {for (auto& array: arrays) array.SubBytes();}
695 void InvSubBytes() {for (auto& array: arrays) array.InvSubBytes();}
696
697 // ShiftRows
698 void ShiftRows() {for (auto& array: arrays) array.ShiftRows();}
699 void InvShiftRows() {for (auto& array: arrays) array.InvShiftRows();}
700
701 // MixColumns
702 void MixColumns() {for (auto& array: arrays) array.MixColumns();}
703 void InvMixColumns() {for (auto& array: arrays) array.InvMixColumns();}
704 };
705
706
716 std::string Cipher(const std::string& in, const std::array<uint64_t, 4>& k, const uint64_t& Nr) {
717 auto s = state(in, k, Nr);
718 s.AddRoundKey(0);
719
720 for (size_t x = 0; x < Nr - 1; ++x) {
721 s.SubBytes();
722 s.ShiftRows();
723 s.MixColumns();
724 s.AddRoundKey(x + 1);
725 }
726
727 s.SubBytes();
728 s.ShiftRows();
729 s.AddRoundKey(Nr - 1);
730
731 return s.unravel();
732 }
733
734
744 std::string InvCipher(const std::string& in, const std::array<uint64_t, 4>& k, const uint64_t& Nr) {
745 auto s = state(in, k, Nr);
746
747 // Because the AddRoundKey is literally just XOR, running it again, but in reverse (Nr-1 -> 0),
748 // undoes the operation, so we don't need a dedicated InvAddRoundKey like the other
749 // steps.
750 s.AddRoundKey(Nr - 1);
751
752 for (size_t x = Nr - 1; x >= 1; --x) {
753 s.InvShiftRows();
754 s.InvSubBytes();
755 s.AddRoundKey(x);
756 s.InvMixColumns();
757 }
758
759 s.InvShiftRows();
760 s.InvSubBytes();
761 s.AddRoundKey(0);
762
763 return s.unravel();
764 }
765
766
776 std::string Ctr(const std::string& in, const std::array<uint64_t, 4>& k, const uint64_t Nr, uint64_t nonce) {
777 // We just use this to partition the input into individual state_arrays.
778 auto s = state(in, k, Nr);
779
780 // Go through each array.
781 for (auto& array: s.get_arrays()) {
782
783 // Generate a Pad for it.
784 auto pad = state_array(Cipher(std::string(reinterpret_cast<char*>(&nonce), sizeof(uint64_t)), k, Nr));
785
786 // XOR
787 array.xor_arr(pad);
788
789 // Increment the nonce for the next array.
790 nonce++;
791 }
792
793 // Unravel the state.
794 return s.unravel();
795 }
796
797
805 namespace gcm {
806
829 // Get the underlying array.
830 auto& array = X.get();
831
832 // The state array is always 128 bits, so len(X) = 128/8 = 16.
833 // While the Reference makes the increment function generic in
834 // Terms of the s value, it is always used with s=32 within GCM,
835 // which is 4 bytes.
836 //
837 // The incrementing function takes the len(X) - s Most
838 // Significant Bits (The first 12 bytes), and does nothing.
839 //
840 // Then, it takes the s least significant bits (the last 4)
841 // Bytes, casts it into a integer, adds one, and then applies
842 // A modulus 2**s, and then brings it all back together.
843 //
844 // So, for the purpose of implementation, we just need to
845 // extract the last four bytes of the state_array used for
846 // the counter (Which just means the last column for our
847 // implementation, treat the entire thing as a single value,
848 // increment it, mod it, and then replace the existing values
849 // with these new values.
850 //
851
852 // Basically we just take each 8bit value, and shift it:
853 // uint32_t = 00000000 00000000 00000000 00000000 |
854 // 11111111 00000000 00000000 00000000 | (array[3][0])
855 // 00000000 11111111 00000000 00000000 | (array[3][1])
856 // 00000000 00000000 11111111 00000000 | (array[3][2])
857 // 00000000 00000000 00000000 11111111 | (array[3][3])
858 // -----------------------------------
859 // 11111111 11111111 11111111 11111111
860 //
861 // Don't think about this too hard, it just takes our bytes in the state_array
862 // and puts them into a form so that we can increment the entire thing.
863 uint32_t lsb = (array[3][0] << 24) | (array[3][1] << 16) | (array[3][2] << 8) | array[3][3];
864
865 // Fun fact, since we're working with uint32_t, modding by 2**32 is not required, since if the
866 // value is exceeded, it will automatically overflow for us. Therefore, we can just increment it
867 // And let C++ handle the "mod".
868 lsb += 1;
869
870 // This just returns the value into individual 8 bit values in the array.
871 // We go in reverse because the first 8 bits are array[3][3].
872 for (int x = 3; x >= 0; --x) {
873
874 // Mask to get the first 8 bits. Then shift those values out to get the next eight.
875 array[3][x] = lsb & 0xFF;
876 lsb >>= 8;
877 }
878 }
879
880
889 // This is just a constant. Which is just a state version
890 // of the reducing polynomial.
891 state_array R;
892 R.get()[0][0] = 0b11100001;
893
894 // Basically to multiply we start with generation 0
895 // Of Z, V, and then iterating through each BIT of
896 // X, we mutate Z and V. Once we've gone through
897 // All the bits, we'll have 128 generations, and
898 // we return the last Z.
899 //
900 // You may notice this scheme is very similar to our Galois
901 // Field 128 mult, which takes one of the numbers, continually
902 // bit shifts it down to zero, and then performs XOR depending
903 // on that bit. This is little more than a state_array version
904 // of that function! Go back up to gf::mult and compare!
905 //
906 state_array Z, V = Y;
907
908 // To iterate through X, we just do our normal row, col
909 // Iteration, and then for each value, we iterate 8 times
910 // For each bit.
911 for (size_t row = 0; row < 4; ++row) {
912 for (size_t col = 0; col < 4; ++col) {
913 uint8_t byte = X.get()[col][row];
914 for (size_t bit = 0; bit < 8; ++bit) {
915 // We can treat this as a boolean, as every non-zero
916 // value is "true".
917 bool x = byte & 0b10000000;
918
919 // If x_i = 1, Z+1 = Z ^ V. Otherwise, it is unchanged.
920 if (x) Z.xor_arr(V);
921
922 // If the least significant bit of V is 1, we
923 // Shift V, and then XOR it with R.
924 // Otherwise, we just Shift it.
925 V.shift_r(1);
926 if (V.get()[3][3] & 1 == 1)
927 V.xor_arr(R);
928
929 // Shift to the next bit.
930 byte << 1;
931 }
932 }
933 }
934 return Z;
935 }
936
937
956 state_array GHASH(const state& X, const state_array& H) {
957 state_array Y;
958
959 // Set the new generation of Y to (Y XOR X_i) * H;
960 for (const auto& array: X.get_arrays()) {
961 Y.xor_arr(array);
962 Y = mult(Y, H);
963 }
964 return Y;
965 }
966
967
980 // Go through each array.
981 for (auto& array: s.get_arrays()) {
982
983 // Generate a Pad for it.
984 // I KNOW. Unravelling the ICB, and then generating a state is not the pinnacle of efficiency,
985 // but creating a separate Cipher for handling a state_array/state would length an already massive
986 // source file.
987 auto pad = state_array(Cipher(ICB.unravel(), s.get_key(), s.get_rounds()));
988
989 // XOR
990 array.xor_arr(pad);
991
992 // Increment the nonce for the next array.
993 increment(ICB);
994 }
995 return s;
996 }
997
998
999 /*
1000 * @brief Encrypt a message with AES-GCM
1001 * @param in: The input string.
1002 * @param k: The key.
1003 * @param Nr: The number of rounds to perform.
1004 * @param nonce: The nonce IV.
1005 * @returns An encrypted string, with the hash block attached to the end
1006 */
1007 std::string Enc(const std::string& in, const std::array<uint64_t, 4>& k, const uint64_t Nr, uint64_t nonce) {
1008
1009 // Generate our H hash subkey by encrypting a 0 block.
1010 state_array H = Cipher("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", k, Nr);
1011
1012 // Generate the J0 that we'll use as a counter, based on our IV/Nonce.
1013 auto J = GHASH(state(std::string(reinterpret_cast<char*>(&nonce), sizeof(nonce)), k, Nr), H);
1014
1015 // This J is incremented for encrypting the message (We use J0 for the hash). This is so that
1016 // We can immediately check the hash on the decryption step, avoiding having to decrypt the message
1017 // before we can verify if it's been modified
1018 auto Jc = J;
1019 increment(Jc);
1020
1021 // Encrypt our message.
1022 auto cipher_state = state(in, k, Nr);
1023 cipher_state = GCTR(cipher_state, Jc);
1024
1025 // Generate our Hash. Basically, we run GHASH to get a single block or state_array, and then turn that into
1026 // A "state" of 1 so that GCTR can encrypt it, and then pull out the singular block to get a state_array again.
1027 // One thing to note here, is that this block, called S in the Reference,
1028 // Can optionally take AAD, or Additional Authenticated Data, which can be
1029 // anything from destination IP, to Names (This data will be Authenticated, but must be sent in the clear)
1030 // . For this implementation the only AAD that we would consider is the Nonce, but since it's already
1031 // Included in the Hash via J, we just hash the cipher.
1032 auto hash = GCTR(state({GHASH(cipher_state, H)}, k, Nr), J).get_arrays()[0];
1033
1034 // Add the hash to the end of the cipher_state, and return it as one object.
1035 cipher_state.get_arrays().emplace_back(hash);
1036 return cipher_state.unravel();
1037 }
1038
1039
1049 std::string Dec(const std::string& in, const std::array<uint64_t, 4>& k, const uint64_t Nr, uint64_t nonce) {
1050
1051 // Generate our H hash subkey by encrypting a 0 block.
1052 state_array H = Cipher("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", k, Nr);
1053
1054 // Generate the J0 that we'll use as a counter, based on our IV/Nonce.
1055 auto J = GHASH(state(std::string(reinterpret_cast<char*>(&nonce), sizeof(nonce)), k, Nr), H);
1056
1057 // Get the cipher, and then pop the hash off the back.
1058 auto cipher_state = state(in, k, Nr);
1059 auto hash = cipher_state.get_arrays().back();
1060 cipher_state.get_arrays().pop_back();
1061
1062 // Then, compute the hash using J.
1063 hash = GCTR(state({hash}, k, Nr), J).get_arrays()[0];
1064
1065 // If they don't match then either the key was wrong, or one of the blocks has been modified.
1066 // Either way, throw a runtime error.
1067 if (hash.unravel() != GHASH(cipher_state, H).unravel()) {
1068 throw std::runtime_error("Message does not match! Refusing to decrypt!");
1069 }
1070
1071 // If they do match, then increment J and proceed with decryption.
1072 increment(J);
1073 return GCTR(cipher_state, J).unravel();
1074 }
1075 }
1076}
The state array is a 4x4 byte matrix to which AES operations are performed; also called a block.
Definition aes.h:248
void ShiftRows()
Cyclically shift the bytes in each row.
Definition aes.h:488
void InvShiftRows()
Invert the cyclical shift in ShiftRows()
Definition aes.h:533
void AddRoundKey(const uint64_t &round, const std::vector< uint32_t > &keys)
Add the round key.
Definition aes.h:396
state_array(const std::string &in, size_t &x)
Initialize a state_array from a string.
Definition aes.h:263
void MixColumns()
Transform each column by a single, fixed matrix.
Definition aes.h:560
void InvMixColumns()
Inverts the column transformation.
Definition aes.h:587
std::string unravel() const
Unravel the state_array back into a string.
Definition aes.h:379
void InvSubBytes()
: Invert the SubBytes step of AES.
Definition aes.h:459
void SubBytes()
A invertible, non-linear transformation of the state.
Definition aes.h:419
An arbitrary collection of state arrays.
Definition aes.h:615
std::string unravel() const
Unravel a state into a character string.
Definition aes.h:678
void AddRoundKey(const uint64_t &round)
Definition aes.h:691
state GCTR(state s, state_array ICB)
Apply AES-CTR to a message.
Definition aes.h:979
std::string Dec(const std::string &in, const std::array< uint64_t, 4 > &k, const uint64_t Nr, uint64_t nonce)
Decrypt a message with AES-GCM.
Definition aes.h:1049
state_array mult(const state_array &X, const state_array &Y)
Perform a multiplication on two blocks of data.
Definition aes.h:888
void increment(state_array &X)
The Nonce Increment Function.
Definition aes.h:828
state_array GHASH(const state &X, const state_array &H)
Calculate the GHASH for a state.
Definition aes.h:956
uint8_t mult(uint8_t a, uint8_t b)
Multiply two bytes in GA(256)
Definition aes.h:49
uint8_t inverse(uint8_t a)
Find the Multiplicative Inverse of a byte in GF(2**8)
Definition aes.h:106
uint32_t Rcon[10]
The round constants.
Definition aes.h:130
uint32_t RotWord(const uint32_t &word)
Rotate a word by one byte left.
Definition aes.h:142
uint32_t SubWord(const uint32_t &word)
Substitue the bytes in a key-schedule word.
Definition aes.h:152
std::vector< uint32_t > Expansion(const std::array< uint64_t, 4 > &key, const uint64_t &Nk)
Expand a set of keys.
Definition aes.h:193
The namespace containing AES encryption/decryption functions.
Definition aes.h:34
std::string InvCipher(const std::string &in, const std::array< uint64_t, 4 > &k, const uint64_t &Nr)
Decrypt a message with AES.
Definition aes.h:744
std::string Ctr(const std::string &in, const std::array< uint64_t, 4 > &k, const uint64_t Nr, uint64_t nonce)
An implementation of AES in CTR mode.
Definition aes.h:776
std::string Cipher(const std::string &in, const std::array< uint64_t, 4 > &k, const uint64_t &Nr)
Encrypt a message with AES.
Definition aes.h:716