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