├── .gitignore ├── CMakeLists.txt ├── FunWithCbc.java ├── FunWithGcm.java ├── README.md ├── fun_with_gcm_siv.cc ├── gauss.h ├── gf2_128_polyval.cc └── gf2_128_polyval.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | *.o 4 | *.so 5 | 6 | # third_party directory 7 | build 8 | third_party/ 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(FunWithGCM CXX) 3 | set(CMAKE_CXX_STANDARD 17) 4 | 5 | add_subdirectory(third_party/tink) 6 | add_library(gf2_128_polyval gf2_128_polyval.cc) 7 | add_executable(fun_with_gcm_siv fun_with_gcm_siv.cc) 8 | target_link_libraries(fun_with_gcm_siv tink::static) 9 | target_link_libraries(fun_with_gcm_siv gf2_128_polyval) 10 | -------------------------------------------------------------------------------- /FunWithCbc.java: -------------------------------------------------------------------------------- 1 | import java.nio.ByteBuffer; 2 | import java.io.BufferedReader; 3 | import java.io.InputStreamReader; 4 | import java.nio.ByteOrder; 5 | import java.nio.charset.Charset; 6 | import java.security.Key; 7 | import java.security.SecureRandom; 8 | import java.util.Arrays; 9 | import javax.crypto.Cipher; 10 | import javax.crypto.KeyGenerator; 11 | import javax.crypto.spec.GCMParameterSpec; 12 | import javax.crypto.spec.IvParameterSpec; 13 | import javax.crypto.spec.SecretKeySpec; 14 | 15 | class FunWithCbc { 16 | public static Charset UTF8 = Charset.forName("UTF-8"); 17 | 18 | public static String encode(final byte[] bytes) { 19 | String chars = "0123456789abcdef"; 20 | StringBuilder result = new StringBuilder(2 * bytes.length); 21 | for (byte b : bytes) { 22 | // convert to unsigned 23 | int val = b & 0xff; 24 | result.append(chars.charAt(val / 16)); 25 | result.append(chars.charAt(val % 16)); 26 | } 27 | return result.toString(); 28 | } 29 | 30 | public static byte[] encrypt(byte message[], byte key[]) throws Exception { 31 | ByteBuffer in = ByteBuffer.wrap(message, 0, message.length); 32 | ByteBuffer out = ByteBuffer.allocate((message.length / 16 + 2) * 16); 33 | SecureRandom rand = new SecureRandom(); 34 | byte iv[] = new byte[16]; 35 | rand.nextBytes(iv); 36 | out.put(iv); 37 | IvParameterSpec s = new IvParameterSpec(iv); 38 | Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); 39 | c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), s); 40 | c.doFinal(in, out); 41 | return out.array(); 42 | } 43 | 44 | public static byte[] decrypt(byte ciphertext[], byte key[]) throws Exception { 45 | ByteBuffer in = ByteBuffer.wrap(ciphertext, 0, ciphertext.length); 46 | byte iv[] = new byte[16]; 47 | byte ciphertextWithoutIv[] = new byte[ciphertext.length - 16]; 48 | in.get(iv); 49 | in.get(ciphertextWithoutIv); 50 | IvParameterSpec s = new IvParameterSpec(iv); 51 | Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); 52 | c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), s); 53 | return c.doFinal(ciphertextWithoutIv); 54 | } 55 | 56 | public static class Bob { 57 | private byte key[]; 58 | private int guessCounter; 59 | 60 | public Bob(byte key[]) { 61 | this.key = key; 62 | this.guessCounter = 0; 63 | } 64 | 65 | public boolean oracle(byte ciphertext[]) { 66 | byte message[]; 67 | this.guessCounter++; 68 | try { 69 | message = decrypt(ciphertext, key); 70 | } catch (Exception e) { 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | public boolean guess(byte ciphertext[], byte plaintextGuess[]) { 77 | byte message[]; 78 | try { 79 | message = decrypt(ciphertext, key); 80 | } catch (Exception e) { 81 | return false; 82 | } 83 | return Arrays.equals(message, plaintextGuess); 84 | } 85 | 86 | public int getGuessCounter() { 87 | return guessCounter; 88 | } 89 | 90 | public void printCBC(byte ciphertext[]) { 91 | try { 92 | byte trimmed[] = Arrays.copyOfRange(ciphertext, 16, ciphertext.length); 93 | Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); 94 | c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES")); 95 | byte decrypted[] = c.doFinal(trimmed); 96 | for (int i = 0; i < decrypted.length; i++) { 97 | decrypted[i] = (byte)(ciphertext[i] ^ decrypted[i]); 98 | } 99 | System.out.println(encode(decrypted)); 100 | } catch (Exception e) { 101 | e.printStackTrace(); 102 | } 103 | } 104 | } 105 | 106 | public static class Alice { 107 | private byte key[]; 108 | 109 | public Alice() { 110 | SecureRandom rand = new SecureRandom(); 111 | key = new byte[32]; 112 | rand.nextBytes(key); 113 | } 114 | 115 | byte[] getKey() { 116 | return key; 117 | } 118 | 119 | byte[] message(String plaintext) { 120 | byte ciphertext[]; 121 | try { 122 | ciphertext = encrypt(plaintext.getBytes(UTF8), key); 123 | } catch (Exception e) { 124 | e.printStackTrace(); 125 | return null; 126 | } 127 | return ciphertext; 128 | } 129 | } 130 | 131 | public static class Eve { 132 | private Bob bob; 133 | 134 | public Eve(Bob bob) { 135 | this.bob = bob; 136 | } 137 | 138 | public byte[] attack(byte ciphertext[]) { 139 | System.out.println("Eve: Trying to find the plaintext for:"); 140 | System.out.println(" " + encode(ciphertext)); 141 | System.out.println(); 142 | System.out.println("This is what Bob sees with padding:"); 143 | System.out.print(" "); 144 | bob.printCBC(ciphertext); 145 | byte padding = findPadding(ciphertext); 146 | byte plaintext[] = new byte[ciphertext.length - 16 - padding]; 147 | for (int i = 0; i < plaintext.length; i++) { 148 | guessByte(plaintext.length - i - 1, ciphertext, plaintext); 149 | } 150 | return plaintext; 151 | } 152 | 153 | private void guessByte(int index, byte ciphertext[], byte plaintext[]) { 154 | byte padding = (byte)(ciphertext.length - plaintext.length - 16); 155 | byte ciphertextMod[] = Arrays.copyOf(ciphertext, (index / 16 + 2) * 16); 156 | byte newPadding = (byte)(ciphertextMod.length - index - 16); 157 | for (int i = ciphertextMod.length - 16 - 1; i > index; i--) { 158 | ciphertextMod[i] = (byte)(ciphertextMod[i] 159 | ^ (i < plaintext.length ? 160 | (plaintext[i] ^ newPadding) : (padding ^ newPadding))); 161 | } 162 | byte orig = ciphertextMod[index]; 163 | System.out.println("Eve: Flipped bits for new padding 0x" + encode(new byte[] {newPadding}) + ":"); 164 | System.out.print(" "); 165 | bob.printCBC(ciphertextMod); 166 | for (int guess = 0; guess < 256; guess++) { 167 | ciphertextMod[index] = (byte)(orig ^ newPadding ^ guess); 168 | if (bob.oracle(ciphertextMod)) { 169 | System.out.println("Found the valid padding, making the guess 0x" + encode(new byte[] {(byte)guess}) + ":"); 170 | System.out.print(" "); 171 | bob.printCBC(ciphertextMod); 172 | plaintext[index] = (byte)guess; 173 | return; 174 | } 175 | } 176 | throw new RuntimeException("No guess found!"); 177 | } 178 | 179 | private byte findPadding(byte ciphertext[]) { 180 | byte xor = 0x01; 181 | if (ciphertext.length % 16 != 0) { 182 | return (byte)0xff; 183 | } 184 | System.out.println("Eve: Looking for padding, modified ciphertext, now Bob sees:"); 185 | for (byte padding = 0x01; padding <= 16; padding++) { 186 | if (padding + 16 >= ciphertext.length) { 187 | return 0x10; 188 | } 189 | byte modCiphertext[] = xorPlaintextByte(ciphertext.length - 16 - padding - 1, xor, ciphertext); 190 | System.out.print(" "); 191 | bob.printCBC(modCiphertext); 192 | if (bob.oracle(modCiphertext)) { 193 | return padding; 194 | } 195 | } 196 | return (byte)0xff; 197 | } 198 | 199 | 200 | private byte[] xorPlaintextByte(int index, byte xor, byte ciphertext[]) { 201 | if (ciphertext.length < index + 16) { 202 | throw new IndexOutOfBoundsException(); 203 | } 204 | byte result[] = Arrays.copyOf(ciphertext, ciphertext.length); 205 | result[index] = (byte)(result[index] ^ xor); 206 | return result; 207 | } 208 | } 209 | 210 | public static void main(String[] args) { 211 | Alice alice = new Alice(); 212 | Bob bob = new Bob(alice.getKey()); 213 | Eve eve = new Eve(bob); 214 | String message = ""; 215 | try { 216 | BufferedReader in = new BufferedReader(new InputStreamReader(System.in, UTF8)); 217 | message = in.readLine(); 218 | } catch (Exception e) { 219 | e.printStackTrace(); 220 | } 221 | byte ciphertext[] = alice.message(message); 222 | byte guess[] = eve.attack(ciphertext); 223 | if (bob.guess(ciphertext, guess)) { 224 | System.out.println("Success: \"" + new String(guess, UTF8) + "\""); 225 | System.out.println("Number of guesses: " + bob.getGuessCounter()); 226 | } else { 227 | System.out.println("Failed: \"" + encode(guess) + "\""); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /FunWithGcm.java: -------------------------------------------------------------------------------- 1 | import java.nio.ByteBuffer; 2 | import java.nio.ByteOrder; 3 | import java.nio.charset.Charset; 4 | import java.security.Key; 5 | import java.security.SecureRandom; 6 | import java.util.Arrays; 7 | import javax.crypto.Cipher; 8 | import javax.crypto.KeyGenerator; 9 | import javax.crypto.spec.GCMParameterSpec; 10 | import javax.crypto.spec.IvParameterSpec; 11 | import javax.crypto.spec.SecretKeySpec; 12 | 13 | class FunWithGcm { 14 | public static String encode(final byte[] bytes) { 15 | String chars = "0123456789abcdef"; 16 | StringBuilder result = new StringBuilder(2 * bytes.length); 17 | for (byte b : bytes) { 18 | // convert to unsigned 19 | int val = b & 0xff; 20 | result.append(chars.charAt(val / 16)); 21 | result.append(chars.charAt(val % 16)); 22 | } 23 | return result.toString(); 24 | } 25 | 26 | public static byte[] encrypt(byte message[], byte key[]) throws Exception { 27 | ByteBuffer in = ByteBuffer.wrap(message, 0, message.length); 28 | ByteBuffer out = ByteBuffer.allocate(message.length + 16 + 12); 29 | SecureRandom rand = new SecureRandom(); 30 | byte iv[] = new byte[12]; 31 | rand.nextBytes(iv); 32 | out.put(iv); 33 | GCMParameterSpec s = new GCMParameterSpec(128, iv); 34 | Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); 35 | c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), s); 36 | c.updateAAD(new byte[0]); 37 | c.doFinal(in, out); 38 | return out.array(); 39 | } 40 | 41 | public static byte[] decrypt(byte ciphertext[], byte key[]) throws Exception { 42 | ByteBuffer in = ByteBuffer.wrap(ciphertext, 0, ciphertext.length); 43 | ByteBuffer out = ByteBuffer.allocate(ciphertext.length - 16 - 12); 44 | byte iv[] = new byte[12]; 45 | in.get(iv); 46 | GCMParameterSpec s = new GCMParameterSpec(128, iv); 47 | Cipher c = Cipher.getInstance("AES/GCM/NoPadding"); 48 | c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), s); 49 | c.doFinal(in, out); 50 | return out.array(); 51 | } 52 | 53 | public static boolean trent(byte ciphertext[], byte key[]) throws Exception { 54 | byte message[]; 55 | System.in.read(); 56 | System.out.println("Trent: I've received the encrypted message \"" + encode(Arrays.copyOf(ciphertext, 4)) + "...\" from Alice,"); 57 | System.out.print(" and the key \"" + encode(Arrays.copyOf(key, 4)) + "...\""); 58 | System.in.read(); 59 | try { 60 | System.out.print("Trent: I will decrypt this and scan for evil messages."); 61 | message = decrypt(ciphertext, key); 62 | System.in.read(); 63 | } catch (Exception e) { // Message did not decrypt. 64 | System.out.print(" Decryption failed!"); 65 | System.in.read(); 66 | System.out.print("Trent: Gotcha, evildoer: Invalid ciphertext!"); 67 | return false; 68 | } 69 | System.out.println(" Decryption successful."); 70 | System.out.print(" Message is \"" + encode(message) + "\"."); 71 | System.in.read(); 72 | if (message[0] == 0x13 && message[1] == 0x37) { 73 | System.out.print(" Evil Message!"); 74 | System.in.read(); 75 | System.out.print("Trent: Gotcha, evildoer: Evil message!"); 76 | return false; 77 | } 78 | System.out.print("Trent: Everything is perfect, no evil detected!"); 79 | return true; 80 | } 81 | 82 | public static void bob(byte ciphertext[], byte key[]) throws Exception { 83 | byte message[]; 84 | System.in.read(); 85 | System.out.println("Bob: I've received the encrypted message \"" + encode(Arrays.copyOf(ciphertext, 4)) + "...\" from Trent, he says it's okay,"); 86 | System.out.print(" and Alice gave me the key \"" + encode(Arrays.copyOf(key, 4)) + "...\""); 87 | System.in.read(); 88 | try { 89 | System.out.print("Bob: Let's try to decrypt this."); 90 | System.in.read(); 91 | message = decrypt(ciphertext, key); 92 | } catch (Exception e) { // Decryption failed. 93 | System.out.print(" Decryption failed!"); 94 | System.in.read(); 95 | System.out.print("Bob: Yeah, that doesn't work?"); 96 | System.in.read(); 97 | System.out.print("Bob could not decrypt the message, Bob wins!"); 98 | return; 99 | } 100 | System.out.println(" Decryption successful."); 101 | System.out.print(" Message is \"" + encode(message) + "\"."); 102 | System.in.read(); 103 | if (message[0] == 0x13 && message[1] == 0x37) { 104 | System.out.print(" Evil Message!"); 105 | System.in.read(); 106 | System.out.print("Bob: OH NO!"); 107 | System.in.read(); 108 | System.out.print("Alice managed to sneak an evil message to Bob, Alice wins!"); 109 | return; 110 | } 111 | System.out.print("Bob: Interesting message!"); 112 | System.in.read(); 113 | System.out.print("Bob got a good message, Bob wins!"); 114 | } 115 | 116 | public static void aliceOutput(byte ciphertext[], byte keyForTrent[], byte keyForBob[]) throws Exception { 117 | if (trent(ciphertext, keyForTrent)) { 118 | bob(ciphertext, keyForBob); 119 | } else { 120 | System.in.read(); 121 | System.out.print("Trent cannot guarantee that the message is good, Bob wins!"); 122 | } 123 | System.in.read(); 124 | } 125 | 126 | public static byte[] gctr(byte message[], byte iv[], byte key[]) throws Exception { 127 | return gctr(message, iv, key, 2); 128 | } 129 | 130 | public static byte[] gctr(byte message[], byte iv[], byte key[], int offset) throws Exception { 131 | ByteBuffer messageBlock = ByteBuffer.allocate(16); 132 | ByteBuffer encrypted = ByteBuffer.allocate(message.length); 133 | for (int i = 0; i < (message.length - 1)/ 16 + 1; i++) { 134 | int leftover = Math.min(message.length - i * 16, 16); 135 | messageBlock.put(message, i * 16, leftover); 136 | messageBlock.put(new byte[16 - leftover]); 137 | encrypted.put(gctrBlock(messageBlock.array(), iv, key, i + offset), 0, leftover); 138 | messageBlock.rewind(); 139 | } 140 | return encrypted.array(); 141 | } 142 | 143 | public static byte[] gctrBlock(byte block[], byte iv[], byte key[], int offset) throws Exception { 144 | ByteBuffer ivp = ByteBuffer.allocate(16); 145 | ivp.put(iv); 146 | ivp.putInt(offset); 147 | Cipher c = Cipher.getInstance("AES/ECB/NoPadding"); 148 | c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES")); 149 | return add(c.doFinal(ivp.array()), block); 150 | } 151 | 152 | public static byte[] authKey(byte key[]) throws Exception { 153 | return gctr(new byte[16], new byte[12], key, 0); 154 | } 155 | 156 | public static byte[] tagBlock(byte iv[], byte key[]) throws Exception { 157 | return gctr(new byte[16], iv, key, 1); 158 | } 159 | 160 | public static byte[] gctrBlock(byte iv[], byte key[], int block) throws Exception { 161 | return gctr(new byte[16], iv, key, block); 162 | } 163 | 164 | public static byte[] add(byte a[], byte b[]) throws Exception { 165 | if (a.length != 16 || b.length != 16) { 166 | throw new Exception("Block size wrong!"); 167 | } 168 | byte res[] = new byte[16]; 169 | for (int i = 0; i < 16; i++) { 170 | res[i] = (byte)(a[i] ^ b[i]); 171 | } 172 | return res; 173 | } 174 | 175 | public static int degree(byte in[]) throws Exception { 176 | if (in.length != 16) { 177 | throw new Exception("Block size wrong!"); 178 | } 179 | for (int i = 0; i < 16; i++) { 180 | for (int j = 0; j < 8; j++) { 181 | int mask = 0x01 << j; 182 | if ((mask & in[15 - i]) == mask) { 183 | return 8 * (15 - i) + 7 - j; 184 | } 185 | } 186 | } 187 | return -1; 188 | } 189 | 190 | public static byte[] powerOfX(int n) throws Exception { 191 | if (n < 0 || n >= 128) { 192 | throw new Exception("Wrong exponent size"); 193 | } 194 | byte res[] = new byte[16]; 195 | res[n / 8] = (byte)(0x01 << (7 - (n % 8))); 196 | return res; 197 | } 198 | 199 | public static byte[] inverse(byte[] a) throws Exception { 200 | if (a.length != 16) { 201 | throw new Exception("Block size wrong!"); 202 | } 203 | int deg = degree(a); 204 | if (deg < 0) { 205 | throw new Exception("Division by zero!"); 206 | } 207 | if (deg == 0) { 208 | return a; 209 | } 210 | a = Arrays.copyOf(a, 16); 211 | byte b[] = mul(Arrays.copyOf(a, 16), powerOfX(128 - deg)); 212 | // p = X^128 + X^7 + X^2 + X + 1 213 | byte x[] = powerOfX(0); // x(0) * a + 0 * p = a(0) 214 | byte y[] = powerOfX(128 - deg); // y(0) * a + 1 * p = b(0) 215 | while (deg > 0) { 216 | int degb = degree(b); 217 | if (deg > degb) { 218 | byte tmp[] = a; 219 | a = b; 220 | b = tmp; 221 | tmp = x; 222 | x = y; 223 | y = tmp; 224 | int degtmp = deg; 225 | deg = degb; 226 | degb = degtmp; 227 | } 228 | b = add(mul(a, powerOfX(degb - deg)), b); // b(n+1) = b(n) + X^(deg(b(n)) - deg(a(n))) * a(n) 229 | // x(n) * a + ? * p = a(n) 230 | // y(n) * a + ? * p = b(n) 231 | // b(n + 1) = y(n) * a + X^d * (x(n) * a)) = (y(n) + X^d * x(n)) * a 232 | // x(n + 1) = x(n) 233 | // y(n + 1) = y(n) + X^d * x(n) 234 | y = add(y, mul(x, powerOfX(degb - deg))); 235 | } 236 | return x; 237 | } 238 | 239 | public static byte rightShiftByteOne(byte in) { 240 | return (byte)((in >> 1) & 0x7f); 241 | } 242 | 243 | public static byte[] mulByX(byte a[]) throws Exception { 244 | if (a.length != 16) { 245 | throw new Exception("Block size wrong!"); 246 | } 247 | byte res[] = new byte[16]; 248 | for (int i = 1; i < 16; i++) { 249 | res[i] = (byte)(rightShiftByteOne(a[i]) | (a[i-1] << 7)); 250 | } 251 | res[0] = (byte)(rightShiftByteOne(a[0]) ^ (((a[15] & 0x01) == 0x01) ? 0xe1: 0)); 252 | return res; 253 | } 254 | 255 | public static byte[] mul(byte a[], byte b[]) throws Exception { 256 | if (a.length != 16 || b.length != 16) { 257 | throw new Exception("Block size wrong!"); 258 | } 259 | byte res[] = new byte[16]; 260 | byte mulX[] = Arrays.copyOf(a, 16); 261 | for (int i = 0; i < 16; i++) { 262 | for (int j = 0; j < 8; j++) { 263 | int mask = 0x01 << 7 - j; 264 | if ((mask & b[i]) == mask) { 265 | res = add(res, mulX); 266 | } 267 | mulX = mulByX(mulX); 268 | } 269 | } 270 | return res; 271 | } 272 | 273 | public static byte[] lengthBlock(int aadLength, int ciphertextLength) { 274 | ByteBuffer lastBlock = ByteBuffer.allocate(16); 275 | lastBlock.putLong(aadLength * 8); 276 | lastBlock.putLong(ciphertextLength * 8); 277 | return lastBlock.array(); 278 | } 279 | 280 | public static byte[] ghash(byte aad[], byte ciphertext[], byte authKey[]) throws Exception { 281 | byte block[]; 282 | byte res[] = new byte[16]; 283 | for (int i = 0; i < (aad.length + 15) / 16; i++) { 284 | block = Arrays.copyOfRange(aad, i*16, (i+1)*16); 285 | res = mul(add(res, block), authKey); 286 | } 287 | for (int i = 0; i < (ciphertext.length + 15) / 16; i++) { 288 | block = Arrays.copyOfRange(ciphertext, i*16, (i+1)*16); 289 | res = mul(add(res, block), authKey); 290 | } 291 | res = mul(add(res, lengthBlock(aad.length, ciphertext.length)), authKey); 292 | return res; 293 | } 294 | 295 | public static byte[] manualGCM(byte message[], byte iv[], byte key[]) throws Exception { 296 | ByteBuffer out = ByteBuffer.allocate(message.length + 12 + 16); 297 | out.put(iv); 298 | byte ciphertext[] = gctr(message, iv, key); 299 | out.put(ciphertext); 300 | byte tagBlock[] = tagBlock(iv, key); 301 | byte ghash[] = ghash(new byte[0], ciphertext, authKey(key)); 302 | out.put(add(tagBlock, ghash)); 303 | return out.array(); 304 | } 305 | 306 | public static void main(String[] args) throws Exception { 307 | byte message[] = "Hello, World!".getBytes(Charset.forName("UTF-8")); 308 | SecureRandom rand = new SecureRandom(); 309 | byte key[] = new byte[32]; 310 | rand.nextBytes(key); 311 | byte key2[] = new byte[32]; 312 | rand.nextBytes(key2); 313 | byte ciphertext[] = encrypt(message, key); 314 | System.out.println("---------------------------------------------------------------------------"); 315 | System.out.println("Scenario 1: good message, Bob and Trent get the correct key."); 316 | System.out.print("---------------------------------------------------------------------------"); 317 | aliceOutput(ciphertext, key, key); 318 | System.out.println(); 319 | System.out.println("---------------------------------------------------------------------------"); 320 | System.out.println("Scenario 2: good message, Trent gets the correct key, Bob gets a wrong key."); 321 | System.out.print("---------------------------------------------------------------------------"); 322 | aliceOutput(ciphertext, key, key2); 323 | System.out.println(); 324 | System.out.println("---------------------------------------------------------------------------"); 325 | System.out.println("Scenario 3: good message, Trent gets a wrong key, Bob gets the correct key."); 326 | System.out.print("---------------------------------------------------------------------------"); 327 | aliceOutput(ciphertext, key2, key); 328 | message[0] = 0x13; 329 | message[1] = 0x37; 330 | ciphertext = encrypt(message, key); 331 | System.out.println(); 332 | System.out.println("---------------------------------------------------------------------------"); 333 | System.out.println("Scenario 4: evil message, Bob and Trent get the correct key."); 334 | System.out.print("---------------------------------------------------------------------------"); 335 | aliceOutput(ciphertext, key, key); 336 | 337 | byte message1[] = new byte[16]; 338 | message1[0] = 0x13; 339 | message1[1] = 0x37; 340 | byte iv[] = new byte[12]; 341 | rand.nextBytes(iv); 342 | byte authKey1[] = authKey(key); 343 | byte authKey2[] = authKey(key2); 344 | byte tagBlock1[] = tagBlock(iv, key); 345 | byte tagBlock2[] = tagBlock(iv, key2); 346 | byte ciphertext1[] = gctr(message1, iv, key); 347 | byte lengthBlock[] = lengthBlock(0, 32); 348 | // tag(H, tb) = c1 * H^3 + c2 * H^2 + lb * H + tb 349 | // c1 * H1^3 + c2 * H1^2 + lb * H1 + tb1 = c1 * H2^3 + c2 * H2^2 + lb * H2 + tb2 350 | // c2 * (H1^2 + H2^2) = c1 * (H1^3 + H2^3) + lb * (H1 + H2) + tb1 + tb2 351 | byte rhs[] = add(tagBlock1, tagBlock2); 352 | rhs = add(rhs, mul(lengthBlock, add(authKey1, authKey2))); 353 | byte authKey1sq[] = mul(authKey1, authKey1); 354 | byte authKey2sq[] = mul(authKey2, authKey2); 355 | byte lhs[] = add(authKey1sq, authKey2sq); 356 | byte authKey1cb[] = mul(authKey1sq, authKey1); 357 | byte authKey2cb[] = mul(authKey2sq, authKey2); 358 | rhs = add(rhs, mul(ciphertext1, add(authKey1cb, authKey2cb))); 359 | byte ciphertext2[] = mul(inverse(lhs), rhs); 360 | ByteBuffer attackCiphertext = ByteBuffer.allocate(32); 361 | attackCiphertext.put(ciphertext1); 362 | attackCiphertext.put(ciphertext2); 363 | byte ghash[] = ghash(new byte[0], attackCiphertext.array(), authKey1); 364 | ByteBuffer attack = ByteBuffer.allocate(12 + 32 + 16); 365 | attack.put(iv); 366 | attack.put(ciphertext1); 367 | attack.put(ciphertext2); 368 | attack.put(add(tagBlock1, ghash)); 369 | ciphertext = attack.array(); 370 | System.out.println(); 371 | System.out.println("---------------------------------------------------------------------------"); 372 | System.out.println("Scenario 5: Trent gets a good message with the correct key,"); 373 | System.out.println(" Bob gets an evil message with the correct key."); 374 | System.out.print("---------------------------------------------------------------------------"); 375 | aliceOutput(ciphertext, key2, key); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This is a repository to play with various basic cryptographic attacks for 4 | myself. 5 | 6 | # CBC Padding Oracle Attack 7 | 8 | The CBC padding oracle attack is an attack that can be run against any endpoint 9 | that uses unauthenticated CBC. All that is needed is that the endpoint attempts 10 | to decrypt a message and failing on invalid padding. The attack allows for 11 | recovery of the plaintext. 12 | 13 | ## How it works 14 | 15 | ### CBC Mode 16 | 17 | In order to avoid encrypting a repeated plaintext block to the same ciphertext, 18 | CBC mode ranodmizes the input into AES by xoring the plaintext block with the 19 | previous ciphertext block, using the IV for the first plaintext block. 20 | 21 | This avoids the problem of ECB mode, as for all intents and purposes, the input 22 | into the AES block function will always be random: For the first block, as the 23 | IV for CBC mode is chosen randomly, and for all subsequent blocks because the 24 | ouput of AES without knowledge of the key is indistinguishable from random 25 | numbers. If any block or IV ever repeats, information is leaked to the attacker. 26 | As this is a random collision chance, the number of messages that can be safely 27 | encrypted with CBC mode is bound by the birthday paradox on the block size. 28 | 29 | In order for CBC mode to work, the plaintext has to be a multiple of the 30 | blocksize, as we need a full block to encrypt or decrypt with the block cipher. 31 | 32 | ### Padding 33 | 34 | In order to achieve this multiple of the block size requirement, we have to pad 35 | the ciphertext. Any bijective mapping between plaintext and padded plaintexts 36 | works, but we will focus on the PKCS 5 padding here. This padding works by 37 | taking the number of bytes missing to a full multiple of 16 bytes 38 | 39 | ### Attack 40 | 41 | The attacker takes the ciphertext they want to decrypt and tries to flip bits 42 | so that the last byte forms a valid padding byte for a ciphertext one shorter 43 | than the original. 44 | 45 | The padding used (PKCS5) adds n bytes of value n to the end of the plaintext 46 | with n between 1 and 16 chosen so that the padded plaintext has a size that is 47 | a multiple of 16. 48 | CBC mode xors the preceeding ciphertext block (or the IV) with the plaintext 49 | before encrypting it. So changing the corresponding byte in the preceeding 50 | ciphertext block changes what the decrypted byte looks like. 51 | 52 | EXAMPLE: Suppose the plaintext has 31 characters with the last having the value 53 | s. In order to obtain a 32 byte padded plaintext, a single byte with value 0x01 54 | is appended. If we change byte 15 of the ciphertext (indexing with 0) by xoring 55 | with 0x03 (binary 11) than the padding byte is changed to 0x02. The decryption 56 | will only be successful if s happens to be 0x02 and otherwise 57 | 58 | ## The code 59 | 60 | # GCM Key Commitment Attack 61 | -------------------------------------------------------------------------------- /fun_with_gcm_siv.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "gauss.h" 8 | #include "gf2_128_polyval.h" 9 | #include "openssl/evp.h" 10 | #include "openssl/rand.h" 11 | 12 | constexpr int kNonceSize = 12; 13 | constexpr int kTagSize = 16; 14 | constexpr int kKeySize = 16; 15 | 16 | template 17 | void PrintHex(const T& data) { 18 | for (int i = 0; i < data.size(); i++) { 19 | std::cout << std::hex << std::setw(2) << std::setfill('0') 20 | << static_cast(data[i]); 21 | } 22 | std::cout << std::endl; 23 | } 24 | 25 | class ManualAesGcmSiv { 26 | public: 27 | std::array EncryptBlock( 28 | std::array key, 29 | std::array block) { 30 | bssl::UniquePtr ctx(EVP_CIPHER_CTX_new()); 31 | EVP_EncryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr); 32 | int len; 33 | std::array out; 34 | if (EVP_EncryptUpdate(ctx.get(), out.data(), &len, block.data(), 35 | block.size()) != 1 || 36 | len != kBlockSize) { 37 | std::cerr << "Block encryption failed" << std::endl; 38 | std::abort(); 39 | } 40 | return out; 41 | } 42 | 43 | std::array DecryptBlock( 44 | std::array key, 45 | std::array block) { 46 | bssl::UniquePtr ctx(EVP_CIPHER_CTX_new()); 47 | EVP_DecryptInit_ex(ctx.get(), cipher, nullptr, key.data(), nullptr); 48 | EVP_CIPHER_CTX_set_padding(ctx.get(), 0); 49 | int len; 50 | std::array out; 51 | if (EVP_DecryptUpdate(ctx.get(), out.data(), &len, block.data(), 52 | block.size()) != 1 || 53 | len != kBlockSize) { 54 | std::cerr << "Block decryption failed" << std::endl; 55 | std::abort(); 56 | } 57 | return out; 58 | } 59 | 60 | std::array LengthBlock(int additional_data_size, 61 | int plaintext_size) { 62 | std::array lb; 63 | additional_data_size *= 8; 64 | for (int i = 0; i < 8; i++) { 65 | lb[i] = additional_data_size % 256; 66 | additional_data_size /= 256; 67 | } 68 | plaintext_size *= 8; 69 | for (int i = 8; i < kBlockSize; i++) { 70 | lb[i] = plaintext_size % 256; 71 | plaintext_size /= 256; 72 | } 73 | return lb; 74 | } 75 | 76 | GF2_128_polyval AuthConstant( 77 | const std::array& auth_key) { 78 | std::array constant; 79 | constant.fill(0x00); 80 | constant[15] = 0x92; 81 | constant[14] = 0x04; 82 | constant[0] = 0x01; 83 | GF2_128_polyval auth_constant(constant); 84 | auth_constant *= auth_key; 85 | return auth_constant; 86 | } 87 | 88 | GF2_128_polyval Polyval(const std::array& auth_key, 89 | const std::vector& additional_data, 90 | const std::vector& plaintext) { 91 | std::array last_ad_block; 92 | last_ad_block.fill(0x00); 93 | int aligned_ad_size = kBlockSize * (additional_data.size() / kBlockSize); 94 | memcpy(last_ad_block.data(), additional_data.data() + aligned_ad_size, 95 | additional_data.size() - aligned_ad_size); 96 | std::array last_block; 97 | last_block.fill(0x00); 98 | int aligned_size = kBlockSize * (plaintext.size() / kBlockSize); 99 | memcpy(last_block.data(), plaintext.data() + aligned_size, 100 | plaintext.size() - aligned_size); 101 | GF2_128_polyval auth_constant = AuthConstant(auth_key); 102 | GF2_128_polyval result(0); 103 | for (int i = 0; i < aligned_ad_size; i += kBlockSize) { 104 | result += GF2_128_polyval(additional_data.data() + i); 105 | result *= auth_constant; 106 | } 107 | if (additional_data.size() % kBlockSize != 0) { 108 | result += last_ad_block; 109 | result *= auth_constant; 110 | } 111 | for (int i = 0; i < aligned_size; i += kBlockSize) { 112 | result += GF2_128_polyval(plaintext.data() + i); 113 | result *= auth_constant; 114 | } 115 | if (plaintext.size() % kBlockSize != 0) { 116 | result += last_block; 117 | result *= auth_constant; 118 | } 119 | result += LengthBlock(additional_data.size(), plaintext.size()); 120 | result *= auth_constant; 121 | return result; 122 | } 123 | 124 | std::array CounterBlock( 125 | const std::array& initial, uint32_t counter) { 126 | std::array in = initial; 127 | uint32_t in_ctr = 0; 128 | for (int i = 3; i >= 0; i--) { 129 | in_ctr *= 256; 130 | in_ctr += initial[i]; 131 | } 132 | counter += in_ctr; 133 | for (int i = 0; i < 4; i++) { 134 | in[i] = counter % 256; 135 | counter /= 256; 136 | } 137 | return in; 138 | } 139 | 140 | std::pair, std::array> 141 | GenerateKeys(const std::array& key, 142 | const std::array& nonce) { 143 | std::array input; 144 | input.fill(0x00); 145 | std::array output; 146 | std::array auth_key; 147 | std::array enc_key; 148 | memcpy(input.data() + 4, nonce.data(), kNonceSize); 149 | output = EncryptBlock(key, input); 150 | memcpy(auth_key.data(), output.data(), 8); 151 | input[0] = 0x01; 152 | output = EncryptBlock(key, input); 153 | memcpy(auth_key.data() + 8, output.data(), 8); 154 | input[0] = 0x02; 155 | output = EncryptBlock(key, input); 156 | memcpy(enc_key.data(), output.data(), 8); 157 | input[0] = 0x03; 158 | output = EncryptBlock(key, input); 159 | memcpy(enc_key.data() + 8, output.data(), 8); 160 | if (kKeySize == 32) { 161 | input[0] = 0x04; 162 | output = EncryptBlock(key, input); 163 | memcpy(enc_key.data() + 16, output.data(), 8); 164 | input[0] = 0x05; 165 | output = EncryptBlock(key, input); 166 | memcpy(enc_key.data() + 24, output.data(), 8); 167 | } 168 | return make_pair(auth_key, enc_key); 169 | } 170 | 171 | std::array CounterForTag( 172 | const std::array& tag) { 173 | std::array counter = tag; 174 | counter[15] |= 0x80; 175 | return counter; 176 | } 177 | 178 | std::array TagForPolyvalResult( 179 | const std::array& enc_key, 180 | const std::array& nonce, 181 | const GF2_128_polyval& result) { 182 | std::array s = result.get(); 183 | for (int i = 0; i < kNonceSize; i++) { 184 | s[i] ^= nonce[i]; 185 | } 186 | s[15] &= 0x7f; 187 | return EncryptBlock(enc_key, s); 188 | } 189 | 190 | GF2_128_polyval PolyvalResultForTag( 191 | const std::array& enc_key, 192 | const std::array& nonce, 193 | const std::array& tag, bool* ok) { 194 | std::array s = DecryptBlock(enc_key, tag); 195 | *ok = (s[15] & 0x80) == 0; 196 | for (int i = 0; i < kNonceSize; i++) { 197 | s[i] ^= nonce[i]; 198 | } 199 | return s; 200 | } 201 | 202 | std::pair>, 203 | std::vector> 204 | SystemOfEquations(const GF2_128_polyval& auth_constant1, 205 | const GF2_128_polyval& polyval_result1, 206 | const std::vector& key_stream1, 207 | const GF2_128_polyval& auth_constant2, 208 | const GF2_128_polyval& polyval_result2, 209 | const std::vector& key_stream2, 210 | int num_blocks) { 211 | std::vector> matrix( 212 | num_blocks + 2, std::vector(2 * num_blocks, 0)); 213 | std::vector rhs(num_blocks + 2); 214 | // X11 * H1^3 + X12 * H1^2 = S1 + LB1 * H1 215 | // X21 * H2^3 + X22 * H2^2 = S2 + LB2 * H2 216 | // X11 + KS11 = X21 + KS21 217 | // X12 + KS12 = X22 + KS22 218 | rhs[0] = polyval_result1 + 219 | auth_constant1 * LengthBlock(0, kBlockSize * num_blocks); 220 | rhs[1] = polyval_result2 + 221 | auth_constant2 * LengthBlock(0, kBlockSize * num_blocks); 222 | GF2_128_polyval h1 = auth_constant1; 223 | GF2_128_polyval h2 = auth_constant2; 224 | for (int i = 0; i < num_blocks; i++) { 225 | h1 *= auth_constant1; 226 | h2 *= auth_constant2; 227 | int index = num_blocks - i - 1; 228 | matrix[0][index] = h1; 229 | matrix[1][num_blocks + index] = h2; 230 | matrix[i + 2][i] = 1; 231 | matrix[i + 2][num_blocks + i] = 1; 232 | rhs[i + 2] = key_stream1[i] + key_stream2[i]; 233 | } 234 | return std::make_pair(matrix, rhs); 235 | } 236 | 237 | std::vector MakeFragileCiphertext( 238 | const std::array& key1, 239 | const std::array& key2) { 240 | int num_blocks = 2; 241 | std::vector ciphertext(kNonceSize + num_blocks * kBlockSize + 242 | kBlockSize); 243 | std::array nonce; 244 | std::array auth_key1; 245 | std::array enc_key1; 246 | std::array auth_key2; 247 | std::array enc_key2; 248 | RAND_bytes(nonce.data(), kNonceSize); 249 | for (int i = 0; i < nonce.size(); i++) { 250 | ciphertext[i] = nonce[i]; 251 | } 252 | std::tie(auth_key1, enc_key1) = GenerateKeys(key1, nonce); 253 | std::tie(auth_key2, enc_key2) = GenerateKeys(key2, nonce); 254 | bool ok1 = false; 255 | bool ok2 = false; 256 | GF2_128_polyval polyval_result1; 257 | GF2_128_polyval polyval_result2; 258 | std::array tag; 259 | while (!(ok1 && ok2)) { 260 | RAND_bytes(tag.data(), kBlockSize); 261 | std::cout << "Tag:\n"; 262 | PrintHex(tag); 263 | polyval_result1 = PolyvalResultForTag(enc_key1, nonce, tag, &ok1); 264 | polyval_result2 = PolyvalResultForTag(enc_key2, nonce, tag, &ok2); 265 | } 266 | auto counter = CounterForTag(tag); 267 | std::vector key_stream1(num_blocks); 268 | std::vector key_stream2(num_blocks); 269 | for (int i = 0; i < num_blocks; i++) { 270 | key_stream1[i] = EncryptBlock(enc_key1, CounterBlock(counter, i)); 271 | key_stream2[i] = EncryptBlock(enc_key2, CounterBlock(counter, i)); 272 | } 273 | std::vector> matrix; 274 | std::vector rhs; 275 | std::tie(matrix, rhs) = SystemOfEquations( 276 | AuthConstant(auth_key1), polyval_result1, key_stream1, 277 | AuthConstant(auth_key2), polyval_result2, key_stream2, num_blocks); 278 | std::vector result; 279 | std::vector> homogenous; 280 | std::tie(result, homogenous) = solve(matrix, rhs); 281 | if (result.size() != 2 * num_blocks) { 282 | std::cout << "Matrix rank too small" << std::endl; 283 | return MakeFragileCiphertext(key1, key2); 284 | } 285 | std::vector plaintext1(kBlockSize * num_blocks); 286 | std::vector plaintext2(kBlockSize * num_blocks); 287 | for (int i = 0; i < num_blocks; i++) { 288 | std::cout << "Plaintext1 Fragment " << i << std::endl; 289 | PrintHex(result[i].get()); 290 | std::cout << "Plaintext2 Fragment " << i << std::endl; 291 | PrintHex(result[num_blocks + i].get()); 292 | auto ciphertext_fragment = key_stream1[i] + result[i]; 293 | std::cout << "Ciphertext Fragment " << i << std::endl; 294 | PrintHex(ciphertext_fragment.get()); 295 | ciphertext_fragment = key_stream2[i] + result[num_blocks + i]; 296 | std::cout << "Ciphertext Fragment " << i << std::endl; 297 | PrintHex(ciphertext_fragment.get()); 298 | for (int j = 0; j < ciphertext_fragment.get().size(); j++) { 299 | plaintext1[kBlockSize * i + j] = result[i].get()[j]; 300 | plaintext2[kBlockSize * i + j] = result[num_blocks + i].get()[j]; 301 | ciphertext[kNonceSize + kBlockSize * i + j] = 302 | ciphertext_fragment.get()[j]; 303 | } 304 | } 305 | std::vector additional_data; 306 | std::cout << "Expected Polyval Result 1:\n"; 307 | PrintHex(polyval_result1.get()); 308 | std::cout << "Actual Polyval Result 1:\n"; 309 | PrintHex(Polyval(auth_key1, additional_data, plaintext1).get()); 310 | std::cout << "Expected Polyval Result 2:\n"; 311 | PrintHex(polyval_result2.get()); 312 | std::cout << "Actual Polyval Result 2:\n"; 313 | PrintHex(Polyval(auth_key2, additional_data, plaintext2).get()); 314 | for (int i = 0; i < tag.size(); i++) { 315 | ciphertext[kNonceSize + kBlockSize * num_blocks + i] = tag[i]; 316 | } 317 | return ciphertext; 318 | } 319 | 320 | std::vector Encrypt(const std::array& key, 321 | const std::array& nonce, 322 | const std::vector& additional_data, 323 | const std::vector& plaintext) { 324 | std::vector ciphertext(kNonceSize); 325 | memcpy(ciphertext.data(), nonce.data(), nonce.size()); 326 | auto keys = GenerateKeys(key, nonce); 327 | auto tag = TagForPolyvalResult( 328 | keys.second, nonce, Polyval(keys.first, additional_data, plaintext)); 329 | std::array counter = CounterForTag(tag); 330 | std::array keystream; 331 | for (int i = 0; i < plaintext.size(); i++) { 332 | if (i % kBlockSize == 0) { 333 | keystream = EncryptBlock(keys.second, CounterBlock(counter, i / 16)); 334 | } 335 | ciphertext.push_back(keystream[i % kBlockSize] ^ plaintext[i]); 336 | } 337 | for (int i = 0; i < tag.size(); i++) { 338 | ciphertext.push_back(tag[i]); 339 | } 340 | return ciphertext; 341 | } 342 | 343 | private: 344 | const EVP_CIPHER* cipher = 345 | kKeySize == 16 ? EVP_aes_128_ecb() : EVP_aes_256_ecb(); 346 | }; 347 | 348 | std::vector DecryptBoringSSL(std::array key, 349 | std::vector additional_data, 350 | std::vector ciphertext) { 351 | const EVP_AEAD* aead = EVP_aead_aes_128_gcm_siv(); 352 | bssl::UniquePtr ctx( 353 | EVP_AEAD_CTX_new(aead, key.data(), key.size(), kTagSize)); 354 | size_t len; 355 | std::vector decrypted(ciphertext.size() - kNonceSize - kTagSize); 356 | if (EVP_AEAD_CTX_open(ctx.get(), decrypted.data(), &len, decrypted.size(), 357 | ciphertext.data(), kNonceSize, 358 | ciphertext.data() + kNonceSize, 359 | ciphertext.size() - kNonceSize, additional_data.data(), 360 | additional_data.size()) != 1 || 361 | len != decrypted.size()) { 362 | std::cerr << "Decryption failed!" << std::endl; 363 | std::abort(); 364 | } 365 | std::cout << "Decryption successful:\n"; 366 | PrintHex(decrypted); 367 | return decrypted; 368 | } 369 | 370 | int main(int argc, char** argv) { 371 | std::array key; 372 | RAND_bytes(key.data(), key.size()); 373 | std::array nonce; 374 | RAND_bytes(nonce.data(), nonce.size()); 375 | std::vector plaintext(6); 376 | plaintext[0] = 'H'; 377 | plaintext[1] = 'e'; 378 | plaintext[2] = 'l'; 379 | plaintext[3] = 'l'; 380 | plaintext[4] = 'o'; 381 | plaintext[5] = 0x00; 382 | 383 | std::vector additional_data; 384 | std::vector ciphertext(kNonceSize + kTagSize + plaintext.size()); 385 | memcpy(ciphertext.data(), nonce.data(), nonce.size()); 386 | const EVP_AEAD* aead = EVP_aead_aes_128_gcm_siv(); 387 | bssl::UniquePtr ctx( 388 | EVP_AEAD_CTX_new(aead, key.data(), key.size(), kTagSize)); 389 | size_t len; 390 | if (EVP_AEAD_CTX_seal(ctx.get(), ciphertext.data() + kNonceSize, &len, 391 | ciphertext.size() - kNonceSize, nonce.data(), 392 | nonce.size(), plaintext.data(), plaintext.size(), 393 | additional_data.data(), additional_data.size()) != 1 || 394 | len != ciphertext.size() - kNonceSize) { 395 | std::cerr << "Encryption failed!" << std::endl; 396 | return 1; 397 | } 398 | std::cout << "Encryption successful:" << std::endl; 399 | PrintHex(ciphertext); 400 | DecryptBoringSSL(key, additional_data, ciphertext); 401 | ManualAesGcmSiv manual; 402 | ciphertext = manual.Encrypt(key, nonce, additional_data, plaintext); 403 | std::cout << "Manual Encryption successful:" << std::endl; 404 | PrintHex(ciphertext); 405 | DecryptBoringSSL(key, additional_data, ciphertext); 406 | std::array key2; 407 | RAND_bytes(key2.data(), key2.size()); 408 | ciphertext = manual.MakeFragileCiphertext(key, key2); 409 | std::cout << "Created fragile ciphertext:" << std::endl; 410 | PrintHex(ciphertext); 411 | DecryptBoringSSL(key, additional_data, ciphertext); 412 | DecryptBoringSSL(key2, additional_data, ciphertext); 413 | return 0; 414 | } 415 | -------------------------------------------------------------------------------- /gauss.h: -------------------------------------------------------------------------------- 1 | #ifndef GAUSS_H_ 2 | #define GAUSS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | void scaleRow(std::vector>* matrix, int row, T value) { 10 | for (int j = 0; j < (*matrix)[row].size(); j++) { 11 | (*matrix)[row][j] *= value; 12 | } 13 | } 14 | 15 | template 16 | void shearRow(std::vector>* matrix, int pivot_row, T value, 17 | int row) { 18 | for (int j = 0; j < (*matrix)[row].size(); j++) { 19 | (*matrix)[row][j] -= value * (*matrix)[pivot_row][j]; 20 | } 21 | } 22 | 23 | template 24 | void swapRows(std::vector>* matrix, int row1, int row2) { 25 | if (row1 == row2) { 26 | return; 27 | } 28 | std::swap((*matrix)[row1], (*matrix)[row2]); 29 | } 30 | 31 | template 32 | std::pair, std::vector>> solve( 33 | const std::vector>& matrix, const std::vector& rhs) { 34 | int rows = matrix.size(); 35 | int cols = matrix[0].size(); 36 | std::vector> m(rows, std::vector(cols + 1)); 37 | for (int i = 0; i < rows; i++) { 38 | for (int j = 0; j < cols; j++) { 39 | m[i][j] = matrix[i][j]; 40 | } 41 | m[i][cols] = rhs[i]; 42 | } 43 | std::set slips; 44 | int pivot_row = 0; 45 | int pivot_col = 0; 46 | while (pivot_row < rows) { 47 | if (pivot_col >= cols) { 48 | return std::make_pair(std::vector(), std::vector>()); 49 | } 50 | for (int i = pivot_row; i < rows; i++) { 51 | if (m[i][pivot_col] != 0) { 52 | swapRows(&m, pivot_row, i); 53 | break; 54 | } 55 | } 56 | if (m[pivot_row][pivot_col] == 0) { 57 | slips.insert(pivot_col); 58 | pivot_col++; 59 | continue; 60 | } 61 | scaleRow(&m, pivot_row, 1 / m[pivot_row][pivot_col]); 62 | for (int i = pivot_row + 1; i < rows; i++) { 63 | shearRow(&m, pivot_row, m[i][pivot_col], i); 64 | } 65 | pivot_row++; 66 | pivot_col++; 67 | } 68 | while (pivot_col < cols) { 69 | slips.insert(pivot_col); 70 | pivot_col++; 71 | } 72 | std::vector result(cols); 73 | std::vector> hom; 74 | pivot_col = cols - 1; 75 | for (int pivot_row = rows - 1; pivot_row >= 0; pivot_row--) { 76 | while (slips.find(pivot_col) != slips.end()) { 77 | result[pivot_col] = 0; 78 | pivot_col--; 79 | } 80 | for (int i = pivot_row - 1; i >= 0; i--) { 81 | shearRow(&m, pivot_row, m[i][pivot_col], i); 82 | } 83 | result[pivot_col] = m[pivot_row][cols]; 84 | pivot_col--; 85 | } 86 | pivot_col = cols - 1; 87 | for (int pivot_row = rows - 1; pivot_row >= 0; pivot_row--) { 88 | int last_col = pivot_col; 89 | while (slips.find(pivot_col) != slips.end()) { 90 | result[pivot_col] = 0; 91 | pivot_col--; 92 | } 93 | for (int j = last_col; j > pivot_col; j--) { 94 | std::vector h(cols, 0); 95 | h[j] = 1; 96 | int col = pivot_col; 97 | for (int i = pivot_row; i >= 0; i--) { 98 | while (slips.find(col) != slips.end()) { 99 | col--; 100 | } 101 | h[col] -= m[i][j]; 102 | col--; 103 | } 104 | hom.push_back(h); 105 | } 106 | pivot_col--; 107 | } 108 | return make_pair(result, hom); 109 | } 110 | 111 | #endif // GAUSS_H_ 112 | -------------------------------------------------------------------------------- /gf2_128_polyval.cc: -------------------------------------------------------------------------------- 1 | #include "gf2_128_polyval.h" 2 | 3 | #include 4 | #include 5 | 6 | GF2_128_polyval::GF2_128_polyval(int val) : val_() { 7 | for (int i = 0; i < kBlockSize; i++) { 8 | val_[i] = val % 256; 9 | val /= 256; 10 | } 11 | } 12 | 13 | GF2_128_polyval::GF2_128_polyval(const uint8_t* val) : val_() { 14 | for (int i = 0; i < kBlockSize; i++) { 15 | val_[i] = val[i]; 16 | } 17 | } 18 | 19 | bool operator==(const GF2_128_polyval& lhs, const GF2_128_polyval& rhs) { 20 | for (int i = 0; i < kBlockSize; i++) { 21 | if (lhs.val_[i] != rhs.val_[i]) { 22 | return false; 23 | } 24 | } 25 | return true; 26 | } 27 | 28 | GF2_128_polyval GF2_128_polyval::inv() const { 29 | int deg = degree(); 30 | if (deg < 0) { 31 | std::cerr << "Division by zero!" << std::endl; 32 | std::abort(); 33 | } 34 | if (deg == 0) { 35 | return *this; 36 | } 37 | GF2_128_polyval a(*this); 38 | GF2_128_polyval b; 39 | b.power_of_x_assign(128 - deg); 40 | b *= a; 41 | GF2_128_polyval x; 42 | x.power_of_x_assign(0); 43 | GF2_128_polyval y; 44 | y.power_of_x_assign(128 - deg); 45 | while (deg > 0) { 46 | int degb = b.degree(); 47 | if (deg > degb) { 48 | std::swap(a, b); 49 | std::swap(x, y); 50 | std::swap(deg, degb); 51 | } 52 | GF2_128_polyval q; 53 | q.power_of_x_assign(degb - deg); 54 | b += a * q; 55 | y += x * q; 56 | } 57 | return x; 58 | } 59 | 60 | int GF2_128_polyval::degree() const { 61 | for (int i = kBlockSize - 1; i >= 0; i--) { 62 | for (int j = 7; j >= 0; j--) { 63 | if (val_[i] & (0x01 << j)) { 64 | return i * 8 + j; 65 | } 66 | } 67 | } 68 | return -1; 69 | } 70 | 71 | void GF2_128_polyval::power_of_x_assign(int exp) { 72 | for (int i = 0; i < kBlockSize; i++) { 73 | val_[i] = 0x00; 74 | } 75 | val_[exp / 8] = 0x01 << (exp % 8); 76 | } 77 | 78 | void GF2_128_polyval::mul_x_assign() { 79 | uint8_t carry = val_[kBlockSize - 1] >> 7; 80 | for (int i = kBlockSize - 1; i >= 1; i--) { 81 | val_[i] = (val_[i] << 1) | (val_[i - 1] >> 7); 82 | } 83 | val_[0] <<= 1; 84 | if (carry != 0) { 85 | val_[kBlockSize - 1] ^= 0xc2; 86 | val_[0] ^= 0x01; 87 | } 88 | } 89 | 90 | GF2_128_polyval& GF2_128_polyval::operator*=(const GF2_128_polyval& rhs) { 91 | GF2_128_polyval mul_by_x(0); 92 | std::swap(val_, mul_by_x.val_); 93 | for (int i = 0; i < kBlockSize; i++) { 94 | uint8_t work = rhs.val_[i]; 95 | for (int j = 0; j < 8; j++) { 96 | if (work & 0x01) { 97 | *this += mul_by_x; 98 | } 99 | work >>= 1; 100 | mul_by_x.mul_x_assign(); 101 | } 102 | } 103 | return *this; 104 | } 105 | GF2_128_polyval& GF2_128_polyval::operator+=(const GF2_128_polyval& rhs) { 106 | for (int i = 0; i < kBlockSize; i++) { 107 | val_[i] ^= rhs.val_[i]; 108 | } 109 | return *this; 110 | } 111 | 112 | std::ostream& operator<<(std::ostream& os, const GF2_128_polyval& rhs) { 113 | char fill = os.fill('0'); 114 | auto flags = os.flags(); 115 | os.setf(std::ios_base::hex, std::ios_base::basefield); 116 | for (int i = kBlockSize - 1; i >= 0; i--) { 117 | os << std::setw(2) << static_cast(rhs.val_[i]); 118 | } 119 | os.fill(fill); 120 | os.flags(flags); 121 | return os; 122 | } 123 | -------------------------------------------------------------------------------- /gf2_128_polyval.h: -------------------------------------------------------------------------------- 1 | #ifndef GF2_128_POLYVAL_H_ 2 | #define GF2_128_POLYVAL_H_ 3 | 4 | #include 5 | #include 6 | 7 | constexpr int kBlockSize = 16; 8 | 9 | class GF2_128_polyval { 10 | public: 11 | GF2_128_polyval() : val_() {} 12 | GF2_128_polyval(int val); 13 | GF2_128_polyval(const uint8_t* val); 14 | GF2_128_polyval(const std::array& val) : val_(val) {} 15 | GF2_128_polyval(std::array&& val) : val_(std::move(val)) {} 16 | GF2_128_polyval(const GF2_128_polyval& rhs) : val_(rhs.val_) {} 17 | GF2_128_polyval(GF2_128_polyval&& rhs) : val_(std::move(rhs.val_)) {} 18 | 19 | const std::array& get() const { return val_; } 20 | 21 | GF2_128_polyval& operator=(GF2_128_polyval rhs) noexcept { 22 | std::swap(val_, rhs.val_); 23 | return *this; 24 | } 25 | 26 | friend bool operator==(const GF2_128_polyval& lhs, 27 | const GF2_128_polyval& rhs); 28 | 29 | friend bool operator!=(const GF2_128_polyval& lhs, 30 | const GF2_128_polyval& rhs) { 31 | return !(lhs == rhs); 32 | } 33 | 34 | friend std::ostream& operator<<(std::ostream& os, const GF2_128_polyval& rhs); 35 | 36 | GF2_128_polyval& operator+=(const GF2_128_polyval& rhs); 37 | friend GF2_128_polyval operator+(GF2_128_polyval lhs, 38 | const GF2_128_polyval& rhs) { 39 | lhs += rhs; 40 | return lhs; 41 | } 42 | 43 | GF2_128_polyval& operator-=(const GF2_128_polyval& rhs) { 44 | return *this += rhs; 45 | } 46 | friend GF2_128_polyval operator-(GF2_128_polyval lhs, 47 | const GF2_128_polyval& rhs) { 48 | lhs -= rhs; 49 | return lhs; 50 | } 51 | 52 | GF2_128_polyval& operator*=(const GF2_128_polyval& rhs); 53 | friend GF2_128_polyval operator*(GF2_128_polyval lhs, 54 | const GF2_128_polyval& rhs) { 55 | lhs *= rhs; 56 | return lhs; 57 | } 58 | 59 | GF2_128_polyval& operator/=(const GF2_128_polyval& rhs) { 60 | return *this *= rhs.inv(); 61 | } 62 | 63 | friend GF2_128_polyval operator/(GF2_128_polyval lhs, 64 | const GF2_128_polyval& rhs) { 65 | lhs /= rhs; 66 | return lhs; 67 | } 68 | 69 | private: 70 | GF2_128_polyval inv() const; 71 | int degree() const; 72 | 73 | void power_of_x_assign(int exp); 74 | void mul_x_assign(); 75 | 76 | std::array val_; 77 | }; 78 | 79 | #endif // GF2_128_POLYVAL_H_ 80 | --------------------------------------------------------------------------------