(load (expand-file-name "../util" (file-name-directory (or load-file-name buffer-file-name)))) (require 'seq) (setq ciphertext (base64-to-bytes (file-contents (expand-file-name "06.txt" (file-name-directory (or load-file-name buffer-file-name)))))) (test-equal (hamming-distance "this is a test" "wokka wokka!!!") 37) (defun analyze-keysizes (bytes min max) (mapcar (lambda (keysize) (let* ((block1 (substring bytes 0 keysize)) (block2 (substring bytes keysize (* keysize 2))) (block3 (substring bytes (* keysize 2) (* keysize 3))) (block4 (substring bytes (* keysize 3) (* keysize 4))) (distance1 (/ (hamming-distance block1 block2) (float keysize))) (distance2 (/ (hamming-distance block2 block3) (float keysize))) (distance3 (/ (hamming-distance block3 block4) (float keysize))) (average (/ (+ distance1 distance2 distance3) 3.0))) (list average keysize))) (number-sequence min max))) (setq best-keysizes (mapcar 'cadr (seq-take (seq-sort (lambda (a b) (< (car a) (car b))) (analyze-keysizes ciphertext 2 40)) 3))) (defun guess-block-byte (block) (let ((best-score 0) (best-byte 0)) (dotimes (i 256) (let ((score (english-score (xor-byte block i)))) (when (> score best-score) (setq best-score score best-byte i)))) best-byte)) (defun guess-key (bytes keysize) (let ((blocks (seq-partition bytes keysize))) (mapconcat (lambda (block) (string (guess-block-byte block))) (transpose-bytes blocks) ""))) (let ((best-score 0) (best-key nil)) (dolist (keysize best-keysizes) (let* ((key (guess-key ciphertext keysize)) (plaintext (xor-key ciphertext key)) (score (english-score plaintext))) (message "Score for key %S: %.2f" key score) (when (> score best-score) (setq best-score score best-key key)))) (message "%s" (xor-key ciphertext best-key)))