mack-encryption 0.8.1 → 0.8.2
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.
- data/lib/gems/ezcrypto-0.7/lib/active_crypto.rb +313 -0
- data/lib/gems/ezcrypto-0.7/lib/ezcrypto.rb +592 -0
- data/lib/gems/ezcrypto-0.7/lib/ezsig.rb +535 -0
- data/lib/gems/ezcrypto-0.7/lib/trusted.pem +2304 -0
- data/lib/gems.rb +18 -0
- data/lib/mack-encryption.rb +3 -0
- metadata +20 -16
@@ -0,0 +1,592 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'digest/sha2'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module EzCrypto #:nodoc:
|
7
|
+
|
8
|
+
|
9
|
+
=begin rdoc
|
10
|
+
The Key is the only class you need to understand for simple use.
|
11
|
+
|
12
|
+
=== Algorithms
|
13
|
+
|
14
|
+
The crypto algorithms default to aes-128-cbc however on any of the class methods you can change it to one of the standard openssl cipher names using the
|
15
|
+
optional <tt>:algorithm=>alg name</tt> parameter.
|
16
|
+
|
17
|
+
Eg.
|
18
|
+
Key.new @raw, :algorithm=>"des"
|
19
|
+
Key.generate :algorithm=>"blowfish"
|
20
|
+
Key.with_password @pwd,@salt,:algorithm=>"aes256"
|
21
|
+
|
22
|
+
|
23
|
+
== License
|
24
|
+
|
25
|
+
ActiveCrypto and EzCrypto are released under the MIT license.
|
26
|
+
|
27
|
+
|
28
|
+
== Support
|
29
|
+
|
30
|
+
To contact the author, send mail to pelleb@gmail.com
|
31
|
+
|
32
|
+
Also see my blogs at:
|
33
|
+
http://stakeventures.com and
|
34
|
+
http://neubia.com
|
35
|
+
|
36
|
+
This project was based on code used in my project StakeItOut, where you can securely share web services with your partners.
|
37
|
+
https://stakeitout.com
|
38
|
+
|
39
|
+
(C) 2005 Pelle Braendgaard
|
40
|
+
|
41
|
+
=end
|
42
|
+
|
43
|
+
class Key
|
44
|
+
|
45
|
+
attr_reader :raw, :algorithm
|
46
|
+
|
47
|
+
@@block_size = 512
|
48
|
+
|
49
|
+
=begin rdoc
|
50
|
+
Set the block-size for IO-operations (default: 512 bytes)
|
51
|
+
=end
|
52
|
+
def self.block_size=(size)
|
53
|
+
@@block_size = size
|
54
|
+
end
|
55
|
+
|
56
|
+
=begin rdoc
|
57
|
+
Return the block-size for IO-operations.
|
58
|
+
=end
|
59
|
+
def self.block_size
|
60
|
+
@@block_size
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
=begin rdoc
|
65
|
+
Initialize the key with raw unencoded binary key data. This needs to be at least
|
66
|
+
16 bytes long for the default aes-128 algorithm.
|
67
|
+
=end
|
68
|
+
def initialize(raw,options = {})
|
69
|
+
@raw=raw
|
70
|
+
@algorithm=options[:algorithm]||"aes-128-cbc"
|
71
|
+
end
|
72
|
+
|
73
|
+
=begin rdoc
|
74
|
+
Generate random key.
|
75
|
+
=end
|
76
|
+
def self.generate(options = {})
|
77
|
+
Key.new(EzCrypto::Digester.generate_key(calculate_key_size(options[:algorithm])),options)
|
78
|
+
end
|
79
|
+
|
80
|
+
=begin rdoc
|
81
|
+
Create key generated from the given password and salt
|
82
|
+
=end
|
83
|
+
def self.with_password(password,salt,options = {})
|
84
|
+
Key.new(EzCrypto::Digester.get_key(password,salt,calculate_key_size(options[:algorithm])),options)
|
85
|
+
end
|
86
|
+
|
87
|
+
=begin rdoc
|
88
|
+
Initialize the key with Base64 encoded key data.
|
89
|
+
=end
|
90
|
+
def self.decode(encoded,options = {})
|
91
|
+
Key.new(Base64.decode64(encoded),options)
|
92
|
+
end
|
93
|
+
|
94
|
+
=begin rdoc
|
95
|
+
Encrypts the data with the given password and a salt. Short hand for:
|
96
|
+
|
97
|
+
key=Key.with_password(password,salt,options)
|
98
|
+
key.encrypt(data)
|
99
|
+
|
100
|
+
=end
|
101
|
+
def self.encrypt_with_password(password,salt,data,options = {})
|
102
|
+
key=Key.with_password(password,salt,options)
|
103
|
+
key.encrypt(data)
|
104
|
+
end
|
105
|
+
|
106
|
+
=begin rdoc
|
107
|
+
Decrypts the data with the given password and a salt. Short hand for:
|
108
|
+
|
109
|
+
key=Key.with_password(password,salt,options)
|
110
|
+
key.decrypt(data)
|
111
|
+
|
112
|
+
|
113
|
+
=end
|
114
|
+
def self.decrypt_with_password(password,salt,data,options = {})
|
115
|
+
key=Key.with_password(password,salt,options)
|
116
|
+
key.decrypt(data)
|
117
|
+
end
|
118
|
+
|
119
|
+
=begin rdoc
|
120
|
+
Given an algorithm this calculates the keysize. This is used by both
|
121
|
+
the generate and with_password methods. This is not yet 100% complete.
|
122
|
+
=end
|
123
|
+
def self.calculate_key_size(algorithm)
|
124
|
+
if !algorithm.nil?
|
125
|
+
algorithm=~/^([[:alnum:]]+)(-(\d+))?/
|
126
|
+
if $3
|
127
|
+
size=($3.to_i)/8
|
128
|
+
else
|
129
|
+
case $1
|
130
|
+
when "bf"
|
131
|
+
size = 16
|
132
|
+
when "blowfish"
|
133
|
+
size = 16
|
134
|
+
when "des"
|
135
|
+
size = 8
|
136
|
+
when "des3"
|
137
|
+
size = 24
|
138
|
+
when "aes128"
|
139
|
+
size = 16
|
140
|
+
when "aes192"
|
141
|
+
size = 24
|
142
|
+
when "aes256"
|
143
|
+
size = 32
|
144
|
+
when "rc2"
|
145
|
+
size = 16
|
146
|
+
when "rc4"
|
147
|
+
size = 16
|
148
|
+
else
|
149
|
+
size = 16
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
if size.nil?
|
154
|
+
size = 16
|
155
|
+
end
|
156
|
+
|
157
|
+
size
|
158
|
+
end
|
159
|
+
|
160
|
+
=begin rdoc
|
161
|
+
returns the Base64 encoded key.
|
162
|
+
=end
|
163
|
+
def encode
|
164
|
+
Base64.encode64(@raw).chop
|
165
|
+
end
|
166
|
+
|
167
|
+
=begin rdoc
|
168
|
+
returns the Base64 encoded key. Synonym for encode.
|
169
|
+
=end
|
170
|
+
def to_s
|
171
|
+
encode
|
172
|
+
end
|
173
|
+
|
174
|
+
=begin rdoc
|
175
|
+
Encrypts the data and returns it in encrypted binary form.
|
176
|
+
=end
|
177
|
+
def encrypt(data)
|
178
|
+
if data==nil || data==""
|
179
|
+
nil
|
180
|
+
else
|
181
|
+
encrypter("")
|
182
|
+
@cipher.encrypt(data)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
=begin rdoc
|
187
|
+
Encrypts the data and returns it in encrypted Base64 encoded form.
|
188
|
+
=end
|
189
|
+
def encrypt64(data)
|
190
|
+
Base64.encode64(encrypt(data))
|
191
|
+
end
|
192
|
+
|
193
|
+
=begin rdoc
|
194
|
+
Decrypts the data passed to it in binary format.
|
195
|
+
=end
|
196
|
+
def decrypt(data)
|
197
|
+
if data==nil || data==""
|
198
|
+
nil
|
199
|
+
else
|
200
|
+
decrypter("")
|
201
|
+
@cipher.gulp(data)
|
202
|
+
end
|
203
|
+
# rescue
|
204
|
+
# puts @algorithm
|
205
|
+
# puts self.encode
|
206
|
+
# puts data.size
|
207
|
+
# throw $!
|
208
|
+
end
|
209
|
+
|
210
|
+
=begin rdoc
|
211
|
+
Decrypts a Base64 formatted string
|
212
|
+
=end
|
213
|
+
def decrypt64(data)
|
214
|
+
decrypt(Base64.decode64(data))
|
215
|
+
end
|
216
|
+
|
217
|
+
=begin rdoc
|
218
|
+
Allows keys to be marshalled
|
219
|
+
=end
|
220
|
+
def marshal_dump
|
221
|
+
"#{self.algorithm}$$$#{self.encode}"
|
222
|
+
end
|
223
|
+
|
224
|
+
=begin rdoc
|
225
|
+
Allows keys to be unmarshalled
|
226
|
+
=end
|
227
|
+
def marshal_load(s)
|
228
|
+
a, r = s.split '$$$'
|
229
|
+
@algorithm = a
|
230
|
+
@raw = Base64.decode64(r)
|
231
|
+
end
|
232
|
+
|
233
|
+
=begin rdoc
|
234
|
+
Create a file with minimal permissions, and yield
|
235
|
+
it to a block. By default only the owner may read it.
|
236
|
+
=end
|
237
|
+
def safe_create(file, mod=0400, mask=0066)
|
238
|
+
begin
|
239
|
+
old_umask = File.umask
|
240
|
+
File.umask(mask)
|
241
|
+
File.open(file, File::CREAT | File::EXCL | File::WRONLY) do |f|
|
242
|
+
yield(f) if block_given?
|
243
|
+
end
|
244
|
+
ensure
|
245
|
+
File.umask(old_umask)
|
246
|
+
end
|
247
|
+
File.chmod(mod, file) if File.exists?(file)
|
248
|
+
File.size(file)
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
=begin rdoc
|
253
|
+
Overwrite a file with zero values before removing it's filesystem inode.
|
254
|
+
This is not very safe :-)
|
255
|
+
=end
|
256
|
+
def safe_delete(file)
|
257
|
+
begin
|
258
|
+
to_clear = File.size(file)
|
259
|
+
zeroes = Array.new(Key.block_size, "\0").join
|
260
|
+
File.chmod(0600, file)
|
261
|
+
File.open(file, File::WRONLY) do |f|
|
262
|
+
f.rewind
|
263
|
+
while to_clear > 0
|
264
|
+
f.write(zeroes)
|
265
|
+
to_clear -= Key.block_size
|
266
|
+
end
|
267
|
+
f.flush
|
268
|
+
end
|
269
|
+
ensure
|
270
|
+
File.delete(file) if File.exists?(file)
|
271
|
+
end
|
272
|
+
return !File.exists?(file)
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
=begin rdoc
|
277
|
+
Open a file readonly and yield it to a block.
|
278
|
+
=end
|
279
|
+
def safe_read(file)
|
280
|
+
File.open(file, File::RDONLY) do |i|
|
281
|
+
yield(i) if block_given?
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
private :safe_create, :safe_read, :safe_delete
|
286
|
+
|
287
|
+
=begin rdoc
|
288
|
+
Load a key from a yaml_file generated via Key#store.
|
289
|
+
=end
|
290
|
+
def self.load(filename)
|
291
|
+
require 'yaml'
|
292
|
+
hash = YAML::load_file(filename)
|
293
|
+
req = proc { |k| hash[k] or raise "Missing element #{k} in #{filename}" }
|
294
|
+
key = self.new Base64.decode64(req.call(:key)) , :algorithm => req.call(:algorithm)
|
295
|
+
return key
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
=begin rdoc
|
300
|
+
Save the key data into a file, try to do this in a secure manner.
|
301
|
+
NOTE: YAML::store & friends are not used to encance control over
|
302
|
+
the generated file format.
|
303
|
+
=end
|
304
|
+
def store(filename)
|
305
|
+
safe_create(filename) do |f|
|
306
|
+
selfenc = self.encode
|
307
|
+
f.puts "---"
|
308
|
+
f.puts ":EZCRYPTO KEY FILE: KEEP THIS SECURE !"
|
309
|
+
f.puts ":created: #{Time.now}"
|
310
|
+
f.puts ":algorithm: #{@algorithm}"
|
311
|
+
f.puts ":key: #{selfenc}"
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
=begin rdoc
|
317
|
+
Get a Encrypter object. You have to call #final on it by yourself!
|
318
|
+
=end
|
319
|
+
def encrypter(target='')
|
320
|
+
@cipher = EzCrypto::Encrypter.new(self,target,@algorithm)
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
=begin rdoc
|
325
|
+
Get a Decrypter object. You have to call #final on it by yourself!
|
326
|
+
=end
|
327
|
+
def decrypter(target='')
|
328
|
+
@cipher = EzCrypto::Decrypter.new(self,target,@algorithm)
|
329
|
+
end
|
330
|
+
|
331
|
+
=begin rdoc
|
332
|
+
Create a Decrypter object and yield it to a block.
|
333
|
+
You must *not* call #final by yourself, the method
|
334
|
+
does this.
|
335
|
+
=end
|
336
|
+
def on_decrypter(target='', &block)
|
337
|
+
decrypter(target)
|
338
|
+
on_cipher(&block)
|
339
|
+
end
|
340
|
+
|
341
|
+
=begin rdoc
|
342
|
+
Create an Encrypter object and yield it to a block.
|
343
|
+
You must *not* call #final by yourself, the method
|
344
|
+
does this.
|
345
|
+
=end
|
346
|
+
def on_encrypter(target='', &block)
|
347
|
+
encrypter(target)
|
348
|
+
on_cipher(&block)
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
=begin rdoc
|
353
|
+
Helper method, yields the current cipher to a block.
|
354
|
+
=end
|
355
|
+
def on_cipher(&block)
|
356
|
+
begin
|
357
|
+
block.call @cipher
|
358
|
+
ensure
|
359
|
+
@cipher.final
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
private :on_cipher
|
364
|
+
|
365
|
+
|
366
|
+
=begin rdoc
|
367
|
+
Encrypt a file 'inplace' and add a suffix
|
368
|
+
see #cipher_file.
|
369
|
+
IMPORTANT: The inputfile will be deleted by default.
|
370
|
+
=end
|
371
|
+
def encrypt_file(src, tgt=nil, options = {} )
|
372
|
+
options = { :suffix => '.ez', :autoclean => 'true' }.update(options)
|
373
|
+
tgt = "#{src}#{options[:suffix]}" unless tgt
|
374
|
+
cipher_file :on_encrypter, src, tgt, options[:autoclean]
|
375
|
+
end
|
376
|
+
|
377
|
+
|
378
|
+
=begin rdoc
|
379
|
+
Decrypt a file 'inplace' and remove a suffix
|
380
|
+
see #cipher_file
|
381
|
+
IMPORTANT: The inputfile will be deleted by default.
|
382
|
+
=end
|
383
|
+
def decrypt_file(src, tgt=nil, options = {} )
|
384
|
+
options = { :suffix => '.ez', :autoclean => 'true' }.update(options)
|
385
|
+
unless tgt
|
386
|
+
tgt = src
|
387
|
+
tgt = tgt.gsub(/#{options[:suffix]}$/, '')
|
388
|
+
end
|
389
|
+
cipher_file :on_decrypter, src, tgt, options[:autoclean]
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
=begin rdoc
|
394
|
+
uses either #on_decrypter or #on_encrypter to transform a given sourcefile
|
395
|
+
to a targetfile. the sourcefile is deleted after sucessful transformation
|
396
|
+
the delete_source is 'true'.
|
397
|
+
=end
|
398
|
+
def cipher_file(method, sourcefile, targetfile, delete_source)
|
399
|
+
raise(ArgumentError, "source == target #{sourcefile}") if sourcefile == targetfile
|
400
|
+
safe_create(targetfile,0600) do |o|
|
401
|
+
self.send(method, o) do |c|
|
402
|
+
safe_read(sourcefile) do |i|
|
403
|
+
loop do
|
404
|
+
buffer = i.read(Key.block_size) or break
|
405
|
+
c << buffer
|
406
|
+
end
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
safe_delete(sourcefile) if delete_source && File.exists?(targetfile)
|
411
|
+
return targetfile
|
412
|
+
end
|
413
|
+
|
414
|
+
private :cipher_file
|
415
|
+
|
416
|
+
end
|
417
|
+
|
418
|
+
=begin rdoc
|
419
|
+
Abstract Wrapper around OpenSSL's Cipher object. Extended by Encrypter and Decrypter.
|
420
|
+
|
421
|
+
You probably should be using the Key class instead.
|
422
|
+
|
423
|
+
Warning! The interface may change.
|
424
|
+
|
425
|
+
=end
|
426
|
+
class CipherWrapper #:nodoc:
|
427
|
+
|
428
|
+
=begin rdoc
|
429
|
+
|
430
|
+
=end
|
431
|
+
def initialize(key,target,mode,algorithm)
|
432
|
+
@cipher = OpenSSL::Cipher::Cipher.new(algorithm)
|
433
|
+
if mode
|
434
|
+
@cipher.encrypt
|
435
|
+
else
|
436
|
+
@cipher.decrypt
|
437
|
+
end
|
438
|
+
@cipher.key=key.raw
|
439
|
+
@cipher.padding=1
|
440
|
+
@target=target
|
441
|
+
@finished=false
|
442
|
+
end
|
443
|
+
|
444
|
+
|
445
|
+
def to_target(data)
|
446
|
+
return data
|
447
|
+
end
|
448
|
+
|
449
|
+
=begin rdoc
|
450
|
+
Process the givend data with the cipher.
|
451
|
+
=end
|
452
|
+
def update(data)
|
453
|
+
reset if @finished
|
454
|
+
@target<< to_target(@cipher.update(data))
|
455
|
+
end
|
456
|
+
|
457
|
+
=begin rdoc
|
458
|
+
|
459
|
+
=end
|
460
|
+
def <<(data)
|
461
|
+
update(data)
|
462
|
+
end
|
463
|
+
|
464
|
+
=begin rdoc
|
465
|
+
Finishes up any last bits of data in the cipher and returns the final result.
|
466
|
+
=end
|
467
|
+
def final
|
468
|
+
@target<< to_target(@cipher.final)
|
469
|
+
@finished=true
|
470
|
+
@target
|
471
|
+
end
|
472
|
+
|
473
|
+
=begin rdoc
|
474
|
+
Processes the entire data string using update and performs a final on it returning the data.
|
475
|
+
=end
|
476
|
+
def gulp(data)
|
477
|
+
update(data)
|
478
|
+
final
|
479
|
+
# rescue
|
480
|
+
# breakpoint
|
481
|
+
end
|
482
|
+
|
483
|
+
=begin rdoc
|
484
|
+
|
485
|
+
=end
|
486
|
+
def reset(target="")
|
487
|
+
@target=target
|
488
|
+
@finished=false
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
=begin rdoc
|
493
|
+
Wrapper around OpenSSL Cipher for Encryption use.
|
494
|
+
|
495
|
+
You probably should be using Key instead.
|
496
|
+
|
497
|
+
Warning! The interface may change.
|
498
|
+
|
499
|
+
=end
|
500
|
+
class Encrypter<EzCrypto::CipherWrapper #:nodoc:
|
501
|
+
|
502
|
+
=begin rdoc
|
503
|
+
|
504
|
+
=end
|
505
|
+
def initialize(key,target="",algorithm="aes-128-cbc")
|
506
|
+
super(key,target,true,algorithm)
|
507
|
+
end
|
508
|
+
|
509
|
+
=begin rdoc
|
510
|
+
|
511
|
+
=end
|
512
|
+
def encrypt(data)
|
513
|
+
gulp(data)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
=begin rdoc
|
518
|
+
Wrapper around OpenSSL Cipher for Decryption use.
|
519
|
+
|
520
|
+
You probably should be using Key instead.
|
521
|
+
|
522
|
+
Warning! The interface may change.
|
523
|
+
=end
|
524
|
+
class Decrypter<EzCrypto::CipherWrapper #:nodoc:
|
525
|
+
=begin rdoc
|
526
|
+
|
527
|
+
=end
|
528
|
+
def initialize(key,target="",algorithm="aes-128-cbc")
|
529
|
+
super(key,target,false,algorithm)
|
530
|
+
end
|
531
|
+
|
532
|
+
=begin rdoc
|
533
|
+
|
534
|
+
=end
|
535
|
+
def decrypt(data)
|
536
|
+
gulp(data)
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
=begin rdoc
|
541
|
+
|
542
|
+
=end
|
543
|
+
class Digester
|
544
|
+
=begin rdoc
|
545
|
+
Various handy Digest methods.
|
546
|
+
|
547
|
+
Warning! The interface may change.
|
548
|
+
=end
|
549
|
+
def self.get_key(password,salt,size)
|
550
|
+
digest(salt+password,size)
|
551
|
+
end
|
552
|
+
|
553
|
+
=begin rdoc
|
554
|
+
|
555
|
+
=end
|
556
|
+
def self.generate_key(size=16)
|
557
|
+
key=OpenSSL::Random.random_bytes(size)
|
558
|
+
digest(key,size)
|
559
|
+
end
|
560
|
+
|
561
|
+
=begin rdoc
|
562
|
+
|
563
|
+
=end
|
564
|
+
def self.generate_key64(size=32)
|
565
|
+
key=OpenSSL::Random.random_bytes(size)
|
566
|
+
digest(key,size)
|
567
|
+
end
|
568
|
+
|
569
|
+
=begin rdoc
|
570
|
+
|
571
|
+
=end
|
572
|
+
def self.digest(data,size=16)
|
573
|
+
if size==0
|
574
|
+
""
|
575
|
+
elsif size<=16
|
576
|
+
Digest::SHA1.digest(data)[0..(size-1)]
|
577
|
+
else
|
578
|
+
Digest::SHA256.digest(data)[0..(size-1)]
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
=begin rdoc
|
583
|
+
|
584
|
+
=end
|
585
|
+
def self.digest64(data)
|
586
|
+
Base64.encode64(digest(data))
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
end
|
591
|
+
|
592
|
+
|