lenc 1.1.3 → 1.2.0

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.
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