require_once '../01-basics/06-break-repeating-key-xor.php'; require_once '18-implement-ctr-the-stream-cipher-mode.php'; $plaintexts = array_map('base64_decode', file('20-data.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); $key = getRandomBytes(16); $ciphertexts = array_map('encryptAES128CTR', $plaintexts, array_fill(0, count($plaintexts), $key)); $cipherLens = array_map('strlen', $ciphertexts); // challenge text says use a common length, but we can recover more if we don't // this is because after transposition there's still enough data to statistically recover more /* $minLength = min($cipherLens); $truncated = array_map('str_split', $ciphertexts, array_fill(0, count($plaintexts), $minLength)); $truncated = array_column($truncated, 0); */ $truncated = $ciphertexts; // some copy/paste/tweak from challenge 6 print "\nSolving keys based on English Language scoring:\n"; $blocks = transposeBlocks($ciphertexts); $englishLanguageWeights['/'] = 0; list($topScores, $topChars) = scoreSingleByteXORStrings($blocks, $englishLanguageWeights, 20); $potentialKey = implode(array_map('chr', $topChars)); foreach ($truncated as $k => $ciphertext) { $recovered = $ciphertext ^ $potentialKey; print "{$k}: {$recovered}\n"; } print "\n\nSome texts will not be fully recovered.\nThis is expected for simple automatic statistical recovery.\n\n\n"; foreach ($truncated as $k => $ciphertext) { $recovered = $ciphertext ^ $potentialKey; if ($recovered !== $plaintexts[$k]) { print "Cracked : {$recovered}\nOriginal: {$plaintexts[$k]}\n\n"; } }
function scoreSingleByteXORStrings(array $strings, array $weights, $penalty = 0) { $topScores = []; $topChars = []; foreach ($strings as $pos => $string) { $scores = scoreSingleByteXOR($string, $weights, $penalty); arsort($scores); $topScores[$pos] = current($scores); $topChars[$pos] = key($scores); } return [$topScores, $topChars]; } // don't output if we're included into another script. if (!debug_backtrace()) { $encrypted = array_map('hex2bin', file('04-data.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)); list($topScores, $topChars) = scoreSingleByteXORStrings($encrypted, $englishLanguageWeights); arsort($topScores); print "Highest scoring strings indexes and characters:\n"; $i = 0; foreach ($topScores as $k => $v) { $c = $topChars[$k]; print "{$k} - {$c} - {$v}\n"; if (++$i === 3) { break; } } print "\nDecrypted strings:\n"; $i = 0; foreach ($topScores as $k => $v) { $encryptedLen = strlen($encrypted[$k]); $decypted = $encrypted[$k] ^ str_repeat(chr($topChars[$k]), $encryptedLen);