Skip to content

Instantly share code, notes, and snippets.

@rob-brown
Last active June 7, 2021 14:48
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rob-brown/cdea88cbbdd6749a25db0cca0605498e to your computer and use it in GitHub Desktop.
Save rob-brown/cdea88cbbdd6749a25db0cca0605498e to your computer and use it in GitHub Desktop.
Encode secret messages into text using zero-width spaces
defmodule SecretMessage do
@zero "\u200C"
@one "\u200D"
def encode(text, secret) when byte_size(text) > byte_size(secret) do
secret
|> obfuscate
|> Stream.concat(Stream.repeatedly fn -> "" end)
|> Stream.zip(String.codepoints text)
|> Enum.map(fn {x, y} -> [y, x] end)
|> IO.iodata_to_binary
end
def encode(_text, _secret) do
raise "Text must be longer than secret"
end
def decode(text) do
text
|> String.codepoints
|> Stream.filter(& &1 in [@zero, @one])
|> Stream.map(fn @zero -> <<0::1>>; @one -> <<1::1>> end)
|> Stream.chunk_every(8)
|> Stream.map(&deobfuscate/1)
|> Enum.filter(& bit_size(&1) == 8)
|> IO.iodata_to_binary
end
defp obfuscate(secret) do
for << b::1 <- secret >> do
case b do
0 -> @zero
1 -> @one
end
end
|> Enum.chunk_every(8)
end
defp deobfuscate(bits) do
bits |> Enum.reduce(<<>>, fn x, acc -> <<acc::bitstring, x::bitstring>> end)
end
end
text = "This is a test, it is only a test"
secret = "Secret secret"
IO.puts "Text:"
IO.puts text
IO.puts "Secret:"
IO.puts secret
IO.puts "Encoded:"
SecretMessage.encode(text, secret) |> IO.puts
IO.puts "Decoded:"
SecretMessage.encode(text, secret) |> SecretMessage.decode |> IO.puts
IO.puts "String length:"
SecretMessage.encode(text, secret) |> String.length |> IO.puts
IO.puts "Byte size:"
SecretMessage.encode(text, secret) |> byte_size |> IO.puts
IO.puts "Codepoints:"
SecretMessage.encode(text, secret) |> String.codepoints |> IO.inspect(limit: 500)
# Run with `elixir SecretMessage.exs`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment