Skip to content

Instantly share code, notes, and snippets.

@Ljzn
Created February 25, 2023 02:53
Show Gist options
  • Save Ljzn/b1bfb4063fb616660351b942b98b9edf to your computer and use it in GitHub Desktop.
Save Ljzn/b1bfb4063fb616660351b942b98b9edf to your computer and use it in GitHub Desktop.

cryptopals.com

Untitled

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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment