/
decryptOTP.rb
270 lines (233 loc) · 8.24 KB
/
decryptOTP.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
$togDebug=false
#---Hex operations
def hex_str_to_char_arr(str)
temp_arr=[]
str.gsub(/([0-9a-zA-Z]{2})/) {|f| temp_arr<<f}
return temp_arr
end
def hex_to_asc(hex_code) #hex code string (i.e. "20" for space) as input
hex_code.hex.chr
end
#--Array operations
def combine_arrs(arr)
all_arrs=[]
arr.each do |a|
all_arrs<<hex_str_to_char_arr(a)
end
return all_arrs
end
def and_arrays(arr_coll) #takes collection of arrays as input, outputs AND of all those arrays
max_len=0
arr_coll.each do |el| #find length of longest array in collection of arrays
if el.length > max_len
max_len = el.length
end
end
result = arr_coll[0][0..(max_len-1)]
arr_coll[1..arr_coll.length].each do |arr|
arr[0..max_len-1].each_with_index do |el, ind|
if el != result[ind]
result[ind]=0
end
end
end
return result
end
#Xor functions
def xor_hex(char1,char2)
(char1.hex ^ char2.hex).to_s(16)
end
def xor_array_els_as_hex(a,b) #input two arrays each with hex characters as elements
xoredarr=[]
if a.length > b.length || a.length == b.length
for i in 0..b.length-1
xored = (a[i].hex ^ b[i].hex).to_s(16)
xoredarr<<xored
end
else b.length > a.length
for i in 0..a.length-1
xored = (a[i].hex ^ b[i].hex).to_s(16)
xoredarr<<xored
end
end
return xoredarr
end
#Functions for analysis of ciphertexts
def find_shifted_case(orig,final) #input arrays of same length and with ascii characters
shifted_case=Array.new(orig.length,0)
orig.each_with_index do |el, i|
el=hex_to_asc(el)
fin=hex_to_asc(final[i])
if (el == fin.downcase) || (fin == el.downcase) #if original element matches upper or lowercase of final element in same position
shifted_case[i]=1 #mark as shift in case
end
end
return shifted_case
end
def build_key_from_spaces(spaces_array, cts_array) #array of arrays showing spaces in cts
key=[]
spaces_array.each_with_index do |arr, arr_ind|
arr.each_with_index do |el, el_ind|
if el==1 #i.e. there is a space there
#xor hex space (20) with the appropriate ct element --> that is the key value for that index (s ^ k ^ s = k)
key[el_ind] = ("20".hex ^ cts_array[arr_ind][el_ind].hex).to_s(16)
end
end
end
return key
end
def decode(ct, key, debug=false) #given ciphertext array and key array, output plaintext as ascii string
pt=[]
ct.each_with_index do |el, ind|
if ind>98
sp=" "
else
sp=" "
end
if !key[ind].nil?
pt << (el.hex ^ key[ind].hex).chr + sp
else
pt << "."+sp
end
end
if debug
pt=pt.join("|")
else
pt=pt.join("")
end
end
def decode_cts(cts_array,key,debug) #use decode method to do whole set of ciphertexts
i=1
cts_array.each do |arr|
pt=decode(arr,key,debug)
if debug
j=0
print "\n"
print "ct #{i}: "
arr.length.times.reject {|el| el==0}.each do |num|
if num < 10
print "#{num} |" #add a space to preserve alignment with double digit stuff
else
print "#{num}|"
end
end
print "\n"
end
puts "ct #{i}: #{pt}"
i += 1
end
end
def set_key_val(ct_arr,ct_num, key, index, pt_value) #set specific key values
if pt_value.length ==1
key[index]=xor_hex(pt_value.ord.to_s(16),ct_arr[ct_num-1][index])
else #add a few letters in a row from a given starting point
n=0
pt_value.chars.each do |ltr|
key[index+n]=xor_hex(ltr.ord.to_s(16),ct_arr[ct_num-1][index+n])
n=n+1
end
end
end
def main_key_build_run(ct_array, ct_tar_arr, debug=$togDebug)
spaces_arrays=[]
#for one given ct......
ct_array.each_with_index do |curr_ct_1,k|
shifts_array_for_indiv_ct = []
#....xor one at a time with another ct --> result is m1^ m2 (but don't compare against self because xor would just be 0)
ct_array.each_with_index.reject { |el,i| i == (ct_array.index(curr_ct_1)) }.each_with_index do |curr_ct_2,j|
curr_ct_2=curr_ct_2[0]
xor12 = xor_array_els_as_hex(curr_ct_1,curr_ct_2)
#Then xor m1^m2 with an array of spaces (hex 20)
arr_spaces=Array.new(xor12.length,"20")
xored_w_sp=xor_array_els_as_hex(xor12,arr_spaces) # --> m1^m2^s
#then compare m1^m2 and m1^m2^s to see if any hex values correspond to a shift in case
#if so, that means m1 is a space -- s^m2 would result in shift in case (a-->A) and then s^s^m2 would shift it back
a=find_shifted_case(xor12,xored_w_sp)
#store that comparison between two cts
shifts_array_for_indiv_ct << a
end
#after comparing one ct against all others, if one position consistently shifted register when xored with space (hex 20) then
#it is most likely a space in that first ct --> we can find that by ANDing all arrays together
spaces_in_ct=and_arrays(shifts_array_for_indiv_ct)
#store an array showing likely spaces for an invividual ct into a larger array that will hold individual arrays for each ct
spaces_arrays<<spaces_in_ct
#do this all over again but with the next ct as the main comparison
end
#after all cts have been analyzed, build the key using that data
#this is done by taking the array showing where spaces are in each plaintext, and where each space is, xor that ct (hex) value with
#the value for space (hex 20), and that is the key value for that character position, i.e. where m1=s (m1^k^s=s^k^s=k)
key=build_key_from_spaces(spaces_arrays, ct_array)
return key
end
def print_decoded_cts(key, ct_array, ct_tar_arr, debug=false)
puts "key = #{key}"
#try and decode target
decoded_tar = decode(ct_tar_arr, key)
puts "targ: #{decoded_tar}"
decode_cts(ct_array,key,debug)
end
def pop_array(file)
arr=[]
File.open(file).each_line { |s| arr << s }
arr
end
def process_inp(input, ct_arr, ct_tar_arr, key) #matches input (i.e. "1,5, cipher") to fill text "cipher" starting at position 5 on ct# 1
ct_arr=[ct_tar_arr]+ct_arr
ct_num, pos, txtval = input.split(/,[ ]*/)
if ct_num.nil? || pos.nil? || txtval.nil?
puts "improper input; should be ct_num,position,value"
else
if ct_num == ("tar" || "t")
ct_num=0
else
ct_num=ct_num.to_i+1
end
set_key_val(ct_arr,ct_num, key, (pos.to_i-1), txtval)
end
end
if __FILE__ == $0 #run this if executed from command line
if ARGV.empty?
puts %Q{\nProper usage is:\n\n '#{$0} cts_file'\n\nwhere cts_file is a file containing all ciphertexts, separated by a blank line}
puts %Q{\nFrom there, the ciphertexts are analyzed and you can fill in the remaining blanks using the super fancy method of:\n\n txt_num,pos,val\n\n}
puts %Q{for example: 1,1,X to change the first character in the first text to X -- all changes are then replicated across other texts}
Process.exit
end
#load cts in file, each separated by blank line, with target as last line (actually doesn't matter bc all cts will be decrypted)
ct_file = ARGV[0]
ct_inp=File.open(ct_file).read.split("\n").reject { |el| el==""}
ct_strs=ct_inp[0..ct_inp.length-2]
ct_tar=ct_inp[ct_inp.length-1]
#convert ct_strs to array of arrays containing each hex code (2 digits) in an array
ct_array=combine_arrs(ct_strs) # i.e. ["asdflk","asdkfjsaldj"]->[["as","df","lk"],["as",....]]
ct_tar_arr=hex_str_to_char_arr(ct_tar) # same, but only one array
#do analysis, etc. up to and including building the key
key=main_key_build_run(ct_array, ct_tar_arr)
ct_arr=combine_arrs(ct_strs)
#then we enter loop where once can:
while true
print_decoded_cts(key,ct_arr,ct_tar_arr,$togDebug)
inp = $stdin.gets.chomp
case inp
# 1) toggle between debug and not
when "D"
if $togDebug
$togDebug=false
else
$togDebug=true
end
# 2) quit
when "Q"
Process.exit
else
begin
# 3) add our own observations to help build the key
process_inp(inp, ct_arr, ct_tar_arr,key)
rescue => msg
puts "Something went wrong: #{msg}"
end
end
end
# there is also and undo function
end
#Best computed guess before filling in blanks mentally:
#targ: T h e s e c r e t m e s s a g e i s : W h e . . . s . . . a s t r e a m c i p h e r , n e . . . . . s e t h e k . y m . r e . t h a n . n . .