-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b4580c7
Showing
3 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: <null key> | ||
Output: 4 49 10 53 24 8 51 44 6 4 33 20 39 19 34 42 | ||
Ciphertext: EXKYI ZSGEH UNTIQ | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'f' | ||
Output: 49 24 8 46 16 1 12 33 10 10 9 27 4 32 24 | ||
Ciphertext: XYIUQ BMHKK JBEGY | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'fo' | ||
Output: 19 46 9 24 12 1 4 43 11 32 23 39 29 34 22 | ||
Ciphertext: TUJYM BERLG XNDIW | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'foo' | ||
Output: 8 19 7 25 20 53 9 8 22 32 43 5 26 17 53 38 48 | ||
Ciphertext: ITHZU JIWGR FARMW | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'a' | ||
Output: 49 14 3 26 11 32 18 2 46 37 34 42 13 18 28 | ||
Ciphertext: XODAL GSCUL IQNSC | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'aa' | ||
Output: 14 7 32 22 38 23 23 2 26 8 12 2 34 16 15 | ||
Ciphertext: OHGWM XXCAI MCIQP | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'aaa' | ||
Output: 3 28 18 42 24 33 1 16 51 53 39 6 29 43 46 45 | ||
Ciphertext: DCSQY HBQZN GDRUT | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'b' | ||
Output: 49 16 4 30 12 40 8 19 37 25 47 29 18 16 18 | ||
Ciphertext: XQEEM OITLZ VDSQS | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'bc' | ||
Output: 16 13 32 17 10 42 34 7 2 37 6 48 44 28 53 4 | ||
Ciphertext: QNGRK QIHCL GWSCE | ||
|
||
Plaintext: AAAAAAAAAAAAAAA | ||
Key: 'bcd' | ||
Output: 5 38 20 27 50 1 38 26 49 33 39 42 49 2 35 | ||
Ciphertext: FMUBY BMAXH NQXCJ | ||
|
||
Plaintext: AAAAAAAAAAAAAAAAAAAAAAAAA | ||
Key: 'cryptonomicon' | ||
Ciphertext: SUGSR SXSWQ RMXOH IPBFP XARYQ | ||
|
||
Plaintext: SOLITAIRE | ||
Key: 'cryptonomicon' | ||
Ciphertext: KIRAK SFJAN | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
module Solitaire | ||
|
||
UNKEYED_DECK = (1..54).to_a | ||
|
||
module Useful | ||
def ascii_to_a1 codepoint | ||
codepoint - ?A.ord + 1 | ||
end | ||
|
||
def a1_to_ascii a1 | ||
a1 -= 26 if a1 > 26 | ||
a1 += 26 if a1 < 1 | ||
a1 + ?A.ord - 1 | ||
end | ||
|
||
def group_in_fives text | ||
count = 0 | ||
text.each_char.inject('') do |out, char| | ||
out << char | ||
count += 1 | ||
(out << ' '; count = 0) if count == 5 | ||
out | ||
end.strip | ||
end | ||
end | ||
|
||
class KeyStream | ||
include Useful | ||
attr_reader :deck | ||
|
||
def initialize(deck) | ||
@deck = deck.dup | ||
end | ||
|
||
def next_key | ||
joker_A_down_1 | ||
joker_B_down_2 | ||
triple_cut | ||
count_cut_bottom | ||
o = output_letter | ||
o == 54 ? 53 : o | ||
end | ||
|
||
def next_letter | ||
n = next_key | ||
n = next_key while n == 53 | ||
n % 26 | ||
end | ||
|
||
def joker_A_down_1 | ||
move_down 53, 1 | ||
end | ||
|
||
def joker_B_down_2 | ||
move_down 54, 2 | ||
end | ||
|
||
def move_down(card, dist) | ||
pos = @deck.index(card) | ||
@deck.delete_at(pos) | ||
pos -= @deck.length if (pos + dist) > @deck.length | ||
@deck.insert(pos + dist, card) | ||
end | ||
|
||
def triple_cut | ||
a, b = @deck.index(53), @deck.index(54) | ||
a, b = b, a if b < a | ||
@deck = @deck[(b+1)..-1] + @deck[a..b] + @deck[0...a] | ||
end | ||
|
||
def count_cut_bottom | ||
count_cut @deck.last | ||
end | ||
|
||
def count_cut count | ||
count = 53 if count == 54 | ||
@deck = @deck[count..-2] + @deck[0, count] + [@deck.last] | ||
end | ||
|
||
def output_letter | ||
val = @deck[0] | ||
val = val == 54 ? 53 : val | ||
@deck[val] | ||
end | ||
|
||
def key_deck char | ||
joker_A_down_1 | ||
joker_B_down_2 | ||
triple_cut | ||
count_cut_bottom | ||
count_cut ascii_to_a1(char) | ||
end | ||
end | ||
|
||
class Encrypter | ||
include Useful | ||
|
||
def initialize keystream | ||
@keystream = keystream | ||
end | ||
|
||
def encrypt plaintext | ||
plaintext.gsub! /[^A-Za-z]/, '' | ||
plaintext.upcase! | ||
plaintext << 'X' * (5 - plaintext.length % 5) if plaintext.length % 5 != 0 | ||
|
||
ciphertext = plaintext.codepoints.inject('') do |encrypted, char| | ||
char = ascii_to_a1(char) | ||
char += @keystream.next_letter | ||
encrypted << a1_to_ascii(char).chr | ||
end | ||
|
||
group_in_fives(ciphertext) | ||
end | ||
end | ||
|
||
class Decrypter | ||
include Useful | ||
|
||
def initialize keystream | ||
@keystream = keystream | ||
end | ||
|
||
def decrypt ciphertext | ||
ciphertext.codepoints.inject('') do |plain, cp| | ||
next(plain << ' ') if cp == ' '.ord | ||
cp = ascii_to_a1(cp) | ||
cp -= @keystream.next_letter | ||
plain << a1_to_ascii(cp) | ||
end | ||
end | ||
end | ||
|
||
def self.encrypt message, keystream | ||
Encrypter.new(KeyStream.new(keystream)).encrypt(message) | ||
end | ||
|
||
def self.decrypt ciphertext, keystream | ||
Decrypter.new(KeyStream.new(keystream)).decrypt(ciphertext) | ||
end | ||
|
||
def self.key_deck passphrase | ||
passphrase.upcase! | ||
k = KeyStream.new UNKEYED_DECK | ||
passphrase.codepoints do |cp| | ||
k.key_deck cp | ||
end | ||
k.deck | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
require 'test/unit' | ||
require './solitaire' | ||
|
||
JOKER_A = 53 | ||
JOKER_B = 54 | ||
|
||
UNKEYED_DECK = (1..54).to_a | ||
UNKEYED_RESULTS = [4, 49, 10, JOKER_A, 24, 8, 51, 44, 6, 4, 33] | ||
UNKEYED_LETTERS = [4, 23, 10, 24, 8, 25, 18, 6, 4, 7] | ||
|
||
class SolitaireTest < Test::Unit::TestCase | ||
|
||
def test_keystream_create | ||
assert Solitaire::KeyStream.new(UNKEYED_DECK).is_a? Solitaire::KeyStream | ||
end | ||
|
||
def test_keystream_joker_A_down_1 | ||
s = Solitaire::KeyStream.new [1,2,JOKER_A,3,4] | ||
s.joker_A_down_1 | ||
assert_equal [1,2,3,JOKER_A,4], s.deck | ||
|
||
s = Solitaire::KeyStream.new [1,2,3,4,JOKER_A] | ||
s.joker_A_down_1 | ||
assert_equal [1, JOKER_A, 2, 3, 4], s.deck | ||
end | ||
|
||
def test_keystream_joker_B_down_2 | ||
s = Solitaire::KeyStream.new [1, 2, JOKER_B, 3, 4] | ||
s.joker_B_down_2 | ||
assert_equal [1, 2, 3, 4, JOKER_B], s.deck | ||
|
||
s = Solitaire::KeyStream.new [1,2,3,JOKER_B,4] | ||
s.joker_B_down_2 | ||
assert_equal [1, JOKER_B, 2, 3, 4], s.deck | ||
end | ||
|
||
def test_keystream_triple_cut | ||
s = Solitaire::KeyStream.new [1, 2, JOKER_A, 3, 4, JOKER_B, 5, 6] | ||
s.triple_cut | ||
assert_equal [5, 6, JOKER_A, 3, 4, JOKER_B, 1, 2], s.deck | ||
|
||
s = Solitaire::KeyStream.new [1, JOKER_B, 2, 3, 4, JOKER_A, 5, 6] | ||
s.triple_cut | ||
assert_equal [5, 6, JOKER_B, 2, 3, 4, JOKER_A, 1], s.deck | ||
|
||
s = Solitaire::KeyStream.new [JOKER_B, 1, 2, 3, 4, 5, 6, JOKER_A] | ||
s.triple_cut | ||
assert_equal [JOKER_B, 1, 2, 3, 4, 5, 6, JOKER_A], s.deck | ||
end | ||
|
||
def test_keystream_count_cut_bottom | ||
s = Solitaire::KeyStream.new [1, 2, JOKER_A, 3, 4, JOKER_B, 5, 6] | ||
s.count_cut_bottom | ||
assert_equal [5, 1, 2, JOKER_A, 3, 4, JOKER_B, 6], s.deck | ||
end | ||
|
||
def test_keystream_output_letter | ||
s = Solitaire::KeyStream.new [3, 1, 2, 4, 5, 6] | ||
assert_equal 4, s.output_letter | ||
end | ||
|
||
def test_keystream_next_key | ||
s = Solitaire::KeyStream.new UNKEYED_DECK | ||
n = s.next_key | ||
assert_equal 4, n | ||
end | ||
|
||
def test_keystream_unkeyed_sequence | ||
s = Solitaire::KeyStream.new UNKEYED_DECK | ||
UNKEYED_RESULTS.each do |result| | ||
assert_equal result, s.next_key#, s.deck.to_s | ||
end | ||
end | ||
|
||
def test_keystream_unkeyed_sequence_letters | ||
s = Solitaire::KeyStream.new UNKEYED_DECK | ||
UNKEYED_LETTERS.each do |result| | ||
assert_equal result, s.next_letter#, s.deck.to_s | ||
end | ||
end | ||
|
||
def test_encrypter_create | ||
e = Solitaire::Encrypter.new(Solitaire::KeyStream.new(UNKEYED_DECK)) | ||
assert e.is_a? Solitaire::Encrypter | ||
end | ||
|
||
def test_encrypter_unkeyed | ||
e = Solitaire::Encrypter.new(Solitaire::KeyStream.new(UNKEYED_DECK)) | ||
assert_equal "GLNCQ MJAFF FVOMB JIYCB", e.encrypt("CODEI NRUBY LIVEL ONGER") | ||
end | ||
|
||
def test_encrypter_unkeyed | ||
d = Solitaire::Decrypter.new(Solitaire::KeyStream.new(UNKEYED_DECK)) | ||
assert_equal "CODEI NRUBY LIVEL ONGER", d.decrypt("GLNCQ MJAFF FVOMB JIYCB") | ||
end | ||
|
||
def test_module_encrypt_unkeyed | ||
assert_equal "GLNCQ MJAFF FVOMB JIYCB", Solitaire.encrypt("Code in Ruby, live longer", UNKEYED_DECK) | ||
end | ||
|
||
def test_module_decrypt_unkeyed | ||
assert_equal "CODEI NRUBY LIVEL ONGER", Solitaire.decrypt("GLNCQ MJAFF FVOMB JIYCB", UNKEYED_DECK) | ||
end | ||
|
||
def test_examples | ||
assert_equal "YOURC IPHER ISWOR KINGX", Solitaire.decrypt("CLEPK HHNIY CFPWH FDFEH", UNKEYED_DECK) | ||
assert_equal "WELCO METOR UBYQU IZXXX", Solitaire.decrypt("ABVAW LWZSY OORYK DUPVH", UNKEYED_DECK) | ||
end | ||
|
||
def self.setup_test hash | ||
hash['Key'] = hash['Key'].gsub /'/, '' | ||
deck = hash['Key'] == '<null key>' ? UNKEYED_DECK : Solitaire.key_deck(hash['Key']) | ||
|
||
define_method "test_#{hash.object_id}" do | ||
assert_equal hash['Ciphertext'], Solitaire.encrypt(hash['Plaintext'], deck) | ||
end | ||
end | ||
|
||
File.foreach('sol-test.txt').inject({}) do |hash, line| | ||
next(hash) if line.strip == '' | ||
k, v = line.split(':').map(&:strip) | ||
hash[k] = v | ||
|
||
if k == 'Ciphertext' | ||
setup_test hash | ||
next {} | ||
end | ||
hash | ||
end | ||
|
||
end |