lenc 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4b56f1960462bf9fd9382ff2059182a881e51dd
4
- data.tar.gz: 0cb704fcc7bda5e7a326202be609027d1cb7edfa
3
+ metadata.gz: 81e06c0955051deca4de3a4687debbed0d38ab2e
4
+ data.tar.gz: 38b7cf28b8c71b99711d440a27dc7535fc82ed02
5
5
  SHA512:
6
- metadata.gz: 41948757b4d963f3b7cc23f6c8292e8ce51b918b2f1860087cb34c3d95765f6b82900fee46c47adb399b01cb6050a09761c6d56caf3c921badbb542263f45a1a
7
- data.tar.gz: da5a5af9488d3901d29b71c29556df4c343f0dfa82c882556dad404ef24148e593e046d022604abef1cb2b4859c832fa49b41695d1d20fbb4e49bcf7fb3ad038
6
+ metadata.gz: be50fb30cf452036677c901f05c5eaa81db9fcc6b071a2029ec87f11b4c89d9f9ff39606e97ec428720e304a2a02188253abadeea8db67cc9363ddfa22377011
7
+ data.tar.gz: c5e73819b059507bffa9aac25dbb7c22f21a256d278898e1139285627609a80c98b8a7bda407663e8d54c4a8ba2e63a1c0433c0ee8bcb777a966bc17b1ac6024
data/README.md CHANGED
@@ -5,6 +5,9 @@ Lenc
5
5
  LEnc is a Ruby gem that maintains encrypted repositories of files, enabling secure, encrypted
6
6
  backups to free cloud services such as Dropbox, Google Drive, and Microsoft SkyDrive.
7
7
 
8
+ It can also encrypt or decrypt directory trees 'in place', so that the original files are
9
+ overwritten by their encrypted versions.
10
+
8
11
  Written by Jeff Sember, March 2013.
9
12
 
10
13
  [Source code documentation can be found here.](http://rubydoc.info/gems/lenc/frames)
@@ -23,8 +26,8 @@ is named "__lenc_repo__.txt").
23
26
  versions of all the files found in the <source> directory. This directory
24
27
  is usually mapped to a cloud service (e.g., Dropbox), or perhaps to a thumb drive.
25
28
 
26
- NOTE: THE <encrypted> DIRECTORY IS MANAGED BY THE PROGRAM!
27
- ANY FILES WRITTEN TO THIS DIRECTORY BY THE USER MAY BE DELETED.
29
+ NOTE: THE <encrypted> DIRECTORY IS MANAGED BY THE PROGRAM!
30
+ ANY FILES WRITTEN TO THIS DIRECTORY BY THE USER MAY BE DELETED.
28
31
 
29
32
  * A \<recover\> directory. The program can recover a set of encrypted files here.
30
33
  For safety, the this directory must not lie within an existing repository.
@@ -38,16 +41,16 @@ The program can be asked to perform one of the following tasks:
38
41
  __Setting up a repository.__ Select a directory you wish to be the \<source\>
39
42
  directory, and make it the current directory. Type:
40
43
 
41
- lencrypt -i KEY ENCDIR
44
+ lencrypt -i ENCDIR
42
45
 
43
- with KEY a set of characters (8 to 56 letters) to be used as the encryption key,
46
+ with KEY a set of characters (up to 56 letters) to be used as the encryption key,
44
47
  and ENCDIR the name of the \<encrypted\> directory (it must not already exist, and
45
48
  it cannot lie within the current directory's tree).
46
49
 
47
50
 
48
51
  __Updating a repository.__ From within a \<source\> directory tree, type:
49
52
 
50
- lencrypt
53
+ lencrypt
51
54
 
52
55
  You will be prompted for the encryption key, and then the program will examine
53
56
  which files within the \<source\> directory have been changed (since the repository
@@ -56,17 +59,44 @@ was created or last updated), and re-encrypt these into the \<encrypted\> direct
56
59
 
57
60
  __Recovering encrypted files.__ Type:
58
61
 
59
- lencrypt -r KEY ENCDIR RECDIR
62
+ lencrypt -r ENCDIR RECDIR
60
63
 
61
- with KEY the key associated with a repository whose encrypted files are stored in ENCDIR.
64
+ where ENCDIR contains an encrypted repository's files.
62
65
  The recovered files will be stored in RECDIR.
63
66
 
64
-
65
67
  Additional options can be found by typing:
66
68
 
67
- lencrypt -h
68
-
69
+ lencrypt -h
69
70
 
71
+ Encrypting files 'in place'
72
+ ----------------
73
+ In addition to maintaining encrypted repositories, the gem can also encrypt (and decrypt)
74
+ files 'in place', in effect replacing the files with their encrypted counterparts. To encrypt
75
+ a particular directory's contents (and all of its subdirectories), from that directory, type:
76
+
77
+ encr -i
78
+
79
+ This marks the directory as the root of an 'in place' repository (by writing a small configuration file).
80
+ You will be prompted for an encryption password.
81
+
82
+ Once such a repository has been defined, the files can be encrypted. From the repository directory (or
83
+ any of its subdirectories), type:
84
+
85
+ encr
86
+
87
+ After you enter a password, the program will encrypt all the files (or at least those not marked for
88
+ skipping within a .lencignore file). If you create any new unencrypted files, you can
89
+ repeat this command to encrypt them.
90
+
91
+ To decrypt the repository's contents, type:
92
+
93
+ encr -d
94
+
95
+ It is very important to remember the encryption password, since it is NOT stored anywhere by the
96
+ program. By design, you must enter the correct password twice before any files are encrypted: once when the
97
+ repository is initialized, and again when the actual encryption is to take place.
98
+
99
+
70
100
  Ignore files
71
101
  ----------------
72
102
  If desired, you can avoid storing selected files in the encryption repository.
@@ -0,0 +1,5 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'lenc'
4
+
5
+ EncrApp.new().run(ARGV)
@@ -1 +1,2 @@
1
1
  require 'lenc/lencrypt'
2
+ require 'lenc/encr'
@@ -142,7 +142,8 @@ module RepoInternal
142
142
 
143
143
  =end
144
144
  class MyAES
145
-
145
+ attr_accessor :prefix_mode
146
+
146
147
  private
147
148
 
148
149
  # decryptState values
@@ -153,6 +154,7 @@ module RepoInternal
153
154
  # to help generate unique nonces (in conjunction with system clock)
154
155
  @@nonceHelper = 0
155
156
 
157
+
156
158
  # Construct a MyAES object to encrypt/decrypt a sequence of bytes.
157
159
  # @param encrypting true for encryption, false for decryption
158
160
  # @param key a bytearray of 4..56 bytes
@@ -179,7 +181,7 @@ module RepoInternal
179
181
  key = bytes_to_str(key)
180
182
 
181
183
  if key.size < 4 || key.size > 56
182
- raise ArgumentError, 'Key length not 4..56 bytes'
184
+ raise ArgumentError, "Key length ##{key.size} not 4..56 bytes"
183
185
  end
184
186
 
185
187
  # expand the key to be at least 32 bytes
@@ -304,28 +306,37 @@ module RepoInternal
304
306
  raise LEnc::DecryptionError, "header doesn't verify"
305
307
  end
306
308
 
307
- nPadBytes = newData[CHUNK_VERIFY_SIZE].ord
308
- actualEnd = csize - nPadBytes
309
- if nPadBytes > 16 or actualEnd < CHUNK_HEADER_SIZE
310
- raise LEnc::DecryptionError, "nPadBytes/actualEnd mismatch"
311
- end
312
-
309
+ # If we're in prefix mode, we're only interested in whether
310
+ # the chunk start matches the verification string above; we
311
+ # don't actually produce any decoded data (partly because we
312
+ # do not really know how many padding bytes there are in the
313
+ # decryption stream)
313
314
 
315
+ if !prefix_mode
316
+
317
+ nPadBytes = newData[CHUNK_VERIFY_SIZE].ord
318
+ actualEnd = csize - nPadBytes
319
+ if nPadBytes > 16 or actualEnd < CHUNK_HEADER_SIZE
320
+ raise LEnc::DecryptionError, "nPadBytes/actualEnd mismatch"
321
+ end
314
322
 
315
- # Verify that the padding bytes have correct values
316
- (actualEnd...csize).each do |i|
317
- if newData[i] != PAD_CHAR
318
- raise LEnc::DecryptionError,"padding char bad value"
323
+ # Verify that the padding bytes have correct values
324
+ (actualEnd...csize).each do |i|
325
+ if newData[i] != PAD_CHAR
326
+ raise LEnc::DecryptionError,"padding char bad value"
327
+ end
319
328
  end
329
+
330
+ newData = newData[CHUNK_HEADER_SIZE ... actualEnd]
331
+
332
+ @inputBuffer.slice!(0,csize)
333
+ @outputBuffer << newData
334
+
320
335
  end
321
-
322
- newData = newData[CHUNK_HEADER_SIZE ... actualEnd]
323
-
324
- @decryptState = DS_WAITCHUNK
325
-
326
- @inputBuffer.slice!(0,csize)
327
- @outputBuffer << newData
328
336
 
337
+ @decryptState = DS_WAITCHUNK
338
+
339
+
329
340
  end
330
341
 
331
342
  incrNonce()
@@ -427,26 +438,43 @@ module RepoInternal
427
438
  # Returns true iff the start of the string seems to decrypt correctly
428
439
  # for the given password
429
440
  def self.is_string_encrypted(key, test_str)
430
- db = warndb 0
441
+ db = warndb 0
431
442
 
432
- !db || hex_dump(test_str, "areBytesEncrypted?")
443
+ !db || hex_dump(test_str, "is_string_encrypted?")
433
444
 
434
445
  simple_str(test_str)
435
446
 
436
447
  lnth = test_str.size
437
448
  lnth -= NONCE_SIZE_SMALL
438
- if lnth < AES_BLOCK_SIZE
439
- !db || pr(" insufficient # bytes\n")
449
+ if lnth < AES_BLOCK_SIZE || lnth % AES_BLOCK_SIZE != 0
450
+ !db || pr(" bad # bytes\n")
440
451
  return false
441
452
  end
442
453
 
454
+ hdr_size = AES_BLOCK_SIZE
455
+
456
+ # This method is failing, I suspect because with the mode of AES we're using (CRC?) we can't
457
+ # decrypt only a single block, and must instead decrypt a complete chunk.
458
+
459
+ # No, now I think it's interpreting a bad 'padding' value (due to only decrypting partially)
460
+ # as indication of bad decryption
461
+
462
+ if false
463
+ warn("using full chunk size")
464
+ hdr_size = [lnth,CHUNK_SIZE_ENCR].min
465
+ end
466
+
443
467
  begin
444
- de = MyAES.new(false, key)
445
- de.finish(test_str[0...AES_BLOCK_SIZE + NONCE_SIZE_SMALL])
446
- decr = de.flush()
447
- !db || hex_dump(decr,"decrypted successfully")
448
- rescue LEnc::DecryptionError
449
- !db || pr(" (caught DecryptionError)\n")
468
+ de = MyAES.new(false, key)
469
+
470
+ # Put this decryptor into prefix mode, so that we are only interested
471
+ # in whether the header verifies correctly
472
+ de.prefix_mode = true
473
+
474
+ de.finish(test_str[0...hdr_size + NONCE_SIZE_SMALL])
475
+ de.flush()
476
+ rescue LEnc::DecryptionError => e
477
+ !db || pr(" (caught DecryptionError #{e})\n")
450
478
  return false
451
479
  end
452
480
 
@@ -460,20 +488,34 @@ module RepoInternal
460
488
  # for the given password, and the file is of the expected length.
461
489
  def self.is_file_encrypted(key, path)
462
490
 
491
+ db = warndb 0
492
+
493
+ !db || pr("is_file_encrypted '#{path}'?\n")
463
494
  # key = str_to_bytes(key)
464
495
 
465
496
  if not File.file?(path)
497
+ !db || pr(" not a file\n")
466
498
  return false
467
499
  end
468
500
 
469
501
  lnth = File.size(path)
470
502
  minSize = NONCE_SIZE_SMALL + AES_BLOCK_SIZE
471
- if lnth < minSize or ((lnth - minSize) % _AES_BLOCK_SIZE) != 0
503
+ !db || pr(" file size=#{lnth}, minSize=#{minSize}\n")
504
+ if lnth < minSize or ((lnth - minSize) % AES_BLOCK_SIZE) != 0
505
+ !db || pr(" length not appropriate\n")
472
506
  return false
473
507
  end
474
508
 
509
+ if false
510
+ warn("using full size of file")
511
+ minSize = lnth
512
+ end
513
+
475
514
  f = File.open(path,"rb")
476
- return is_string_encrypted(key, f.read(minSize))
515
+ s = f.read(minSize)
516
+ ret = is_string_encrypted(key, s)
517
+ !db || pr(" is_string_encrypted returning #{ret}\n")
518
+ ret
477
519
  end
478
520
 
479
521
 
@@ -485,35 +527,68 @@ end # module RepoInternal
485
527
  if main? __FILE__
486
528
 
487
529
  s = ''
488
- 16.times {|x| s << (65+x).chr}
489
-
530
+ 17.times {|x| s << (65+x).chr}
531
+ s *= 8
490
532
 
491
533
  nonce = "abc" * 20
492
534
  nonce = nonce[0...16]
493
535
  key = "onefishtwofishredfishbluefish" * 3
494
536
  key = key[0...32]
495
537
 
496
- hex_dump(key,"key")
497
- hex_dump(nonce,"nonce")
498
538
 
499
- aes = OpenSSL::Cipher.new("AES-256-CBC")
500
-
501
- aes.padding = 0
502
- aes.encrypt
503
- aes.key = key
504
-
505
- aes.iv = nonce
506
-
507
- enc = aes.update(s)
508
- enc << aes.final
509
-
510
- hex_dump(s,"calling aes.encrypt with")
511
- hex_dump(enc,"aes.encrypt returned")
539
+ if false # this seems to work, disable for now
540
+ hex_dump(key,"key")
541
+ hex_dump(nonce,"nonce")
542
+
543
+ aes = OpenSSL::Cipher.new("AES-256-CBC")
544
+
545
+ aes.padding = 0
546
+ aes.encrypt
547
+ aes.key = key
548
+
549
+ aes.iv = nonce
512
550
 
513
- s = enc
551
+ enc = aes.update(s)
552
+ enc << aes.final
553
+
554
+ hex_dump(s,"calling aes.encrypt with")
555
+ hex_dump(enc,"aes.encrypt returned")
556
+
557
+ s = enc
558
+
559
+ require 'base64'
560
+ s = Base64.urlsafe_encode64(s)
561
+ hex_dump(s,"base64")
562
+
563
+ # Verify that we don't need every block to verify encryption
564
+ aes = OpenSSL::Cipher.new("AES-256-CBC")
565
+ aes.padding = 0
566
+ aes.decrypt
567
+ aes.key = key
568
+ aes.iv = nonce
569
+
570
+ dec = aes.update(enc[0...16])
571
+ dec << aes.final
572
+
573
+ hex_dump(dec,"decrypted")
574
+ end
575
+
576
+ include RepoInternal
577
+
578
+ aes = MyAES.new(true, key, nonce)
579
+
580
+ aes.finish(s)
581
+ enc = aes.flush()
582
+
583
+ hex_dump(enc,"encrypted")
584
+
585
+ aes = MyAES.new(false, key)
586
+ aes.finish(enc)
587
+ dec = aes.flush()
588
+ hex_dump(dec,"decrypted")
589
+
590
+ isenc = MyAES.is_string_encrypted(key, enc)
591
+ pr("is encrypted= #{isenc}\n")
514
592
 
515
- require 'base64'
516
- s = Base64.urlsafe_encode64(s)
517
- hex_dump(s,"base64")
518
593
 
519
594
  end
@@ -0,0 +1,64 @@
1
+ require_relative 'repo'
2
+
3
+ class EncrApp
4
+ include LEnc
5
+
6
+ def run(argv = ARGV)
7
+
8
+ req 'trollop'
9
+ p = Trollop::Parser.new do
10
+ opt :init, "create new singular repository"
11
+ opt :orignames, "(with --init) leave filenames unencrypted"
12
+ opt :encrypt, "encrypt files (default operation)"
13
+ opt :decrypt, "decrypt files"
14
+ opt :key, "encryption key", :type => :string
15
+ opt :verbose,"verbose operation"
16
+ opt :where, "specify source directory (default = current directory)", :type => :strings
17
+ opt :quiet, "quiet operation"
18
+ end
19
+
20
+ options = Trollop::with_standard_exception_handling p do
21
+ p.parse argv
22
+ end
23
+
24
+ v = 0
25
+ v = -1 if options[:quiet]
26
+ v = 1 if options[:verbose]
27
+
28
+ nOpt = 0
29
+ nOpt += 1 if options[:init]
30
+ nOpt += 1 if options[:encrypt]
31
+ nOpt += 1 if options[:decrypt]
32
+
33
+ p.die("Only one operation can be performed at a time.",nil) if nOpt > 1
34
+
35
+ r = Repo.new(:dryrun => options[:dryrun], :verbosity => v)
36
+
37
+ key = options[:key]
38
+
39
+ begin
40
+
41
+ if options[:init]
42
+ r.create(options[:where], key, nil, options[:orignames])
43
+ elsif options[:decrypt]
44
+ r.open(options[:where],key)
45
+ r.perform_decrypt
46
+ else
47
+ r.open(options[:where],key)
48
+ r.perform_encrypt
49
+ end
50
+
51
+ r.close()
52
+ rescue Exception =>e
53
+ puts("\nProblem encountered: #{e.message}")
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ if __FILE__ == $0
60
+ args = ARGV
61
+
62
+ EncrApp.new().run(args)
63
+ end
64
+
@@ -1,21 +1,17 @@
1
1
  require_relative 'repo'
2
2
 
3
- # The application script (i.e., the 'main program')
4
- #
5
3
  class LEncApp
6
4
  include LEnc
7
5
 
8
6
  def run(argv = ARGV)
9
-
7
+
10
8
  req 'trollop'
11
9
  p = Trollop::Parser.new do
12
- opt :init, "create new encryption repository: KEY ENCDIR ", :type => :strings
10
+ opt :init, "create new encryption repository: ENCDIR ", :type => :string
11
+ opt :key, "encryption key", :type => :string
13
12
  opt :orignames, "(with --init) leave filenames unencrypted"
14
- opt :storekey, "(with --init) store the key within the repository configuration file so it" \
15
- " need not be entered with every update"
16
- opt :update, "update encrypted repository (default operation): KEY", :default => ""
17
- #opt :updatepwd, "specify key for update: KEY", :type => :string
18
- opt :recover, "recover files from an encrypted repository: KEY ENCDIR RECDIR", :type => :strings
13
+ opt :update, "update encrypted repository (default operation)"
14
+ opt :recover, "recover files from an encrypted repository: ENCDIR RECDIR", :type => :strings
19
15
  opt :where, "specify source directory (default = current directory)", :type => :string
20
16
  opt :verbose,"verbose operation"
21
17
  opt :quiet, "quiet operation"
@@ -34,12 +30,9 @@ class LEncApp
34
30
  v = -1 if options[:quiet]
35
31
  v = 1 if options[:verbose]
36
32
 
37
- update_pwd = options[:update]
38
- update_pwd = nil if update_pwd.size == 0
39
-
40
33
  nOpt = 0
41
34
  nOpt += 1 if options[:init]
42
- nOpt += 1 if update_pwd
35
+ nOpt += 1 if options[:update]
43
36
  nOpt += 1 if options[:recover]
44
37
 
45
38
  #pr("trollop opts = %s\n",d2(options))
@@ -52,20 +45,20 @@ class LEncApp
52
45
  begin
53
46
 
54
47
  if (a = options[:init])
55
- p.die("Expecting: KEY ENCDIR",nil) if a.size != 2
56
- pwd,encDir = a
57
- r.create(options[:where], pwd, encDir, options[:orignames], options[:storekey])
48
+ encDir = a
49
+ r.create(options[:where], options[:key], encDir, options[:orignames])
58
50
  elsif (a = options[:recover])
59
- p.Trollop::die("Expecting: KEY ENCDIR RECDIR",nil) if a.size != 3
60
- r.perform_recovery(a[0],a[1],a[2])
51
+ p.die("Expecting: ENCDIR RECDIR",nil) if a.size != 2
52
+ r.perform_recovery(options[:key],a[0],a[1])
61
53
  else
62
- r.open(options[:where],update_pwd)
63
- r.perform_update(options[:verifyenc])
54
+ r.open(options[:where],options[:key])
55
+ r.perform_encrypt()
64
56
  end
65
57
 
66
58
  r.close()
67
59
  rescue Exception =>e
68
60
  puts("\nProblem encountered: #{e.message}")
61
+ puts e.backtrace
69
62
  end
70
63
 
71
64
  end
@@ -74,7 +67,6 @@ end
74
67
  if __FILE__ == $0
75
68
  args = ARGV
76
69
 
77
-
78
70
  LEncApp.new().run(args)
79
71
  end
80
72