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 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: