-
Notifications
You must be signed in to change notification settings - Fork 3
/
bacon-crypt.rb
executable file
·219 lines (204 loc) · 7.35 KB
/
bacon-crypt.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
#!/usr/bin/env ruby
# Encoding: ISO-8859-1
##
## Base Conversion Cryptography 2014.12.4
## Copyright (c) 2013 Renato Silva
## GNU GPLv2 licensed
##
## Usage: @script.name [options], where options are:
##
## --key=FILE Use this FILE for the actions below.
## -c, --create Create a new cryptography key and save it
## to the file specified by the --key option.
##
## --encode=STRING Encode STRING and print the result.
## --decode=STRING Decode STRING and print the result.
## --decode-text=STRING Decode STRING and print the result as text.
##
## --encode-file=FILE Encode FILE and save to FILE.bacon.
## --decode-file=FILE.bacon Decode FILE.bacon and save to FILE.
##
## -v, --verbose Print progress information when encoding or
## decoding files.
## --encoding=ENCODING Use ENCODING for FILE or STRING.
## -l, --lines Add line breaks to encoded text.
## -h, --help This help text.
##
require 'base64'
require 'easyoptions'
options = EasyOptions.options
class String
def scan_digits(length = BiggestBase.length, digit_base = 10, leading_zero = false, fixed_length = true)
offset = leading_zero ? 0 : 1
length = rand(2..length) if length > 2 && !fixed_length
digits = scan(/(.{#{length - offset}}|.{1,#{length - offset}}$)/)
non_zero = leading_zero ? '' : '1'
digits.map { |item| "#{non_zero}#{item[0]}".to_i(digit_base) }
end
def zerofill(length = nil)
length = BiggestBase.length unless length
rjust(length, '0')
end
def decode64
array = Base64.decode64(self).unpack('U*')
Progress.done('Decoded from Base64')
array
end
end
class Array
def zerofill(length = nil)
map { |item| item.to_s.zerofill(length) }.join
end
def encode64
base64 = EasyOptions.options[:lines].nil? ? Base64.strict_encode64(pack('U*')) : Base64.encode64(pack('U*'))
Progress.done('Encoded to Base64')
base64
end
end
class Progress
def initialize(message, maximum)
@message = message
@maximum = maximum
@step = 0
end
def self.done(message)
$stderr.puts("#{message}.") if EasyOptions.options[:verbose]
end
def next
return unless EasyOptions.options[:verbose]
@step += 1
percentage = (100 * @step) / @maximum
previous_percentage = (100 * (@step - 1)) / @maximum
$stderr.print("\r#{@message}... #{percentage} % ") if percentage != previous_percentage
$stderr.print("\r#{' ' * (@message.length + 10)}\r") if @step == @maximum
end
end
class Base
def initialize(base, alphabet = (0..(base - 1)).to_a.shuffle)
@value, @alphabet = base, alphabet
end
def to_this(integer)
result = []
begin
result << @alphabet[integer % @value]
integer = integer / @value
end until integer == 0
Progress.done('Converted from base 10')
result.reverse
end
def to_10(digits)
result = 0
progress = Progress.new('Converting to base 10', digits.length)
digits.reverse.each_with_index do |digit, index|
digit_value = @alphabet.index(digit)
result += digit_value * (@value**index)
progress.next
end
Progress.done('Converted to base 10')
result
end
def length(base = 10)
Math.log(value, base).ceil
end
def bitlength
Math.log2(value)
end
def to_s
hexa_alphabet = @alphabet.map { |digit| digit.to_s(16).upcase }
"#{@value.to_s.zerofill}=#{hexa_alphabet.zerofill(length(16))}"
end
attr_accessor :value
attr_accessor :alphabet
end
DecodedBases = (2**14)..(2**16) # 16384..65536
EncodedBases = (2**10)..(2**12) # 1024..4096
BiggestBase = Base.new([DecodedBases.max, EncodedBases.max].max)
class EncryptionKey
def initialize(file_path = nil)
if file_path.nil?
@decoded = Base.new(rand(DecodedBases))
@encoded = Base.new(rand(EncodedBases))
else
lines = File.readlines(file_path)
@decoded = parse_line(lines[0])
@encoded = parse_line(lines[1])
end
end
def parse_line(line)
columns = line.split('=')
base = Base.new(columns[0].to_i)
base.alphabet = columns[1].scan_digits(base.length(16), 16, true)
base
end
def to_s
"#{@decoded}\n#{@encoded}"
end
attr_accessor :decoded
attr_accessor :encoded
end
class BaseCrypt
def initialize(file_path = nil)
@key = EncryptionKey.new(file_path)
end
def encode(bytes)
digits = to_digits(bytes)
integer = @key.decoded.to_10(digits)
digits = @key.encoded.to_this(integer)
digits.encode64
end
def decode(string)
digits = string.decode64
integer = @key.encoded.to_10(digits)
digits = @key.decoded.to_this(integer)
to_bytes(digits).pack('C*')
end
def to_digits(bytes)
bits = bytes.map { |byte| byte.to_s(2).zerofill(8) }.join
digits = bits.scan_digits(@key.decoded.bitlength.floor, 2, false, false)
Progress.done('Converted from bytes to digits')
digits
end
def to_bytes(digits)
bin_digits = digits.map { |digit| digit.to_s(2)[1..-1] }
bytes = bin_digits.join.scan_digits(8, 2, true)
Progress.done('Converted from digits to bytes')
bytes
end
attr_accessor :key
end
if options.empty?
puts EasyOptions.documentation
exit
end
EasyOptions.finish('--key is required') unless options[:key]
EasyOptions.finish('cannot decode while creating key') if options[:create] && (options[:decode] || options[:decode_file])
EasyOptions.finish('cannot specify multiple encoding actions') if [:encode, :encode_file].find_all { |option| !options[option].nil? }.length > 1
EasyOptions.finish('cannot specify multiple decoding actions') if [:decode, :decode_file, :decode_text].find_all { |option| !options[option].nil? }.length > 1
EasyOptions.finish('encoded file must have the bacon extension') if options[:decode_file] && !options[:decode_file].end_with?('.bacon')
options[:decode] = options[:decode_text] if options[:decode_text]
options[:encode] = File.open(options[:encode_file], 'rb') { |io| io.read } if options[:encode_file]
options[:decode] = File.open(options[:decode_file], 'rb') { |io| io.read } if options[:decode_file]
if options[:create]
bc = BaseCrypt.new
file = File.open(options[:key], 'w')
file.puts(bc.key)
file.close
end
if options[:encode]
bc = BaseCrypt.new(options[:key])
options[:encode].force_encoding(options[:encoding]) if options[:encoding]
ciphertext = bc.encode(options[:encode].bytes)
$stdout = File.open("#{options[:encode_file]}.bacon", 'w') if options[:encode_file]
$stdout.puts ciphertext
end
if options[:decode]
bc = BaseCrypt.new(options[:key])
decoded = bc.decode(options[:decode])
decoded.force_encoding(options[:encoding]) if options[:encoding]
$stdout = File.open(options[:decode_file].sub(/\.bacon$/, ''), 'wb') if options[:decode_file]
if options[:decode_text]
$stdout.puts(decoded)
else
$stdout.binmode.write(decoded)
end
end