% 2007 Stephen Marsh "freeyourmind" ++ [\$@|"gmail.com"]. % This is an implementation of the solitaire encryption algorithm from % http://www.schneier.com/solitaire.html % developed from notes at % http://www.rubyquiz.com/quiz1.html % It uses a deck of 54 cards as its key, making it effectively % a 237 bit symmetric cipher: % lists:foldl(fun (A, B) -> A + B end, 0, % lists:map(fun math:log/1, lists:seq(1, 54)) % ) / math:log(2). -module(solitaire). -export([default_key/0, encode/2, decode/2, encode_with_passphrase/2, decode_with_passphrase/2]). encode_with_passphrase(Message, Phrase) -> encode(Message, key_for_passphrase(Phrase)). decode_with_passphrase(Message, Phrase) -> decode(Message, key_for_passphrase(Phrase)). encode(Message, Key) -> CleanMessage = lists:foldl(fun clean_char/2, [], Message), Xs = (5 - length(CleanMessage) rem 5) rem 5, CleanMessage2 = lists:reverse(CleanMessage, lists:duplicate(Xs, \$X)), {Acc, _} = lists:foldl(fun encode_help/2, {[], Key}, CleanMessage2), space(lists:reverse(Acc)). encode_help(X, {Acc, Key}) -> {KSLet, NK} = advance_key(Key), {[encode_char(X, KSLet)|Acc], NK}. clean_char(X, Acc) when X >= \$A, X =< \$Z -> [X|Acc]; clean_char(X, Acc) when X >= \$a, X =< \$z -> [X - \$a + \$A|Acc]; clean_char(_, Acc) -> Acc. encode_char(Let, KSLet) -> case Let + KSLet of V when V > \$Z -> V - 26; V -> V end. decode_char(Let, KSLet) -> case Let - KSLet of V when V < \$A -> V + 26; V -> V end. decode(Message, Key) -> CleanMessage = lists:reverse(lists:foldl(fun clean_char/2, [], Message)), {Acc, _} = lists:foldl(fun decode_help/2, {[], Key}, CleanMessage), space(lists:reverse(Acc)). decode_help(X, {Acc, Key}) -> {KSLet, NK} = advance_key(Key), {[decode_char(X, KSLet)|Acc], NK}. % breaks a message into groups of five characters separated by spaces space(Message) -> space([], Message). space(Acc, [A,B,C,D,E|T]) -> space ([E,D,C,B,A, \$ |Acc], T); space([], []) -> []; space(Acc, []) -> [_UselessSpace|T] = lists:reverse(Acc), T; space(Acc, L) -> space (lists:reverse([\$ |L], Acc), []). advance_key(Key) -> Key2 = move_jokerA(Key), Key3 = move_jokerB(Key2), Key4 = triple_cut(Key3), Key5 = count_cut(Key4), [H|T] = Key5, if is_atom(H) -> Count = 53; true -> Count = H end, C = lists:nth(Count, T), if is_atom(C) -> advance_key(Key5); C > 26 -> {C - 26, Key5}; true -> {C, Key5} end. move_jokerA(Key) -> move_jokerA([], Key). move_jokerA(Above, [jokerA, A|T]) -> lists:reverse(Above, [A, jokerA] ++ T); move_jokerA(Above, [jokerA]) -> [Top|Above2] = lists:reverse(Above), [Top, jokerA|Above2]; move_jokerA(Above, [H|T]) -> move_jokerA([H|Above], T). move_jokerB(Key) -> move_jokerB([], Key). move_jokerB(Above, [jokerB, A, B|T]) -> lists:reverse(Above, [A, B, jokerB] ++ T); move_jokerB(Above, [jokerB, A]) -> [Top|Above2] = lists:reverse(Above, [A]), [Top, jokerB|Above2]; move_jokerB(Above, [jokerB]) -> [H, H2|AboveT] = lists:reverse(Above), [H, H2, jokerB|AboveT]; move_jokerB(Above, [H|T]) -> move_jokerB([H|Above], T). triple_cut(Key) -> triple_cut([], Key). triple_cut(Above, [H|T]) when is_atom(H) -> % is_joker triple_cut([H|Above], [], T); triple_cut(Above, [H|T]) -> triple_cut([H|Above], T). triple_cut(Above, Middle, [H|T]) when is_atom(H) -> [H2|Above2] = Above, T ++ [H2] ++ lists:reverse(Middle, [H] ++ lists:reverse(Above2)); triple_cut(Above, Middle, [H|T]) -> triple_cut(Above, [H|Middle], T). count_cut(Key) -> case lists:last(Key) of V when is_atom(V) -> count_cut(53, Key); V -> count_cut(V, Key) end. count_cut(N, Key) -> {List1, List2} = lists:split(N, Key), {List3, List4} = lists:split(53 - N, List2), List3 ++ List1 ++ List4. default_key() -> lists:seq(1, 52) ++ [jokerA, jokerB]. key_for_passphrase(Phrase) -> key_for_passphrase(default_key(), Phrase). key_for_passphrase(Key, [H|Phrase]) -> Key2 = move_jokerA(Key), Key3 = move_jokerB(Key2), Key4 = triple_cut(Key3), Key5 = count_cut(Key4), Key6 = count_cut(clean_passphrase_char(H), Key5), key_for_passphrase(Key6, Phrase); key_for_passphrase(Key, []) -> Key. clean_passphrase_char(X) when X >= \$A, X =< \$Z -> X - \$A + 1; clean_passphrase_char(X) when X >= \$a, X =< \$z -> X - \$a + 1.