dekryptos 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +43 -0
- data/Rakefile +36 -0
- data/bin/dekrypt +79 -0
- data/lib/dekryptos/analysis.rb +132 -0
- data/lib/dekryptos/autokey.rb +118 -0
- data/lib/dekryptos/kryptos.rb +71 -0
- data/lib/dekryptos/monoalphabetic.rb +72 -0
- data/lib/dekryptos/playfair.rb +197 -0
- data/lib/dekryptos/transposition.rb +67 -0
- data/lib/dekryptos/vigenere.rb +137 -0
- data/lib/dekryptos.rb +11 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 593f93ec638be132440da71be3eb1374943a24c3
|
4
|
+
data.tar.gz: 7ed51ee4afe57848bb12b4ec27ddcd3e5fe30ffe
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 333bc3c6fc65dc198f060c30e5f5c0dc69cd315c8d6f8880a2a79ed6fbd3572fa2ab748c9e5ebec56807c2405431a4e388abaec7b95b57db532f2d3bbd7b3477
|
7
|
+
data.tar.gz: 16b3b9da8896ba425ed72a51e841b89df3411218d858bfb205b33dc164d50c5865cc46a4c250af2e84f224b5001dbbed3a2388950cd4306093691491d2ad84a2
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2013 - 2014 Eric Weinstein
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
D E K Я Y P T O S
|
2
|
+
===================
|
3
|
+
|
4
|
+
## About
|
5
|
+
These are little bits o' Ruby magic I've been using to mess around with decrypting parts of the [Kryptos sculpture](http://en.wikipedia.org/wiki/Kryptos).
|
6
|
+
|
7
|
+
## Dependencies
|
8
|
+
* Ruby (2.0+).
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
```bash
|
12
|
+
$ gem install dekryptos
|
13
|
+
```
|
14
|
+
|
15
|
+
## Examples
|
16
|
+
```bash
|
17
|
+
$ dekrypt -t EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJYQTQUXQBQVYUVLLTREVJYQTMKYRDMFD -k KRYPTOS,PALIMPSEST -c vigenere
|
18
|
+
BETWEENSUBTLESHADINGANDTHEABSENCEOFLIGHTLIESTHENUANCEOFIQLUSION
|
19
|
+
|
20
|
+
$ dekrypt -t VFPJUDEEHZWETZYVGWHKKQETGFQJNCEGGWHKK?DQMCPFQZDQMMIAGPFXHQRLGTIMVMZJANQLVKQEDAGDVFRPJUNGEUNAQZGZLECGYUXUEENJTBJLBQCRTBJDFHRRYIZETKZEMVDUFKSJHKFWHKUWQLSZFTIHHDDDUVH?DWKBFUFPWNTDFIYCUQZEREEVLDKFEZMOQQJLTTUGSYQPFEUNLAVIDXFLGGTEZ?FKZBSFDQVGOGIPUFXHHDRKFFHQNTGPUAECNUVPDJMQCLQUMUNEDFQELZZVRRGKFFVOEEXBDMVPNFQXEZLGREDNQFMPNZGLFLPMRJQYALMGNUVPDXVKPDQUMEBEDMHDAFMJGZNUPLGESWJLLAETG -k KRYPTOS,ABSCISSA -c vigenere
|
21
|
+
|
22
|
+
ITWASTOTALLYINVISIBLEHOWSTHATPOSSIBLETHEYUSEDTHEEARTHSMAGNETICFIELDXTHEINFORMATIONWASGATHEREDANDTRANSMITTEDUNDERGRUUNDTOANUNKNOWNLOCATIONXDOESLANGLEYKNOWABOUTTHISTHEYSHOULDITSBURIEDOUTTHERESOMEWHEREXWHOKNOWSTHEEXACTLOCATIONONLYWWTHISWASHISLASTMESSAGEXTHIRTYEIGHTDEGREESFIFTYSEVENMINUTESSIXPOINTFIVESECONDSNORTHSEVENTYSEVENDEGREESEIGHTMINUTESFORTYFOURSECONDSWESTXLAYERTWO
|
23
|
+
|
24
|
+
$ dekrypt -t ENDYAHROHNLSRHEOCPTEOIBIDYSHNAIACHTNREYULDSLLSLLNOHSNOSMRWXMNETPRNGATIHNRARPESLNNELEBLPIIACAEWMTWNDITEENRAHCTENEUDRETNHAEOETFOLSEDTIWENHAEIOYTEYQHEENCTAYCREIFTBRSPAMHHEWENATAMATEGYEERLBTEEFOASFIOTUETUAEOTOARMAEERTNRTIBSEDDNIAAHTTMSTEWPIEROAGRIEWFEBAECTDDHILCEIHSITEGOEAOSDDRYDLORITRKLMLEHAGTDHARDPNEOHMGFMFEUHEECDMRIPFEIMEHNLSSTTRTVDOHW -k 4 -c transposition
|
25
|
+
|
26
|
+
YOSOEIHANULLSMMPANPNEIATINCEEATSIHOYEAEBAEAAYLESTUTMRTEITTIAEBTIITEDDILHDDOFUCIINTVWDRLETBSITYSLHSXTGHRLLPCMDEHNRHELTNIEETRTPHNMGREAOTORERSNHSPOIECHEIOSYRKETREGEEREHSTHNHNHPIYAHEDSOOWENIASELAWNEAEDNOODEETHCCFSHEAEETOIEEAENBDAMWRRFEDCSGORORLGANMFEMFESROEAHRCODNCRLLNNRNRTRENBIEWTRTUTEFEWAYQNYIRMWTTEBFFUAOATIDATEEGWADLHEADLTMAHPHMHDPMLTD
|
27
|
+
|
28
|
+
$ dekrypt -t YOSOEIHANULLSMMPANPNEIATINCEEATSIHOYEAEBAEAAYLESTUTMRTEITTIAEBTIITEDDILHDDOFUCIINTVWDRLETBSITYSLHSXTGHRLLPCMDEHNRHELTNIEETRTPHNMGREAOTORERSNHSPOIECHEIOSYRKETREGEEREHSTHNHNHPIYAHEDSOOWENIASELAWNEAEDNOODEETHCCFSHEAEETOIEEAENBDAMWRRFEDCSGORORLGANMFEMFESROEAHRCODNCRLLNNRNRTRENBIEWTRTUTEFEWAYQNYIRMWTTEBFFUAOATIDATEEGWADLHEADLTMAHPHMHDPMLTD -k 48 -c transposition
|
29
|
+
|
30
|
+
SLOWLYDESPARATLYSLOWLYTHEREMAINSOFPASSAGEDEBRISTHATENCUMBEREDTHELOWERPARTOFTHEDOORWAYWASREMOVEDWITHTREMBLINGHANDSIMADEATINYBREACHINTHEUPPERLEFTHANDCORNERANDTHENWIDENINGTHEHOLEALITTLEIINSERTEDTHECANDLEANDPEEREDINTHEHOTAIRESCAPINGFROMTHECHAMBERCAUSEDTHEFLAMETOFLICKERBUTPRESENTLYDETAILSOFTHEROOMWITHINEMERGEDFROMTHEMISTXCANYOUSEEANYTHINGQ
|
31
|
+
```
|
32
|
+
|
33
|
+
## Ciphers
|
34
|
+
There are currently files for Autokey, Monoalphabetic (simple ROT-13), Playfair, Transposition, and Vigenere ciphers.
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
1. Branch (`git checkout -b fancy-new-feature`)
|
38
|
+
2. Commit (`git commit -m "Fanciness!"`)
|
39
|
+
3. Lint (`bundle exec rake rubocop`)
|
40
|
+
4. Test (`bundle exec rake spec`)
|
41
|
+
5. Generate documentation (`bundle exec rake rdoc`)
|
42
|
+
6. Push (`git push origin fancy-new-feature`)
|
43
|
+
7. Ye Olde Pulle Request
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require 'rdoc/task'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'rubocop/rake_task'
|
7
|
+
|
8
|
+
task default: :help
|
9
|
+
|
10
|
+
desc 'Display help menu'
|
11
|
+
task :help do
|
12
|
+
puts <<-eos
|
13
|
+
Available Rake tasks:
|
14
|
+
|
15
|
+
rake rdoc # Generate documentation
|
16
|
+
rake rubocop # Lint
|
17
|
+
rake spec # Test
|
18
|
+
eos
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Generate documentation'
|
22
|
+
RDoc::Task.new do |rdoc|
|
23
|
+
rdoc.main = 'README.rdoc'
|
24
|
+
rdoc.rdoc_files.include('README.md', 'lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Lint'
|
28
|
+
RuboCop::RakeTask.new(:rubocop) do |t|
|
29
|
+
t.patterns = %w(lib/**/*.rb)
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Test'
|
33
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
34
|
+
t.rspec_opts = ['--color --format d']
|
35
|
+
t.pattern = './spec/**/*_spec.rb'
|
36
|
+
end
|
data/bin/dekrypt
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
# Author:: Eric Weinstein
|
5
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
6
|
+
# License:: MIT (see LICENSE)
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
require 'ostruct'
|
10
|
+
require_relative '../lib/dekryptos'
|
11
|
+
|
12
|
+
class Dekrypt
|
13
|
+
def self.parse(args)
|
14
|
+
options = OpenStruct.new
|
15
|
+
|
16
|
+
opt_parser = OptionParser.new do |opts|
|
17
|
+
opts.banner = 'Usage: dekrypt -t TEXT -k KEYS -c CIPHER'
|
18
|
+
opts.separator ''
|
19
|
+
opts.separator 'Running dekryptos with the dekrypt command:'
|
20
|
+
opts.separator ' TEXT is a block of text (without spaces or punctuation)'
|
21
|
+
opts.separator ' that you would like to decrypt.'
|
22
|
+
opts.separator ''
|
23
|
+
opts.separator ' KEYS is a list of comma-delimited words to be used'
|
24
|
+
opts.separator ' as keys in your deciphering.'
|
25
|
+
opts.separator ''
|
26
|
+
opts.separator ' CIPHER is the name of the cipher you would like to use.'
|
27
|
+
opts.separator ' Supported ciphers: autokey, monoalphabetic, playfair,'
|
28
|
+
opts.separator ' transposition, vigenere.'
|
29
|
+
opts.separator ''
|
30
|
+
|
31
|
+
opts.on('-h', '--help', 'Display this screen') do
|
32
|
+
puts opt_parser
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on('-t t', '--text t', String, 'Enter ciphertext') do |text|
|
37
|
+
options.text = text
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-k a,b,c', '--keys a,b,c', Array, 'Enter key(s)') do |key|
|
41
|
+
options.keys = key
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-c c', '--cipher c', String, 'Enter cipher name') do |cipher|
|
45
|
+
options.cipher = cipher
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
begin opt_parser.parse!(args)
|
50
|
+
cipher = options.cipher.capitalize
|
51
|
+
self.class.send(:include, Object.const_get(cipher))
|
52
|
+
|
53
|
+
case cipher
|
54
|
+
when 'Autokey'
|
55
|
+
puts decrypt(options.text, build_tabula_recta, options.keys.first)
|
56
|
+
when 'Monoalphabetic'
|
57
|
+
puts rot_13(options.text)
|
58
|
+
when 'Playfair'
|
59
|
+
puts decrypt(options.text, build_table(options.keys.first))
|
60
|
+
when 'Transposition'
|
61
|
+
puts decrypt(options.text, options.keys.first.to_i)
|
62
|
+
when 'Vigenere'
|
63
|
+
key_one = options.keys.first
|
64
|
+
key_two = options.keys.last
|
65
|
+
puts decrypt(options.text, build_table(key_one, key_two), key_two)
|
66
|
+
else
|
67
|
+
fail "[!] Unknown cipher: #{options.cipher}"
|
68
|
+
end
|
69
|
+
rescue => e
|
70
|
+
msg = "[!] An error occurred: #{e.message}\n"
|
71
|
+
msg << "#{e.backtrace.join("\n")}\n\n"
|
72
|
+
msg << "Run dekrypt -h for help."
|
73
|
+
abort(msg)
|
74
|
+
end
|
75
|
+
options
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
Dekrypt.parse(ARGV)
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
5
|
+
# License:: MIT (see LICENSE)
|
6
|
+
|
7
|
+
##
|
8
|
+
# Tools for analyzing suggested Kryptos solutions.
|
9
|
+
module Analysis
|
10
|
+
# Public: Provides all factors of a given number.
|
11
|
+
#
|
12
|
+
# === Parameter(s)
|
13
|
+
# +n+ - +Integer+: The number to factor.
|
14
|
+
#
|
15
|
+
# === Return Value
|
16
|
+
# +Array<Integer>+: A list of factors.
|
17
|
+
def factor(n)
|
18
|
+
factors = []
|
19
|
+
(1..n).each do |x|
|
20
|
+
factors << x if n % x == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
factors
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Provides all anagrams for a given word.
|
27
|
+
#
|
28
|
+
# === Parameter(s)
|
29
|
+
# +text+ - +String+: The text to anagram.
|
30
|
+
#
|
31
|
+
# === Return Value
|
32
|
+
# +Array<String>+: A list of anagrams.
|
33
|
+
# rubocop: disable MethodLength, UnderscorePrefixedVariableName
|
34
|
+
def anagram(text)
|
35
|
+
head, tail = [], text.split('')
|
36
|
+
stack = [[head, tail]]
|
37
|
+
result = []
|
38
|
+
|
39
|
+
while stack.size > 0
|
40
|
+
head, tail = stack.pop
|
41
|
+
if tail.size.zero?
|
42
|
+
result << head
|
43
|
+
else
|
44
|
+
tail.each_with_index do |_, i|
|
45
|
+
_tail = tail.dup
|
46
|
+
curr = _tail.delete_at(i)
|
47
|
+
_head = head.dup
|
48
|
+
_head << curr
|
49
|
+
stack << [_head, _tail]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
result.map! { |p| p.join('') }
|
55
|
+
result.uniq!
|
56
|
+
result
|
57
|
+
end
|
58
|
+
# rubocop: enable MethodLength, UnderscorePrefixedVariableName
|
59
|
+
|
60
|
+
# Public: Checks whether one word or phrase is
|
61
|
+
# an anagram of another.
|
62
|
+
#
|
63
|
+
# === Parameter(s)
|
64
|
+
# +phrase_one+ - +String+: A word or phrase.
|
65
|
+
# +phrase_two+ - +String+: A word or phrase that
|
66
|
+
# may be an anagram of +phrase_one+.
|
67
|
+
#
|
68
|
+
# === Return Value
|
69
|
+
# +Boolean+: True if one phrase is an anagram
|
70
|
+
# of the other, false otherwise.
|
71
|
+
def anagram?(phrase_one, phrase_two)
|
72
|
+
phrase_one.split('').sort == phrase_two.split('').sort
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Performs frequency analysis on the provided
|
76
|
+
# text.
|
77
|
+
#
|
78
|
+
# === Parameter(s)
|
79
|
+
# +text+ - +String+: The text to analyze.
|
80
|
+
#
|
81
|
+
# === Return Value
|
82
|
+
# +Hash<String, Integer>+: A hash representing the
|
83
|
+
# letter frequencies.
|
84
|
+
def frequency(text)
|
85
|
+
text = text.downcase.gsub(/\s*/, '')
|
86
|
+
chars = text.split('')
|
87
|
+
freqs = Hash[('a'..'z').to_a.zip([0] * 26)]
|
88
|
+
|
89
|
+
chars.each do |c|
|
90
|
+
freqs[c] += 1
|
91
|
+
end
|
92
|
+
|
93
|
+
freqs
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Calculates the index of coincidence for
|
97
|
+
# the given text (see http://en.wikipedia.org/wiki/
|
98
|
+
# Index_of_coincidence).
|
99
|
+
#
|
100
|
+
# === Parameter(s)
|
101
|
+
# +text+ - +String+: The text to analyze.
|
102
|
+
#
|
103
|
+
# === Return Value
|
104
|
+
# +Float+: The index of coincidence for the text.
|
105
|
+
def ioc(text)
|
106
|
+
c = 26.0
|
107
|
+
freqs = frequency(text)
|
108
|
+
n = text.length
|
109
|
+
|
110
|
+
f = freqs.values.reduce(0) { |a, e| a + (e * (e - 1)) }
|
111
|
+
f / ((n * (n - 1)) / c)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Public: Calculates the percentage of text composed
|
115
|
+
# of vowels. (Y is not included as a vowel.)
|
116
|
+
#
|
117
|
+
# === Parameter(s)
|
118
|
+
# +text+ - +String+: The text to analyze.
|
119
|
+
#
|
120
|
+
# === Return Value
|
121
|
+
# +Float+: The percentage comprising vowels.
|
122
|
+
def vowels(text)
|
123
|
+
vowels = %w(A E I O U)
|
124
|
+
chars = text.upcase.split('')
|
125
|
+
vowel_count = 0
|
126
|
+
chars.each do |c|
|
127
|
+
vowel_count += 1 if vowels.include? c
|
128
|
+
end
|
129
|
+
|
130
|
+
vowel_count.to_f / text.length
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
5
|
+
# License:: MIT (see LICENSE)
|
6
|
+
|
7
|
+
##
|
8
|
+
# Encryption/decryption using autokey ciphers
|
9
|
+
# (http://en.wikipedia.org/wiki/Autokey_cipher)
|
10
|
+
module Autokey
|
11
|
+
# Public: encrypts plaintext via autokey cipher.
|
12
|
+
#
|
13
|
+
# We generate our key by prepending the keyword to
|
14
|
+
# the plaintext. Then, for each pair formed by the
|
15
|
+
# i-th index of the key and the plaintext, we look
|
16
|
+
# up that (row, column) coordinate in the tabula
|
17
|
+
# recta and add it to the ciphertext.
|
18
|
+
#
|
19
|
+
# === Parameter(s)
|
20
|
+
# +plaintext+ - +String+: the text to be encrypted.
|
21
|
+
# +table+ - +Array+: the table generated by +build_tabula_recta+.
|
22
|
+
# +keyword+ - +String+: the keyword to be prepended to the plaintext
|
23
|
+
# to form the key.
|
24
|
+
#
|
25
|
+
# === Return Value
|
26
|
+
# +String+: the encrypted text.
|
27
|
+
#
|
28
|
+
# === Example
|
29
|
+
#
|
30
|
+
# +encrypt('ATTACKATDAWN', build_tabula_recta, 'KRYPTOS')+
|
31
|
+
# +=> "KKRPVYSTWTWP"+
|
32
|
+
def encrypt(plaintext, table, keyword)
|
33
|
+
ciphertext = ''
|
34
|
+
plaintext.upcase!
|
35
|
+
keyword.upcase!
|
36
|
+
|
37
|
+
key = (keyword + plaintext).split('')
|
38
|
+
|
39
|
+
plaintext.tr('?', '').split('').each_with_index do |letter, index|
|
40
|
+
ciphertext << table[table[0].index(letter)][table[0].index(key[index])]
|
41
|
+
end
|
42
|
+
|
43
|
+
ciphertext
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: decrypts ciphertext via autokey cipher.
|
47
|
+
#
|
48
|
+
# For each pair formed by the i-th index of the key and
|
49
|
+
# ciphertext, we look up the ciphertext letter in the key
|
50
|
+
# letter row and add the corresponding letter from the top
|
51
|
+
# row to the plaintext. Because the text is enciphered with
|
52
|
+
# itself, we continuously append the deciphered text to the
|
53
|
+
# key as we go along.
|
54
|
+
#
|
55
|
+
# === Parameter(s)
|
56
|
+
# +ciphertext+ - +String+: the text to be decrypted.
|
57
|
+
# +table+ - +Array+: the table generated by +build_tabula_recta+.
|
58
|
+
# +keyword+ - +String+: the keyword used to encrypt the text.
|
59
|
+
#
|
60
|
+
# === Return Value
|
61
|
+
# +String+: the decrypted text.
|
62
|
+
#
|
63
|
+
# === Example
|
64
|
+
#
|
65
|
+
# +decrypt('KKRPVYSTWTWP', build_tabula_recta, 'KRYPTOS')+
|
66
|
+
# +=> "ATTACKATDAWN"+
|
67
|
+
def decrypt(ciphertext, table, keyword)
|
68
|
+
plaintext = ''
|
69
|
+
ciphertext.upcase!
|
70
|
+
keyword.upcase!
|
71
|
+
|
72
|
+
key = (keyword).split('')
|
73
|
+
|
74
|
+
ciphertext.tr('?', '').split('').each_with_index do |letter, index|
|
75
|
+
plaintext << table[0][table[table[0].index(key[index])].index(letter)]
|
76
|
+
key << table[0][table[table[0].index(key[index])].index(letter)]
|
77
|
+
end
|
78
|
+
|
79
|
+
plaintext
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: generates the tabula recta needed to encrypt
|
83
|
+
# text via autokey cipher.
|
84
|
+
#
|
85
|
+
# === Parameter(s)
|
86
|
+
# None.
|
87
|
+
#
|
88
|
+
# === Return Value
|
89
|
+
# +Array+: a two-dimensional array of characters (26 x 26).
|
90
|
+
#
|
91
|
+
# === Example
|
92
|
+
# +build_tabula_recta+
|
93
|
+
#
|
94
|
+
# (See http://en.wikipedia.org/wiki/Tabula_recta for an example.)
|
95
|
+
def build_tabula_recta
|
96
|
+
table = []
|
97
|
+
row = alphabet
|
98
|
+
|
99
|
+
alphabet.each do |letter|
|
100
|
+
table << row.rotate(row.index(letter))
|
101
|
+
end
|
102
|
+
|
103
|
+
table
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Private: generates an alphabet.
|
109
|
+
#
|
110
|
+
# === Parameter(s)
|
111
|
+
# None.
|
112
|
+
#
|
113
|
+
# === Return Value
|
114
|
+
# +Array+: an array comprising the letters of the alphabet.
|
115
|
+
def alphabet
|
116
|
+
('A'..'Z').to_a
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
5
|
+
# License:: MIT (see LICENSE)
|
6
|
+
|
7
|
+
##
|
8
|
+
# == K1, K2, K3, K4
|
9
|
+
#
|
10
|
+
# These are the four parts of the Kryptos sculpture
|
11
|
+
# (http://en.wikipedia.org/wiki/Kryptos).
|
12
|
+
module Kryptos
|
13
|
+
# This is the first section of the ciphertext.
|
14
|
+
K1 = 'EMUFPHZLRFAXYUSDJKZLDKRNSHGNFIVJYQTQUXQ'\
|
15
|
+
'BQVYUVLLTREVJYQTMKYRDMFD'
|
16
|
+
|
17
|
+
# I've added an extra 'S' eight characters before the
|
18
|
+
# end of K2 in order to correct Jim's error (the final
|
19
|
+
# words should be "X LAYER TWO" and not "ID BY ROWS").
|
20
|
+
K2 = 'VFPJUDEEHZWETZYVGWHKKQETGFQJNCEGGWHKK?D'\
|
21
|
+
'QMCPFQZDQMMIAGPFXHQRLGTIMVMZJANQLVKQEDAGDVFRPJUNGEU'\
|
22
|
+
'NAQZGZLECGYUXUEENJTBJLBQCRTBJDFHRRYIZETKZEMVDUFKSJH'\
|
23
|
+
'KFWHKUWQLSZFTIHHDDDUVH?DWKBFUFPWNTDFIYCUQZEREEVLDKF'\
|
24
|
+
'EZMOQQJLTTUGSYQPFEUNLAVIDXFLGGTEZ?FKZBSFDQVGOGIPUFX'\
|
25
|
+
'HHDRKFFHQNTGPUAECNUVPDJMQCLQUMUNEDFQELZZVRRGKFFVOEE'\
|
26
|
+
'XBDMVPNFQXEZLGREDNQFMPNZGLFLPMRJQYALMGNUVPDXVKPDQUM'\
|
27
|
+
'EBEDMHDAFMJGZNUPLGESWJLLAETG'
|
28
|
+
|
29
|
+
# I've omitted the final '?' from K3 in order to
|
30
|
+
# facilitate correct transposition.
|
31
|
+
K3 = 'ENDYAHROHNLSRHEOCPTEOIBIDYSHNAIACHTNR'\
|
32
|
+
'EYULDSLLSLLNOHSNOSMRWXMNETPRNGATIHNRARPESLNNELEBLPI'\
|
33
|
+
'IACAEWMTWNDITEENRAHCTENEUDRETNHAEOETFOLSEDTIWENHAEI'\
|
34
|
+
'OYTEYQHEENCTAYCREIFTBRSPAMHHEWENATAMATEGYEERLBTEEFO'\
|
35
|
+
'ASFIOTUETUAEOTOARMAEERTNRTIBSEDDNIAAHTTMSTEWPIEROAG'\
|
36
|
+
'RIEWFEBAECTDDHILCEIHSITEGOEAOSDDRYDLORITRKLMLEHAGTD'\
|
37
|
+
'HARDPNEOHMGFMFEUHEECDMRIPFEIMEHNLSSTTRTVDOHW'
|
38
|
+
|
39
|
+
# This is the final (and as-yet untranslated)
|
40
|
+
# section of the ciphertext. According to Jim,
|
41
|
+
# NYPVTT deciphers to BERLIN.
|
42
|
+
K4 = 'OBKRUOXOGHULBSOLIFBBWFLRVQQPRNGKSSOTWT'\
|
43
|
+
'QSJQSSEKZZWATJKLUDIAWINFBNYPVTTMZFPKWGDKZXTJCDIGKUH'\
|
44
|
+
'UAUEKCAR'
|
45
|
+
|
46
|
+
# === Example Tables
|
47
|
+
#
|
48
|
+
# This is an example of a Vigenere cipher table.
|
49
|
+
EXAMPLE_VIGENERE_TABLE = [
|
50
|
+
%w(K R Y P T O S A B C D E F G H I J L M N Q U V W X Z),
|
51
|
+
%w(P T O S A B C D E F G H I J L M N Q U V W X Z K R Y),
|
52
|
+
%w(A B C D E F G H I J L M N Q U V W X Z K R Y P T O S),
|
53
|
+
%w(L M N Q U V W X Z K R Y P T O S A B C D E F G H I J),
|
54
|
+
%w(I J L M N Q U V W X Z K R Y P T O S A B C D E F G H),
|
55
|
+
%w(M N Q U V W X Z K R Y P T O S A B C D E F G H I J L),
|
56
|
+
%w(P T O S A B C D E F G H I J L M N Q U V W X Z K R Y),
|
57
|
+
%w(S A B C D E F G H I J L M N Q U V W X Z K R Y P T O),
|
58
|
+
%w(E F G H I J L M N Q U V W X Z K R Y P T O S A B C D),
|
59
|
+
%w(S A B C D E F G H I J L M N Q U V W X Z K R Y P T O),
|
60
|
+
%w(T O S A B C D E F G H I J L M N Q U V W X Z K R Y P)
|
61
|
+
]
|
62
|
+
|
63
|
+
# This is an example of a Playfair cipher table.
|
64
|
+
EXAMPLE_PLAYFAIR_TABLE = [
|
65
|
+
%w(K R Y P T),
|
66
|
+
%w(O S A B C),
|
67
|
+
%w(D E F G H),
|
68
|
+
%w(I J L M N),
|
69
|
+
%w(U V W X Z)
|
70
|
+
]
|
71
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
5
|
+
# License:: MIT (see LICENSE)
|
6
|
+
|
7
|
+
##
|
8
|
+
# Assorted tools for encrypting/decrypting monoalphabetic substitution ciphers
|
9
|
+
# (https://en.wikipedia.org/wiki/Substitution_cipher)
|
10
|
+
module Monoalphabetic
|
11
|
+
# An error raised when a non-alphabetic character is passed to an encryption/
|
12
|
+
# decryption method.
|
13
|
+
class CharacterError < Exception; end
|
14
|
+
|
15
|
+
# Public: encrypts and decrypts text via ROT13
|
16
|
+
# (https://en.wikipedia.org/wiki/ROT13).
|
17
|
+
#
|
18
|
+
# * Each letter is shifted by 13 places in the alphabet, _e.g._ A -> N.
|
19
|
+
# * Because encryption/decryption are symmetric, the same method is used to
|
20
|
+
# both encrypt and decrypt text.
|
21
|
+
#
|
22
|
+
# === Parameter(s)
|
23
|
+
# +plaintext+ - +String+: the text to be encrypted or decrypted.
|
24
|
+
#
|
25
|
+
# === Return Value
|
26
|
+
# +String+: the encrypted/decrypted text.
|
27
|
+
#
|
28
|
+
# === Example
|
29
|
+
#
|
30
|
+
# +rot_13('HELLO')+
|
31
|
+
# +=> "URYYB"+
|
32
|
+
#--
|
33
|
+
# rubocop:disable Style/MethodLength
|
34
|
+
#++
|
35
|
+
def rot_13(plaintext)
|
36
|
+
fail CharacterError,
|
37
|
+
'Plaintext must be a string' unless plaintext.respond_to? :upcase
|
38
|
+
letters = plaintext.upcase.split('')
|
39
|
+
|
40
|
+
ciphertext = []
|
41
|
+
|
42
|
+
letters.each do |letter|
|
43
|
+
fail CharacterError,
|
44
|
+
'Plaintext must be letters only' unless alphabet.include? letter
|
45
|
+
|
46
|
+
idx_to_check = alphabet.index(letter)
|
47
|
+
if idx_to_check < 13
|
48
|
+
ciphertext << alphabet[idx_to_check + 13]
|
49
|
+
else
|
50
|
+
ciphertext << alphabet[idx_to_check - 13]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
ciphertext.join('')
|
55
|
+
end
|
56
|
+
#--
|
57
|
+
# rubocop:enable Style/MethodLength
|
58
|
+
#++
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Private: generates an alphabet.
|
63
|
+
#
|
64
|
+
# === Parameter(s)
|
65
|
+
# None.
|
66
|
+
#
|
67
|
+
# === Return Value
|
68
|
+
# +Array+: an array comprising the letters of the alphabet.
|
69
|
+
def alphabet
|
70
|
+
('A'..'Z').to_a
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
5
|
+
# License:: MIT (see LICENSE)
|
6
|
+
|
7
|
+
##
|
8
|
+
# Encryption/decryption using Playfair ciphers
|
9
|
+
# (http://en.wikipedia.org/wiki/Playfair_cipher)
|
10
|
+
module Playfair
|
11
|
+
# Public: builds the 5 x 5 grid for our Playfair cipher.
|
12
|
+
#
|
13
|
+
# === Parameter(s)
|
14
|
+
# +key+ - +String+: the key forms the first +key.length+ letters of
|
15
|
+
# the 5 x 5 grid from left to right (after repeats are omitted),
|
16
|
+
# followed by the remaining letters of the alphabet in order. In this
|
17
|
+
# algorithm, the letter 'Q' is removed from the table entirely to make
|
18
|
+
# it 5 x 5.
|
19
|
+
#
|
20
|
+
# === Return Value
|
21
|
+
# +Array+: a two-dimensional array of characters (5 x 5).
|
22
|
+
#
|
23
|
+
# === Example
|
24
|
+
# +build_table('KRYPTOS')+
|
25
|
+
#
|
26
|
+
# (See +EXAMPLE_PLAYFAIR_TABLE+ in +kryptos.rb+ for the result.)
|
27
|
+
#--
|
28
|
+
# rubocop:disable Style/MethodLength, Style/Next, Style/CyclomaticComplexity
|
29
|
+
#++
|
30
|
+
def build_table(key)
|
31
|
+
key = key.upcase.split('').uniq
|
32
|
+
grid = [[], [], [], [], []]
|
33
|
+
|
34
|
+
grid_letters = key
|
35
|
+
rest_of_grid = ('A'..'Z').to_a
|
36
|
+
rest_of_grid.each do |letter|
|
37
|
+
grid_letters << letter unless key.include? letter
|
38
|
+
end
|
39
|
+
|
40
|
+
grid_letters.delete('Q')
|
41
|
+
|
42
|
+
grid.each_with_index do |_, index|
|
43
|
+
grid[index] = grid_letters[index * 5, 5]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: encrypts plaintext using a Playfair cipher.
|
48
|
+
#
|
49
|
+
# * If both elements of the pair are in the same row, we add the element
|
50
|
+
# to the right to the ciphertext.
|
51
|
+
# * If the elements of the pair are in _different_ rows, we store the
|
52
|
+
# location as a (row, index) tuple in +matches+ for evaluation as follows:
|
53
|
+
# * Comparing two +matches+ at a time, if they share an index value, they
|
54
|
+
# must be in the same column. We then push the values in the ensuing rows
|
55
|
+
# to the ciphertext string.
|
56
|
+
# * If the two +matches+ do _not_ share an index value, they must form
|
57
|
+
# nonconsecutive vertices (opposing corners) of a rectangle. We then push
|
58
|
+
# their complements to the ciphertext string, which is equivalent to
|
59
|
+
# simply swapping their indices.
|
60
|
+
# * After comparing each set of two +matches+, we shift them off the
|
61
|
+
# +matches+ array and continue.
|
62
|
+
#
|
63
|
+
# === Parameter(s)
|
64
|
+
# +plaintext+ - +String+: the text to be encrypted.
|
65
|
+
# +grid+ - +Array+: the grid generated by +build_table+.
|
66
|
+
#
|
67
|
+
# === Return Value
|
68
|
+
# +String+: the encrypted text.
|
69
|
+
#
|
70
|
+
# === Example
|
71
|
+
# +encrypt('ATTACKATDAWN', EXAMPLE_PLAYFAIR_TABLE)+
|
72
|
+
# +=> "CYYCOTCYFOZL"+
|
73
|
+
def encrypt(plaintext, grid)
|
74
|
+
ciphertext = []
|
75
|
+
pairs = bigramify(plaintext)
|
76
|
+
matches = []
|
77
|
+
|
78
|
+
pairs.each do |pair|
|
79
|
+
grid.each_with_index do |row, index|
|
80
|
+
if row.include?(pair[0]) && row.include?(pair[1])
|
81
|
+
ciphertext << row[(row.index(pair[0]) + 1) % 5]
|
82
|
+
ciphertext << row[(row.index(pair[1]) + 1) % 5]
|
83
|
+
else
|
84
|
+
matches[0] = [index, row.index(pair[0])] if row.include?(pair[0])
|
85
|
+
matches[1] = [index, row.index(pair[1])] if row.include?(pair[1])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
unless matches.empty?
|
89
|
+
if matches[0][1] == matches[1][1]
|
90
|
+
ciphertext << grid[(matches[0][0] + 1) % 5][matches[0][1]]
|
91
|
+
ciphertext << grid[(matches[1][0] + 1) % 5][matches[1][1]]
|
92
|
+
2.times { matches.shift }
|
93
|
+
else
|
94
|
+
ciphertext << grid[matches[0][0]][matches[1][1]]
|
95
|
+
ciphertext << grid[matches[1][0]][matches[0][1]]
|
96
|
+
2.times { matches.shift }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
ciphertext.join('')
|
102
|
+
end
|
103
|
+
|
104
|
+
# Public: decrypts ciphertext using a Playfair cipher.
|
105
|
+
#
|
106
|
+
# * If both elements of the pair are in the same row, we add the element to
|
107
|
+
# the left to the ciphertext.
|
108
|
+
# * If the elements of the pair are in _different_ rows, we store the location
|
109
|
+
# as a (row, index) tuple in +matches+ for evaluation as follows:
|
110
|
+
# * Comparing two +matches+ at a time, if they share an index value, they
|
111
|
+
# must be in the same column. We then push the values in the preceding
|
112
|
+
# rows to the ciphertext string.
|
113
|
+
# * If the two +matches+ do _not_ share an index value, they must form
|
114
|
+
# nonconsecutive vertices (opposing corners) of a rectangle. We then push
|
115
|
+
# their complements to the ciphertext string, which is equivalent to
|
116
|
+
# simply swapping their indices.
|
117
|
+
# * After comparing each set of two +matches+, we shift them off the
|
118
|
+
# +matches+ array and continue.
|
119
|
+
#
|
120
|
+
# === Parameter(s)
|
121
|
+
# +ciphertext+ - +String+: the text to be decrypted.
|
122
|
+
# +grid+ - +Array+: the grid generated by +build_table+.
|
123
|
+
#
|
124
|
+
# === Return Value
|
125
|
+
# +String+: the decrypted text.
|
126
|
+
#
|
127
|
+
# === Example
|
128
|
+
# +encrypt('CYYCOTCYFOZL', EXAMPLE_PLAYFAIR_TABLE)+
|
129
|
+
# +=> "ATTACKATDAWN"+
|
130
|
+
def decrypt(ciphertext, grid)
|
131
|
+
plaintext = []
|
132
|
+
pairs = bigramify(ciphertext)
|
133
|
+
matches = []
|
134
|
+
|
135
|
+
pairs.each do |pair|
|
136
|
+
grid.each_with_index do |row, index|
|
137
|
+
if row.include?(pair[0]) && row.include?(pair[1])
|
138
|
+
plaintext << row[(row.index(pair[0]) + 9) % 5]
|
139
|
+
plaintext << row[(row.index(pair[1]) + 9) % 5]
|
140
|
+
else
|
141
|
+
matches[0] = [index, row.index(pair[0])] if row.include?(pair[0])
|
142
|
+
matches[1] = [index, row.index(pair[1])] if row.include?(pair[1])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
unless matches.empty?
|
146
|
+
if matches[0][1] == matches[1][1]
|
147
|
+
plaintext << grid[(matches[0][0] + 9) % 5][matches[0][1]]
|
148
|
+
plaintext << grid[(matches[1][0] + 9) % 5][matches[1][1]]
|
149
|
+
2.times { matches.shift }
|
150
|
+
else
|
151
|
+
plaintext << grid[matches[0][0]][matches[1][1]]
|
152
|
+
plaintext << grid[matches[1][0]][matches[0][1]]
|
153
|
+
2.times { matches.shift }
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
plaintext.join('')
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
# Private: converts text to bigrams for processing by our
|
164
|
+
# encryption/decryption methods.
|
165
|
+
#
|
166
|
+
# * If we encounter a double-letter bigram, we break it up with an X.
|
167
|
+
# * If we end up with an odd number of letters, we pad with a final X.
|
168
|
+
# * We replace any Qs with Xs (since +build_table+ omits Qs).
|
169
|
+
#
|
170
|
+
# === Parameter(s)
|
171
|
+
# +text+ - +String+: the text to convert to bigrams.
|
172
|
+
#
|
173
|
+
# === Return Value
|
174
|
+
# +Array+: an array of two-character strings (bigrams).
|
175
|
+
def bigramify(text)
|
176
|
+
text_array = text.upcase.split('')
|
177
|
+
|
178
|
+
text_array.each_with_index do |char, index|
|
179
|
+
if index.even?
|
180
|
+
if char == text_array[index + 1]
|
181
|
+
text_array.insert(index + 1, 'X')
|
182
|
+
text = text_array.join('')
|
183
|
+
bigramify(text)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
text_array << 'X' if text_array.length.odd?
|
189
|
+
text = text_array.join('')
|
190
|
+
text.gsub!('Q', 'X')
|
191
|
+
|
192
|
+
text.split(/(.{2})/).reject { |pair| pair if pair.empty? }
|
193
|
+
end
|
194
|
+
#--
|
195
|
+
# rubocop:enable Style/MethodLength, Style/Next, Style/CyclomaticComplexity
|
196
|
+
#++
|
197
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
5
|
+
# License:: MIT (see LICENSE)
|
6
|
+
|
7
|
+
##
|
8
|
+
# Encryption/decryption using keyed columnar transposition
|
9
|
+
# (http://en.wikipedia.org/wiki/Transposition_cipher)
|
10
|
+
module Transposition
|
11
|
+
# Public: encrypts text via keyed columnar transposition.
|
12
|
+
#
|
13
|
+
# The encryption method converts the plaintext to an array,
|
14
|
+
# each row within which comprises +width+ characters. The
|
15
|
+
# array is then transposed and each row is reversed, after
|
16
|
+
# which the array is converted back to a string.
|
17
|
+
#
|
18
|
+
# === Parameter(s)
|
19
|
+
# +plaintext+ - +String+: the text to be encrypted.
|
20
|
+
# +width+ - +Integer+: the width of each row in the intermediary
|
21
|
+
# table required for transposition.
|
22
|
+
#
|
23
|
+
# === Return Value
|
24
|
+
# +String+: the encrypted text.
|
25
|
+
#
|
26
|
+
# === Example
|
27
|
+
# Assuming +pt+ is the plaintext version of K3:
|
28
|
+
# +encrypt(encrypt(pt, 7), 84)+
|
29
|
+
# +=> # Results in K3+
|
30
|
+
def encrypt(plaintext, width)
|
31
|
+
matrix = plaintext.chars.each_slice(width).map(&:join)
|
32
|
+
matrix.each_with_index do |_, index|
|
33
|
+
matrix[index] = matrix[index].split('')
|
34
|
+
end
|
35
|
+
|
36
|
+
matrix.transpose.map(&:reverse).join('')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: decrypts text via keyed columnar transposition.
|
40
|
+
#
|
41
|
+
# The decryption method converts the ciphertext to an array,
|
42
|
+
# each row within which comprises +width+ characters. Each
|
43
|
+
# row in the array is reversed and the entire array is
|
44
|
+
# transposed, after which the array is converted back to a
|
45
|
+
# string.
|
46
|
+
#
|
47
|
+
# === Parameter(s)
|
48
|
+
# +ciphertext+ - +String+: the text to be decrypted.
|
49
|
+
# +width+ - +Integer+: the width of each row in the intermediary
|
50
|
+
# table required for transposition.
|
51
|
+
#
|
52
|
+
# === Return Value
|
53
|
+
# +String+: the decrypted text.
|
54
|
+
#
|
55
|
+
# === Example
|
56
|
+
# Assuming +K3+ is part 3 of the Kryptos sculpture:
|
57
|
+
# +decrypt(decrypt(K3, 4), 48)+
|
58
|
+
# +=> # Results in the plaintext+
|
59
|
+
def decrypt(ciphertext, width)
|
60
|
+
matrix = ciphertext.chars.each_slice(width).map(&:join)
|
61
|
+
matrix.each_with_index do |_, index|
|
62
|
+
matrix[index] = matrix[index].split('')
|
63
|
+
end
|
64
|
+
|
65
|
+
matrix.map(&:reverse).transpose.join('')
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
# Copyright:: (c) 2014 Eric Weinstein
|
5
|
+
# License:: MIT (see LICENSE)
|
6
|
+
|
7
|
+
##
|
8
|
+
# Encryption/decryption using Vigenere ciphers
|
9
|
+
# (http://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher)
|
10
|
+
module Vigenere
|
11
|
+
# Public: builds the tableau needed for encryption/decryption.
|
12
|
+
#
|
13
|
+
# === Parameter(s)
|
14
|
+
# +key1+ - +String+: the key found in each row. Optional; the default is a
|
15
|
+
# standard A-to-Z alphabet.
|
16
|
+
# +key2+ - +String+: the key that forms an acrostic down the left-hand side
|
17
|
+
# of the tableau.
|
18
|
+
#
|
19
|
+
# === Return Value
|
20
|
+
# +Array+: a two-dimensional array of characters comprising the tableau.
|
21
|
+
#
|
22
|
+
# === Example
|
23
|
+
#
|
24
|
+
# +build_table('KRYPTOS', 'PALIMPSEST')+
|
25
|
+
#
|
26
|
+
# (See +EXAMPLE_VIGENERE_TABLE+ in +krpytos.rb+ for the result.)
|
27
|
+
def build_table(key1 = alphabet.join(''), key2)
|
28
|
+
table = []
|
29
|
+
|
30
|
+
key1.upcase!
|
31
|
+
key2.upcase!
|
32
|
+
|
33
|
+
table[0] = key1.split('')
|
34
|
+
table[0] = populate_row(table[0], key1)
|
35
|
+
row = table[0]
|
36
|
+
|
37
|
+
key2.split('').each do |letter|
|
38
|
+
table << row.rotate(row.index(letter))
|
39
|
+
end
|
40
|
+
|
41
|
+
table
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: encrypts plaintext using a Vigenere cipher.
|
45
|
+
#
|
46
|
+
# For each letter in the plaintext, we loop through the first row of
|
47
|
+
# the tableau and add the corresponding character from the row we're
|
48
|
+
# currently on to the ciphertext string.
|
49
|
+
#
|
50
|
+
# === Parameter(s)
|
51
|
+
# +plaintext+ - +String+: the text to be encrypted.
|
52
|
+
# +table+ - +Array+: the table generated by +build_table+.
|
53
|
+
# +key+ - +String+: the alphabet key that forms an acrostic down the
|
54
|
+
# left-hand side of the tableau (+key2+ in +build_table+).
|
55
|
+
#
|
56
|
+
# === Return Value
|
57
|
+
# +String+: the encrypted text.
|
58
|
+
#
|
59
|
+
# === Example
|
60
|
+
#
|
61
|
+
# +encrypt('ATTACKATDAWN', EXAMPLE_VIGENERE_TABLE, 'PALIMPSEST')+
|
62
|
+
# +=> "DEUVRPGIJEKK"+
|
63
|
+
def encrypt(plaintext, table, key)
|
64
|
+
ciphertext = ''
|
65
|
+
key.upcase!
|
66
|
+
|
67
|
+
plaintext.tr('?', '').split('').each_with_index do |char, char_index|
|
68
|
+
table.first.each_with_index do |letter, letter_index|
|
69
|
+
ciphertext <<
|
70
|
+
table[char_index % key.length + 1][letter_index] if letter == char
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
ciphertext
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: decrypts plaintext using a Vigenere cipher.
|
78
|
+
#
|
79
|
+
# For each letter in the ciphertext, we loop through the tableau
|
80
|
+
# (starting at row 1) and add the corresponding character from row
|
81
|
+
# 0 to the plaintext string.
|
82
|
+
#
|
83
|
+
# === Parameter(s)
|
84
|
+
# +ciphertext+ - +String+: the text to be decrypted.
|
85
|
+
# +table+ - +Array+: the table generated by +build_table+.
|
86
|
+
# +key+ - +String+: the alphabet key that forms an acrostic down
|
87
|
+
# the left-hand side of the tableau (+key2+ in +build_table+).
|
88
|
+
#
|
89
|
+
# === Return Value
|
90
|
+
# +String+: the decrypted text.
|
91
|
+
#
|
92
|
+
# === Example
|
93
|
+
#
|
94
|
+
# +decrypt('DEUVRPGIJEKK', EXAMPLE_VIGENERE_TABLE, 'PALIMPSEST')+
|
95
|
+
# +=> "ATTACKATDAWN"+
|
96
|
+
def decrypt(ciphertext, table, key)
|
97
|
+
plaintext = ''
|
98
|
+
key.upcase!
|
99
|
+
|
100
|
+
ciphertext.tr('?', '').split('').each_with_index do |char, char_idx|
|
101
|
+
table[char_idx % key.length + 1].each_with_index do |letter, letter_idx|
|
102
|
+
plaintext << table.first[letter_idx] if letter == char
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
plaintext
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Private: builds a row in our Vigenere table.
|
112
|
+
#
|
113
|
+
# === Parameter(s)
|
114
|
+
# +row+ - +Array+: an array to which we add any alphabet letters,
|
115
|
+
# in order, that aren't part of the key (+key1+ in +build_table+).
|
116
|
+
#
|
117
|
+
# === Return Value
|
118
|
+
# +Array+: an array of characters constituting a row in the tableau.
|
119
|
+
def populate_row(row, key)
|
120
|
+
alphabet.each do |letter|
|
121
|
+
row << letter unless key.include? letter
|
122
|
+
end
|
123
|
+
|
124
|
+
row
|
125
|
+
end
|
126
|
+
|
127
|
+
# Private: generates an alphabet.
|
128
|
+
#
|
129
|
+
# === Parameter(s)
|
130
|
+
# None.
|
131
|
+
#
|
132
|
+
# === Return Value
|
133
|
+
# +Array+: an array comprising the letters of the alphabet.
|
134
|
+
def alphabet
|
135
|
+
('A'..'Z').to_a
|
136
|
+
end
|
137
|
+
end
|
data/lib/dekryptos.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Author:: Eric Weinstein
|
4
|
+
|
5
|
+
require_relative 'dekryptos/analysis'
|
6
|
+
require_relative 'dekryptos/autokey'
|
7
|
+
require_relative 'dekryptos/kryptos'
|
8
|
+
require_relative 'dekryptos/monoalphabetic'
|
9
|
+
require_relative 'dekryptos/playfair'
|
10
|
+
require_relative 'dekryptos/transposition'
|
11
|
+
require_relative 'dekryptos/vigenere'
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dekryptos
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Weinstein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-12 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Some tools that might come in handy attempting to decrypt Kryptos.
|
14
|
+
email:
|
15
|
+
- eric.q.weinstein@gmail.com
|
16
|
+
executables:
|
17
|
+
- dekrypt
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/dekryptos/analysis.rb
|
22
|
+
- lib/dekryptos/autokey.rb
|
23
|
+
- lib/dekryptos/kryptos.rb
|
24
|
+
- lib/dekryptos/monoalphabetic.rb
|
25
|
+
- lib/dekryptos/playfair.rb
|
26
|
+
- lib/dekryptos/transposition.rb
|
27
|
+
- lib/dekryptos/vigenere.rb
|
28
|
+
- lib/dekryptos.rb
|
29
|
+
- LICENSE
|
30
|
+
- README.md
|
31
|
+
- Rakefile
|
32
|
+
- bin/dekrypt
|
33
|
+
homepage: https://github.com/ericqweinstein/dekryptos
|
34
|
+
licenses:
|
35
|
+
- MIT
|
36
|
+
metadata: {}
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
requirements:
|
43
|
+
- - '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 2.0.14
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: Tools for working on the Kryptos sculpture.
|
57
|
+
test_files: []
|
58
|
+
has_rdoc:
|