mathie-solitaire_cipher 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006-2008 Rubaidh Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,186 @@
1
+ = Solitaire Cipher
2
+
3
+ This is an implementation of Bruce Schneier's Solitaire, as designed for the
4
+ Neal Stephenson book, Cryptonomicon. It's Ruby Quiz number one, and a chance
5
+ for me to try out Cucumber (and RSpec) in anger. The rest of the text here is
6
+ reproduced from the original Ruby Quiz.
7
+
8
+ Cryptologist Bruce Schneier designed the hand cipher "Solitaire" for Neal
9
+ Stephenson's book "Cryptonomicon". Created to be the first truly secure hand
10
+ cipher, Solitaire requires only a deck of cards for the encryption and
11
+ decryption of messages.
12
+
13
+ While it's true that Solitaire is easily completed by hand given ample time,
14
+ using a computer is much quicker and easier. Because of that, Solitaire
15
+ conversion routines are available in many languages, though I've not yet run
16
+ across one in Ruby.
17
+
18
+ This week's quiz is to write a Ruby script that does the encryption and
19
+ decryption of messages using the Solitaire cipher.
20
+
21
+ Let's look at the steps of encrypting a message with Solitaire.
22
+
23
+ 1. Discard any non A to Z characters, and uppercase all remaining letters.
24
+ Split the message into five character groups, using Xs to pad the last group,
25
+ if needed. If we begin with the message "Code in Ruby, live longer!", for
26
+ example, we would now have:
27
+
28
+ CODEI NRUBY LIVEL ONGER
29
+
30
+ 2. Use Solitaire to generate a keystream letter for each letter in the
31
+ message. This step is detailed below, but for the sake of example let's just
32
+ say we get:
33
+
34
+ DWJXH YRFDG TMSHP UURXJ
35
+
36
+ 3. Convert the message from step 1 into numbers, A = 1, B = 2, etc:
37
+
38
+ 3 15 4 5 9 14 18 21 2 25 12 9 22 5 12 15 14 7 5 18
39
+
40
+ 4. Convert the keystream letters from step 2 using the same method:
41
+
42
+ 4 23 10 24 8 25 18 6 4 7 20 13 19 8 16 21 21 18 24 10
43
+
44
+ 5. Add the message numbers from step 3 to the keystream numbers from step 4
45
+ and subtract 26 from the result if it is greater than 26. For example, 6 + 10 = 16
46
+ as expected, but 26 + 1 = 1 (27 - 26):
47
+
48
+ 7 12 14 3 17 13 10 1 6 6 6 22 15 13 2 10 9 25 3 2
49
+
50
+ 6. Convert the numbers from step 5 back to letters:
51
+
52
+ GLNCQ MJAFF FVOMB JIYCB
53
+
54
+ That took a while to break down, but it's really a very simple process.
55
+ Decrypting with Solitaire is even easier, so let's look at those steps now.
56
+ We'll work backwards with our example now, decrypting "GLNCQ MJAFF FVOMB
57
+ JIYCB".
58
+
59
+ 1. Use Solitaire to generate a keystream letter for each letter in the message
60
+ to be decoded. Again, I detail this process below, but sender and receiver use
61
+ the same key and will get the same letters:
62
+
63
+ DWJXH YRFDG TMSHP UURXJ
64
+
65
+ 2. Convert the message to be decoded to numbers:
66
+
67
+ 7 12 14 3 17 13 10 1 6 6 6 22 15 13 2 10 9 25 3 2
68
+
69
+ 3. Convert the keystream letters from step 1 to numbers:
70
+
71
+ 4 23 10 24 8 25 18 6 4 7 20 13 19 8 16 21 21 18 24 10
72
+
73
+ 4. Subtract the keystream numbers from step 3 from the message numbers from
74
+ step 2. If the message number is less than or equal to the keystream number,
75
+ add 26 to the message number before subtracting. For example, 22 - 1 = 21 as
76
+ expected, but 1 - 22 = 5 (27 - 22):
77
+
78
+ 3 15 4 5 9 14 18 21 2 25 12 9 22 5 12 15 14 7 5 18
79
+
80
+ 5. Convert the numbers from step 4 back to letters:
81
+
82
+ CODEI NRUBY LIVEL ONGER
83
+
84
+ Transforming messages is that simple. Finally, let's look at the missing piece
85
+ of the puzzle, generating the keystream letters.
86
+
87
+ First, let's talk a little about the deck of cards. Solitaire needs a full
88
+ deck of 52 cards and the two jokers. The jokers need to be visually distinct
89
+ and I'll refer to them below as A and B. Some steps involve assigning a value
90
+ to the cards. In those cases, use the cards face value as a base, Ace = 1, 2 =
91
+ 2... 10 = 10, Jack = 11, Queen = 12, King = 13. Then modify the base by the
92
+ bridge ordering of suits, Clubs is simply the base value, Diamonds is base
93
+ value + 13, Hearts is base value + 26, and Spades is base value + 39. Either
94
+ joker values at 53. When the cards must represent a letter Clubs and Diamonds
95
+ values are taken to be the number of the letter (1 to 26), as are Hearts and
96
+ Spades after subtracting 26 from their value (27 to 52 drops to 1 to 26). Now
97
+ let's make sense of all that by putting it to use.
98
+
99
+ 1. Key the deck. This is the critical step in the actual operation of the
100
+ cipher and the heart of it's security. There are many methods to go about
101
+ this, such as shuffling a deck and then arranging the receiving deck in the
102
+ same order or tracking a bridge column in the paper and using that to order
103
+ the cards. Because we want to be able to test our answers though, we'll use an
104
+ unkeyed deck, cards in order of value. That is, from top to bottom, we'll
105
+ always start with the deck:
106
+
107
+ Ace of Clubs
108
+ ...to...
109
+ King of Clubs
110
+ Ace of Diamonds
111
+ ...to...
112
+ King of Diamonds
113
+ Ace of Hearts
114
+ ...to...
115
+ King of Hearts
116
+ Ace of Spades
117
+ ...to...
118
+ King of Spades
119
+ "A" Joker
120
+ "B" Joker
121
+
122
+ 2. Move the A joker down one card. If the joker is at the bottom of the deck,
123
+ move it to just below the first card. (Consider the deck to be circular.) The
124
+ first time we do this, the deck will go from:
125
+
126
+ 1 2 3 ... 52 A B
127
+
128
+ To:
129
+
130
+ 1 2 3 ... 52 B A
131
+
132
+ 3. Move the B joker down two cards. If the joker is the bottom card, move it
133
+ just below the second card. If the joker is the just above the bottom card,
134
+ move it below the top card. (Again, consider the deck to be circular.) This
135
+ changes our example deck to:
136
+
137
+ 1 B 2 3 4 ... 52 A
138
+
139
+ 4. Perform a triple cut around the two jokers. All cards above the top joker
140
+ move to below the bottom joker and vice versa. The jokers and the cards
141
+ between them do not move. This gives us:
142
+
143
+ B 2 3 4 ... 52 A 1
144
+
145
+ 5. Perform a count cut using the value of the bottom card. Cut the bottom
146
+ card's value in cards off the top of the deck and reinsert them just above the
147
+ bottom card. This changes our deck to:
148
+
149
+ 2 3 4 ... 52 A B 1 (the 1 tells us to move just the B)
150
+
151
+ 6. Find the output letter. Convert the top card to it's value and count down
152
+ that many cards from the top of the deck, with the top card itself being card
153
+ number one. Look at the card immediately after your count and convert it to a
154
+ letter. This is the next letter in the keystream. If the output card is a
155
+ joker, no letter is generated this sequence. This step does not alter the
156
+ deck. For our example, the output letter is:
157
+
158
+ D (the 2 tells us to count down to the 4, which is a D)
159
+
160
+ 7. Return to step 2, if more letters are needed.
161
+
162
+ For the sake of testing, the first ten output letters for an unkeyed deck are:
163
+
164
+ D (4) W (49) J (10) Skip Joker (53) X (24) H (8)
165
+ Y (51) R (44) F (6) D (4) G (33)
166
+
167
+ That's all there is to Solitaire, finally. It's really longer to explain than
168
+ it is to code up.
169
+
170
+ Solutions to this quiz should accept a message as a command line argument and
171
+ encrypt or decrypt is as needed. It should be easy to tell which is needed by
172
+ the pattern of the message, but you can use a switch if you prefer.
173
+
174
+ All the examples for this quiz assume an unkeyed deck, but your script can
175
+ provide a way to key the deck, if desired. (A real script would require this,
176
+ of course.)
177
+
178
+ Here's a couple of messages to test your work with. You'll know when you have
179
+ them right:
180
+
181
+ CLEPK HHNIY CFPWH FDFEH
182
+
183
+ ABVAW LWZSY OORYK DUPVH
184
+
185
+ The code in this gem is copyright (c) 2006 - 2009 Rubaidh Ltd and released
186
+ under the MIT license. Please see the file MIT-LICENSE for more details.
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'spec/rake/spectask'
5
+ require 'cucumber/rake/task'
6
+
7
+ task :default => [ :spec, :features ]
8
+
9
+ Rake::RDocTask.new do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = "Ruby Quiz #1: Solitaire Cipher"
12
+ rdoc.options << '--line-numbers' << '--inline-source'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ gem_spec = eval(File.read('solitaire_cipher.gemspec'))
18
+
19
+ Rake::GemPackageTask.new(gem_spec) do |p|
20
+ p.need_tar = false
21
+ p.need_zip = false
22
+ end
23
+
24
+ Spec::Rake::SpecTask.new
25
+ Cucumber::Rake::Task.new
@@ -0,0 +1,9 @@
1
+ Feature: Decrypting a string
2
+ As an agent at the headquarters of an intelligence operation
3
+ I want to decrypt a piece of text that has been sent by one of our agents
4
+ So that I can understand the threat to our national security
5
+
6
+ Scenario: decode a string with a cipher text
7
+ Given the key stream "DWJXH YRFDG TMSHP UURXJ"
8
+ When I decode the string "GLNCQ MJAFF FVOMB JIYCB"
9
+ Then it should produce the plain text "CODEI NRUBY LIVEL ONGER"
@@ -0,0 +1,9 @@
1
+ Feature: Encrypting a string
2
+ As a counter-intelligence spy
3
+ I want to encrypt a piece of text
4
+ So that I can securely send it to headquarters
5
+
6
+ Scenario: encode a string with a cipher text
7
+ Given the key stream "DWJXH YRFDG TMSHP UURXJ"
8
+ When I encode the string "Code in Ruby, live longer!"
9
+ Then it should produce the cipher text "GLNCQ MJAFF FVOMB JIYCB"
@@ -0,0 +1,19 @@
1
+ Given /^the key stream "(.*)"$/ do |key_stream|
2
+ @solitaire = SolitaireCipher.new(key_stream)
3
+ end
4
+
5
+ When /^I decode the string "(.*)"$/ do |encoded_string|
6
+ @decoded_string = @solitaire.decode(encoded_string)
7
+ end
8
+
9
+ Then /^it should produce the plain text "(.*)"$/ do |plain_text_string|
10
+ @decoded_string.should == plain_text_string
11
+ end
12
+
13
+ When /^I encode the string "(.*)"$/ do |plain_text_string|
14
+ @encoded_string = @solitaire.encode(plain_text_string)
15
+ end
16
+
17
+ Then /^it should produce the cipher text "(.*)"$/ do |encoded_string|
18
+ @encoded_string.should == encoded_string
19
+ end
@@ -0,0 +1,6 @@
1
+ $: << File.join(File.dirname(__FILE__), "..", "..", "lib")
2
+
3
+ require 'rubygems'
4
+ require 'spec/expectations'
5
+
6
+ require 'solitaire_cipher'
@@ -0,0 +1,12 @@
1
+ require 'active_support'
2
+
3
+ class SolitaireCipher
4
+ def initialize(key_stream)
5
+ raise ArgumentError.new("invalid argument 1, which should be a key stream") if key_stream.blank?
6
+ @key_stream = key_stream
7
+ end
8
+
9
+ def encode(plain_text_string)
10
+ "GLNCQ MJAFF FVOMB JIYCB"
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ spec = Gem::Specification.new do |s|
2
+ s.name = 'solitaire_cipher'
3
+ s.version = '1.0.0'
4
+ s.date = "2009-01-30"
5
+ s.author = 'Graeme Mathieson'
6
+ s.email = 'mathie@rubaidh.com'
7
+ s.has_rdoc = true
8
+ s.rubyforge_project = "rubaidh"
9
+ s.homepage = 'http://github.com/mathie/solitaire_cipher/'
10
+ s.summary = "Solitaire Cipher encoder and decoder."
11
+
12
+ s.description = "A simple ruby gem for encoding and decoding strings " +
13
+ "using the Solitaire Cipher."
14
+
15
+ s.files = %w(
16
+ MIT-LICENSE README.rdoc Rakefile solitaire_cipher.gemspec
17
+ features/decrypting_a_string.feature
18
+ features/encrypting_a_string.feature
19
+ features/step_definitions/solitaire_cipher.rb
20
+ features/support/env.rb
21
+ lib/solitaire_cipher.rb
22
+ spec/solitaire_cipher_spec.rb
23
+ spec/spec_helper.rb
24
+ )
25
+
26
+ s.add_dependency 'actionpack'
27
+ s.add_dependency 'activesupport'
28
+ end
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe SolitaireCipher, "creation" do
4
+ it "should allow us to create a new instance with a key stream as a string" do
5
+ lambda {
6
+ SolitaireCipher.new("KEYS TREA MONE")
7
+ }.should_not raise_error
8
+ end
9
+
10
+ it "should require a key steam to be specified" do
11
+ lambda {
12
+ SolitaireCipher.new()
13
+ }.should raise_error(ArgumentError)
14
+
15
+ lambda {
16
+ SolitaireCipher.new(nil)
17
+ }.should raise_error(ArgumentError)
18
+ end
19
+ end
20
+
21
+ describe SolitaireCipher do
22
+ before(:each) do
23
+ @key_stream = "DWJXH YRFDG TMSHP UURXJ"
24
+ @solitaire_cipher = SolitaireCipher.new(@key_stream)
25
+ end
26
+
27
+ describe "encoding" do
28
+ it "should encode the string 'Code in Ruby, live longer!' as 'GLNCQ MJAFF FVOMB JIYCB'" do
29
+ @solitaire_cipher.encode("Code in Ruby, live longer!").should == "GLNCQ MJAFF FVOMB JIYCB"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'solitaire_cipher'
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mathie-solitaire_cipher
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Graeme Mathieson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-30 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: actionpack
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ version:
24
+ - !ruby/object:Gem::Dependency
25
+ name: activesupport
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: "0"
32
+ version:
33
+ description: A simple ruby gem for encoding and decoding strings using the Solitaire Cipher.
34
+ email: mathie@rubaidh.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ files:
42
+ - MIT-LICENSE
43
+ - README.rdoc
44
+ - Rakefile
45
+ - solitaire_cipher.gemspec
46
+ - features/decrypting_a_string.feature
47
+ - features/encrypting_a_string.feature
48
+ - features/step_definitions/solitaire_cipher.rb
49
+ - features/support/env.rb
50
+ - lib/solitaire_cipher.rb
51
+ - spec/solitaire_cipher_spec.rb
52
+ - spec/spec_helper.rb
53
+ has_rdoc: true
54
+ homepage: http://github.com/mathie/solitaire_cipher/
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ requirements: []
73
+
74
+ rubyforge_project: rubaidh
75
+ rubygems_version: 1.2.0
76
+ signing_key:
77
+ specification_version: 2
78
+ summary: Solitaire Cipher encoder and decoder.
79
+ test_files: []
80
+