MCCALLJT.IO

Jack McCallum's Personal Site

One Time Pad in Elixir

Feb 12 2018

Recently I have been very interested in learning about cryptography. I just finished Crypto by Steven Levy which gives a great background to the crypto field and the advances that enabled the current 'Crypto Craze'. I am also working my way through the Stanford Cryptography Course on Coursera which dives deep on some implementations. Given all that, I thought it would be fun to put some of the ideas and learnings into code, and since I am still working on my Elixir skills, lets do it in that. Elixir is actually a nice language for crypto for two reasons:

1) You can use the underlying Erlang crypto library which has many powerful functions.

2) Crypto lends itself very well to functional programming as you are generally passing messages through a number of transformations.

Some of the functions are pretty elegant when seen in Elixir code.

One Time Pad

In this post we will be implementing a basic one time pad. A one time pad is a fundamental concept in cryptography, as it is a cipher that cannot be cracked. A cipher is a means of obscuring a message so that it cannot be read by someone who intercepts it. Many types of ciphers have been used throughout history, and most have been cracked. The one time pad is unique in that it is not just hard to crack, it is mathematically impossible to crack. Unfortunately the one time pad is not all that practical as the key must be the same length or longer as the message you are wishing to encrypt, and can only ever been used once. In practice this means that if you are able to get someone the key in a secure way, you might as well just give them the message that way too.

Nevertheless, lets implement it.

defmodule OneTimePad do
  def encode(message), do: encode(message, message |> String.length |> generate_key)
  def encode(message, key) when byte_size(message) * 2 == byte_size(key) do
    cypher = message
             |> Base.encode16
             |> :crypto.exor(key)
             |> Base.encode16

    IO.puts "Key: #{key}"
    IO.puts "Cypher: #{cypher}"

    cypher
  end
  def encode(_, _), do: IO.puts "Incorrect Byte Sizes"

  def decode(cypher, key) when byte_size(key) * 2 == byte_size(cypher) do
    raw = cypher
          |> Base.decode16!
          |> :crypto.exor(key)
          |> Base.decode16!

    IO.puts "Cypher: #{cypher}"
    IO.puts "Decoded Message: #{raw}"

    raw
  end
  def decode(_, _), do: IO.puts "Incorrect Byte Sizes"

  defp generate_key(length) do
    :crypto.strong_rand_bytes(length)
    |> Base.encode16
  end
end

Running it:

Interactive Elixir (1.6.0) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> cypher = OneTimePad.encode("The secret lies with Charlotte")
Key: A52D26CAC642EB7F6A603CE37EE4F15FD3EAA1D08FEB6317BADA7FA6D9E2
Cypher: 7401047C0403717174050207737100740074010401737370017C73017102077673047378760572080A767171000B07067573720201007602730D7307
"7401047C0403717174050207737100740074010401737370017C73017102077673047378760572080A767171000B07067573720201007602730D7307"

iex(2)> key = "A52D26CAC642EB7F6A603CE37EE4F15FD3EAA1D08FEB6317BADA7FA6D9E2"
"A52D26CAC642EB7F6A603CE37EE4F15FD3EAA1D08FEB6317BADA7FA6D9E2"

iex(3)> OneTimePad.decode(cypher, key)
Cypher: 7401047C0403717174050207737100740074010401737370017C73017102077673047378760572080A767171000B07067573720201007602730D7307
Decoded Message: The secret lies with Charlotte
"The secret lies with Charlotte"

Whats with all the Base.(de|en)code16! calls? Well that is to make the binaries prinatable in iex. I am curious if anyone has a better way to do it in Elixir - please let me know in the comments.

Fantasy Football GroupMe Stats

Jan 29 2017

Two years of Fantasy Football conversation in a GroupMe chat visualized using my GroupMe Stats script and Circos. GroupMe is a messaging application where users can 'Like' other messages. My friends use this to like funny, clever, relevant, etc posts. This has lead to some interesting data over the years.


Posts Liked By Member

Chord chart

This chord chart shows 'likes' given and received by each member of our Fantasy Football GroupMe.


Data for chart above (plug it in here http://mkweb.bcgsc.ca/tableviewer/)

labels Roland Christian Matt Dan Kyle Mike Chuck Kevin Jack Luke Derek Gavin 
Roland 0 3 11 56 19 18 9 38 15 12 3 31 
Christian 0 0 3 2 6 4 3 4 1 3 0 11 
Matt 8 9 0 40 53 25 32 60 37 30 12 71 
Dan 18 5 63 0 65 29 35 112 39 39 16 101 
Kyle 9 7 39 32 0 21 22 60 29 25 4 56 
Mike 7 8 26 44 33 0 5 34 7 27 4 27 
Chuck 0 3 9 13 4 11 0 8 4 5 0 13 
Kevin 6 2 28 37 25 19 16 0 20 27 7 55 
Jack 8 6 22 28 23 5 9 33 0 14 3 45 
Luke 11 11 32 42 34 27 19 58 21 0 8 42 
Derek 2 0 6 2 3 0 1 3 2 4 0 3 
Gavin 13 6 41 68 38 24 18 53 29 23 9 0 

Additional Stats

Post to Like Ratios
Roland: 72 messages and 82 likes for a ratio of 1.14 likes per message
Christian: 31 messages and 60 likes for a ratio of 1.94 likes per message
Matt: 223 messages and 281 likes for a ratio of 1.26 likes per message
Dan: 521 messages and 366 likes for a ratio of 0.7 likes per message
Kyle: 169 messages and 303 likes for a ratio of 1.79 likes per message
Mike: 45 messages and 183 likes for a ratio of 4.07 likes per message
Chuck: 107 messages and 170 likes for a ratio of 1.59 likes per message
Kevin: 263 messages and 465 likes for a ratio of 1.77 likes per message
Jack: 90 messages and 208 likes for a ratio of 2.31 likes per message
Luke: 114 messages and 209 likes for a ratio of 1.83 likes per message
Derek: 68 messages and 66 likes for a ratio of 0.97 likes per message
Gavin: 253 messages and 456 likes for a ratio of 1.8 likes per message
Total Likes Given
Roland: 217
Christian: 39
Matt: 382
Dan: 527
Kyle: 306
Mike: 224
Chuck: 71
Kevin: 246
Jack: 201
Luke: 307
Derek: 26
Gavin: 324
Self Likes Given
Matt: 1
Dan: 2
Chuck: 1
Kevin: 2
Gavin: 1
Average Likes Per Member Per Person - Who are you the toughest critic of?

(Number of times you like it vs number of posts)

Roland: 
TOTAL AVERAGE: 11.46% of posts

-- Mike - Liked 40.0%
-- Gavin - Liked 12.25%
-- Dan - Liked 10.75%
-- Kyle - Liked 11.24%
-- Kevin - Liked 14.45%
-- Christian - Liked 9.68%
-- Luke - Liked 10.53%
-- Chuck - Liked 8.41%
-- Jack - Liked 16.67%
-- Matt - Liked 4.93%
-- Derek - Liked 4.41%

Christian: 
TOTAL AVERAGE: 2.02% of posts

-- Dan - Liked 0.38%
-- Gavin - Liked 4.35%
-- Kevin - Liked 1.52%
-- Mike - Liked 8.89%
-- Kyle - Liked 3.55%
-- Luke - Liked 2.63%
-- Jack - Liked 1.11%
-- Chuck - Liked 2.8%
-- Matt - Liked 1.35%

Matt: 
TOTAL AVERAGE: 21.92% of posts

-- Dan - Liked 7.68%
-- Kyle - Liked 31.36%
-- Jack - Liked 41.11%
-- Luke - Liked 26.32%
-- Mike - Liked 55.56%
-- Gavin - Liked 28.06%
-- Christian - Liked 29.03%
-- Kevin - Liked 22.81%
-- Chuck - Liked 29.91%
-- Roland - Liked 11.11%
-- Derek - Liked 17.65%
-- Matt - Liked 0.45%

Dan: 
TOTAL AVERAGE: 36.47% of posts

-- Roland - Liked 25.0%
-- Kyle - Liked 38.46%
-- Gavin - Liked 39.92%
-- Luke - Liked 34.21%
-- Jack - Liked 43.33%
-- Mike - Liked 64.44%
-- Christian - Liked 16.13%
-- Matt - Liked 28.25%
-- Kevin - Liked 42.59%
-- Chuck - Liked 32.71%
-- Derek - Liked 23.53%
-- Dan - Liked 0.38%

Kyle: 
TOTAL AVERAGE: 17.03% of posts

-- Roland - Liked 12.5%
-- Jack - Liked 32.22%
-- Mike - Liked 46.67%
-- Luke - Liked 21.93%
-- Gavin - Liked 22.13%
-- Kevin - Liked 22.81%
-- Matt - Liked 17.49%
-- Christian - Liked 22.58%
-- Chuck - Liked 20.56%
-- Dan - Liked 6.14%
-- Derek - Liked 5.88%

Mike: 
TOTAL AVERAGE: 11.66% of posts

-- Roland - Liked 9.72%
-- Kyle - Liked 19.53%
-- Dan - Liked 8.45%
-- Jack - Liked 7.78%
-- Gavin - Liked 10.67%
-- Luke - Liked 23.68%
-- Christian - Liked 25.81%
-- Matt - Liked 11.66%
-- Kevin - Liked 12.93%
-- Chuck - Liked 4.67%
-- Derek - Liked 5.88%

Chuck: 
TOTAL AVERAGE: 3.82% of posts

-- Luke - Liked 4.39%
-- Christian - Liked 9.68%
-- Mike - Liked 24.44%
-- Dan - Liked 2.5%
-- Gavin - Liked 5.14%
-- Kevin - Liked 3.04%
-- Jack - Liked 4.44%
-- Kyle - Liked 2.37%
-- Matt - Liked 4.04%
-- Chuck - Liked 0.93%

Kevin: 
TOTAL AVERAGE: 14.45% of posts

-- Roland - Liked 8.33%
-- Kyle - Liked 14.79%
-- Dan - Liked 7.1%
-- Gavin - Liked 21.74%
-- Mike - Liked 42.22%
-- Luke - Liked 23.68%
-- Matt - Liked 12.56%
-- Christian - Liked 6.45%
-- Jack - Liked 22.22%
-- Chuck - Liked 14.95%
-- Kevin - Liked 0.76%
-- Derek - Liked 10.29%

Jack: 
TOTAL AVERAGE: 10.71% of posts

-- Roland - Liked 11.11%
-- Mike - Liked 11.11%
-- Christian - Liked 19.35%
-- Gavin - Liked 17.79%
-- Luke - Liked 12.28%
-- Kyle - Liked 13.61%
-- Dan - Liked 5.37%
-- Matt - Liked 9.87%
-- Kevin - Liked 12.55%
-- Jack - Liked 4.44%
-- Chuck - Liked 8.41%
-- Derek - Liked 4.41%

Luke: 
TOTAL AVERAGE: 16.58% of posts

-- Roland - Liked 15.28%
-- Kyle - Liked 20.12%
-- Gavin - Liked 16.6%
-- Dan - Liked 8.06%
-- Mike - Liked 60.0%
-- Christian - Liked 35.48%
-- Kevin - Liked 22.05%
-- Matt - Liked 14.35%
-- Jack - Liked 23.33%
-- Chuck - Liked 17.76%
-- Derek - Liked 11.76%

Derek: 
TOTAL AVERAGE: 1.37% of posts

-- Kevin - Liked 1.14%
-- Dan - Liked 0.38%
-- Roland - Liked 2.78%
-- Matt - Liked 2.69%
-- Kyle - Liked 1.78%
-- Gavin - Liked 1.19%
-- Luke - Liked 3.51%
-- Jack - Liked 2.22%
-- Chuck - Liked 0.93%

Gavin: 
TOTAL AVERAGE: 18.91% of posts

-- Roland - Liked 18.06%
-- Kyle - Liked 22.49%
-- Dan - Liked 13.05%
-- Jack - Liked 32.22%
-- Mike - Liked 53.33%
-- Christian - Liked 19.35%
-- Matt - Liked 18.39%
-- Kevin - Liked 20.15%
-- Luke - Liked 20.18%
-- Chuck - Liked 16.82%
-- Derek - Liked 13.24%
-- Gavin - Liked 0.4%

More details on the script can be found in my prior blog on it. Credit to the fantastic Circos for the Chord Chart.

-JM

Rubocop Pre Commit Hook With Pipes

Jan 27 2017

We use RuboCop to check our code for style on each push to Github. I was tired of having builds fail because I forgot to run rubocop so I want a little git pre-commit hook to prevent that from happening.

#!/bin/sh
# This would go in your .git/hooks/pre-commit file

if command -v rubocop > /dev/null; then
  rubocop
fi

That takes too long. Lets use pipes to only run RuboCop on files that have actually changed so that it is fast.

We get a list of changed files

mccalljt master % git status --porcelain
M  config.rb
D  deleted_file.rb
A  new_file.rb
M  source/index.html.haml

We only want Added and Modified, not Deleted

mccalljt master % git status --porcelain | grep -E '^A|^M'
M  config.rb
A  new_file.rb
M  source/index.html.haml

Rubocop only works on ruby files so we can ignore the others

mccalljt master % git status --porcelain | grep -E '^A|^M' | grep '.rb'
M  config.rb
A  new_file.rb

We just want the file names

mccalljt master % git status --porcelain | grep -E '^A|^M' | grep '.rb' | awk '{print $2}'
config.rb
new_file.rb

Perfect - lets pass them to rubocop

mccalljt master % git status --porcelain | grep -E '^A|^M' | grep '.rb' | awk '{print $2}' | xargs rubocop
Inspecting 2 files
..

2 files inspected, no offenses detected

Much better. Lets clean it up and put it back into the pre-commit hook.

#!/bin/sh

if command -v rubocop > /dev/null; then
  git status --porcelain \
  | grep -E '^A|^M' \
  | grep '.rb' \
  | awk '{print $2}' \
  | xargs rubocop
fi

Always fun to use the Unix Chainsaw

-JM