defmodule CryptoPals do
def base16to64(x) do
x
|> Base.decode16!(case: :lower)
|> Base.encode64()
end
def fixed_xor(a, b) do
len = String.length(a)
a = Base.decode16!(a, case: :lower) |> b2i()
b = Base.decode16!(b, case: :lower) |> b2i()
Bitwise.bxor(a, b)
|> i2b()
|> Base.encode16(case: :lower)
|> String.pad_leading(len, "0")
end
defp b2i(bin), do: :binary.decode_unsigned(bin)
defp i2b(int), do: :binary.encode_unsigned(int)
def english_score(s) do
if String.printable?(s) do
fre =
String.to_charlist(s)
|> Enum.frequencies()
t = %{?\s => 3, ?e => 2, ?t => 1}
Enum.map(t, fn {k, v} ->
(fre[k] || 0) * v
end)
|> Enum.sum()
else
0
end
end
@spec decrypt_xor(hex :: binary) :: {score :: integer, plain_text :: binary, key :: integer}
def decrypt_xor(ct) do
for i <- 0..255 do
len = String.length(ct) |> div(2)
bin = List.duplicate(i, len) |> :erlang.list_to_binary() |> Base.encode16(case: :lower)
{i, fixed_xor(ct, bin) |> Base.decode16!(case: :lower)}
end
|> Enum.map(fn {i, x} -> {english_score(x), x, i} end)
|> Enum.max()
end
def repeating_xor(pt, ct) do
pt
|> :binary.bin_to_list()
|> Enum.zip(Stream.cycle(ct |> :binary.bin_to_list()))
|> Enum.map(fn {a, b} -> Bitwise.bxor(a, b) end)
|> :binary.list_to_bin()
|> Base.encode16(case: :lower)
end
def edit_distance(a, b) do
Bitwise.bxor(b2i(a), b2i(b))
|> Integer.to_charlist(2)
|> Enum.count(fn x -> x == ?1 end)
end
def decrypt_repeating_xor(ct) do
key_sizes =
for w <- 2..40 do
first = :binary.part(ct, 0, w)
second = :binary.part(ct, w, w + w)
{edit_distance(first, second) / w, w}
end
|> Enum.sort()
|> Enum.take(3)
|> Enum.map(fn {_, s} -> s end)
for w <- key_sizes do
blocks =
ct
|> :binary.bin_to_list()
|> Enum.chunk_every(w, w, :discard)
|> Enum.zip()
|> Enum.map(fn b ->
b
|> Tuple.to_list()
|> :binary.list_to_bin()
|> Base.encode16(case: :lower)
|> decrypt_xor()
end)
if Enum.all?(blocks, fn {score, _r, _key} -> score > 0 end) do
{:ok, blocks |> Enum.map(fn {_, _, key} -> key end) |> :binary.list_to_bin()}
else
{:error, "failed to decrypt with key size: #{w}"}
end
end
|> Enum.find(fn x -> match?({:ok, _}, x) end)
end
def fetch_data(url) do
{b64, 0} = System.cmd("curl", [url])
b64 |> String.replace("\n", "") |> Base.decode64!()
end
def ecb_score(bin, block_size \\ 16) do
:binary.bin_to_list(bin)
|> Enum.chunk_every(block_size, block_size, :discard)
|> Enum.frequencies()
|> Map.values()
|> Enum.filter(fn x -> x > 1 end)
|> Enum.sum()
end
end
"SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t" =
CryptoPals.base16to64(
"49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"
)
"746865206b696420646f6e277420706c6179" =
CryptoPals.fixed_xor(
"1c0111001f010100061a024b53535009181c",
"686974207468652062756c6c277320657965"
)
ct = "1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736"
CryptoPals.decrypt_xor(ct)
{txt, 0} = System.cmd("curl", ["https://cryptopals.com/static/challenge-data/4.txt"])
txt
|> String.split("\n")
|> Enum.map(fn ct -> CryptoPals.decrypt_xor(ct) end)
|> Enum.max()
txt = "Burning 'em, if you ain't quick and nimble
I go crazy when I hear a cymbal"
"0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f" =
CryptoPals.repeating_xor(txt, "ICE")
37 = CryptoPals.edit_distance("this is a test", "wokka wokka!!!")
url = "https://cryptopals.com/static/challenge-data/6.txt"
ct6 = CryptoPals.fetch_data(url)
CryptoPals.decrypt_repeating_xor(ct6)
url = "https://cryptopals.com/static/challenge-data/7.txt"
ct7 = CryptoPals.fetch_data(url)
key = "YELLOW SUBMARINE"
:crypto.crypto_one_time(:aes_128_ecb, key, ct7, [{:encrypt, false}])
|> IO.puts()
url = "https://cryptopals.com/static/challenge-data/8.txt"
{ct8, 0} = System.cmd("curl", [url])
list = ct8 |> String.split("\n")
list
|> Enum.map(fn x ->
{Base.decode16!(x, case: :lower) |> CryptoPals.ecb_score(), x}
end)
|> Enum.filter(fn {s, _} -> s > 0 end)