mack-encryption 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+