lenc 1.0.0 → 1.1.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: a71c838f760ff31a7d853f32b8f9fdcc04621b56
4
- data.tar.gz: e4ee1f83e5fd2b158ec5397703f02402f1b1a182
3
+ metadata.gz: fbdf59fe864cde5c6b5d6ab0029ed7b3086b6abb
4
+ data.tar.gz: 935307daa431368ca0f0d86a8b9eca068d1d1b58
5
5
  SHA512:
6
- metadata.gz: 1bc44af9c93ec3992406479f342e56146d8607f4f564ec594bdb67f23aa1f5e2b5afd31f0c2e32f76951d6e48ec615a0c24d86cadd50b703d98c2099c88a7da4
7
- data.tar.gz: adf0c0379be49bbdb19ace972fa517f56bdf643ef19c51b5deb32376631dc19e098c0b6d301f1d62b5dc054078855efa69ebd8376f9ea806d94beefd2b5abb73
6
+ metadata.gz: 1377690289e75b202bf0a6ee0cffc28c469688ccde9a8dcba36051eed8433b2f32010f5db40b6d648a4d214e0a74ead2c2f4555c2caef5aa5875d21fd17cb90e
7
+ data.tar.gz: 46b0cc17fc7254f5a87abe27099d0f3ee21e92fc133bd87de2e20c1df052bc385def1bcda8a1e64f7fd280656a8e46c6664143c1020a91bfc989fb694cbc71a0
@@ -1,2 +1,7 @@
1
- 2013-03-21 Jeff Sember jpsember@gmail.com
1
+ 2013-03-21
2
2
  * Version 1.0.0 released
3
+
4
+ 2013-03-22
5
+ * For added safety, unless the --storekey option is given when the repository is
6
+ initialized, the key is no longer stored in repository configuration file, and
7
+ the user is prompted at every update to enter the password.
data/README.txt CHANGED
@@ -1,29 +1,35 @@
1
- lenc : Maintains encrypted repositories of files, enabling secure, encrypted
1
+ # @markup markdown
2
+
3
+ Lenc
4
+ ===========
5
+
6
+ A Ruby Gem that maintains encrypted repositories of files, enabling secure, encrypted
2
7
  backups to free cloud services such as Dropbox, Google Drive, and Microsoft SkyDrive.
3
8
 
4
9
  Written and (c) by Jeff Sember, March 2013.
5
- ================================================================================
6
-
7
10
 
11
+ Directory structure
12
+ --------
8
13
  The program manipulates three distinct directories:
9
14
 
10
- 1) A <source> directory, which holds all the
15
+ * A \<source\> directory, which holds all the
11
16
  files to be encrypted. The only file that the program modifies within this
12
17
  directory tree is a hidden configuration file ".lenc" (on Windows, this file
13
18
  is named "__lenc_repo__.txt").
14
19
 
15
- 2) An <encrypted> directory, where the program stores the encrypted
20
+ * An \<encrypted\> directory, where the program stores the encrypted
16
21
  versions of all the files found in the <source> directory. This directory
17
22
  is usually mapped to a cloud service (e.g., Dropbox), or perhaps to a thumb drive.
18
- NOTE: THE <encrypted> DIRECTORY IS MANAGED BY THE PROGRAM!
19
- ANY FILES WRITTEN TO THIS DIRECTORY BY THE USER MAY BE DELETED.
23
+
24
+ NOTE: THE <encrypted> DIRECTORY IS MANAGED BY THE PROGRAM!
25
+ ANY FILES WRITTEN TO THIS DIRECTORY BY THE USER MAY BE DELETED.
20
26
 
21
- 3) A <recover> directory. The program can recover a set of encrypted files here.
27
+ * A \<recover\> directory. The program can recover a set of encrypted files here.
22
28
  For safety, the this directory must not lie within an existing repository.
23
29
 
24
30
 
25
31
  Running the program
26
- ================================================================================
32
+ ----
27
33
 
28
34
  The program can be asked to perform one of the following tasks:
29
35
 
@@ -33,17 +39,17 @@ directory, and make it the current directory. Type:
33
39
  lencrypt -i KEY ENCDIR
34
40
 
35
41
  with KEY a set of characters (8 to 56 letters) to be used as the encryption key,
36
- and ENCDIR the name of the <encrypted> directory (it must not already exist, and
42
+ and ENCDIR the name of the \<encrypted\> directory (it must not already exist, and
37
43
  it cannot lie within the current directory's tree).
38
44
 
39
45
 
40
- 2) Updating a repository. From within a <source> directory tree, type:
46
+ 2) Updating a repository. From within a \<source\> directory tree, type:
41
47
 
42
48
  lencrypt
43
49
 
44
- The program will examine which files within the <source> directory have been
45
- changed (since the repository was created or last updated), and re-encrypt these
46
- into the <encrypted> directory.
50
+ You will be prompted for the encryption key, and then the program will examine
51
+ which files within the \<source\> directory have been changed (since the repository
52
+ was created or last updated), and re-encrypt these into the \<encrypted\> directory.
47
53
 
48
54
  3) Recovering encrypted files. Type:
49
55
 
@@ -52,12 +58,16 @@ into the <encrypted> directory.
52
58
  with KEY the key associated with a repository whose encrypted files are stored in ENCDIR.
53
59
  The recovered files will be stored in RECDIR.
54
60
 
61
+
62
+ Additional options can be found by typing:
63
+
64
+ lencrypt -h
55
65
 
56
66
 
57
67
  Ignore files
58
- ================================================================================
68
+ ----------------
59
69
  If desired, you can avoid storing selected files in the encryption repository.
60
- Within the <source> directory (or any of its subdirectories), place a text
70
+ Within the \<source\> directory (or any of its subdirectories), place a text
61
71
  file '.lencignore' with a list of file or directory names (or patterns) to be
62
72
  ignored. Example:
63
73
 
@@ -74,17 +84,17 @@ Some files are automatically ignored, e.g. ".DS_Store".
74
84
 
75
85
  The format of ignore files is similar to that of .gitignore files. Details:
76
86
 
77
- [] Each line should contain a single pattern representing files or directories to be ignored.
78
- [] A line will be ignored (treated as a comment) if it is blank, or if it starts with '#'.
79
- [] The path separator should be '/' (Mac, Unix) or '\' (Windows).
80
- [] If a pattern starts with '#', you can precede it with '\' to avoid it being ignored.
81
- [] Precede a pattern with '!' to specifically include files/directories, overriding any previous
82
- matching pattern in a parent directory.
83
- [] If a pattern ends with the path separator, it will be removed, and the pattern will
84
- match only directories, not files.
85
- [] The wildcard '*' matches for any sequence of zero or more characters.
86
- [] The wildcard '?' matches any single character.
87
- [] If the pattern contains any path separators, then the wildcards '*', '?' will not
88
- match the path separator.
87
+ * Each line should contain a single pattern representing files or directories to be ignored.
88
+ * A line will be ignored (treated as a comment) if it is blank, or if it starts with '#'.
89
+ * The path separator should be '/' (Mac, Unix) or '\' (Windows).
90
+ * If a pattern starts with '#', you can precede it with '\' to avoid it being ignored.
91
+ * Precede a pattern with '!' to specifically include files/directories, overriding any previous
92
+ matching pattern in a parent directory.
93
+ * If a pattern ends with the path separator, it will be removed, and the pattern will
94
+ match only directories, not files.
95
+ * The wildcard '*' matches for any sequence of zero or more characters.
96
+ * The wildcard '?' matches any single character.
97
+ * If the pattern contains any path separators, then the wildcards '*', '?' will not
98
+ match the path separator.
89
99
 
90
100
 
@@ -430,10 +430,10 @@ module RepoInternal
430
430
  #
431
431
  # Returns true iff the start of the string seems to decrypt correctly
432
432
  # for the given password
433
- def is_string_encrypted(key, test_str)
434
- db = false
433
+ def self.is_string_encrypted(key, test_str)
434
+ db = warndb 0
435
435
 
436
- !db || hexDump(test_str, "areBytesEncrypted?")
436
+ !db || hex_dump(test_str, "areBytesEncrypted?")
437
437
 
438
438
  simple_str(test_str)
439
439
 
@@ -445,10 +445,10 @@ module RepoInternal
445
445
  end
446
446
 
447
447
  begin
448
- de = MyAES(False, key)
449
- de.finish(test_str[:_AES_BLOCK_SIZE + NONCE_SIZE_SMALL])
448
+ de = MyAES.new(false, key)
449
+ de.finish(test_str[0...AES_BLOCK_SIZE + NONCE_SIZE_SMALL])
450
450
  decr = de.flush()
451
- !db || hexDump(decr,"decrypted successfully")
451
+ !db || hex_dump(decr,"decrypted successfully")
452
452
  rescue LEnc::DecryptionError
453
453
  !db || pr(" (caught DecryptionError)\n")
454
454
  return false
@@ -460,9 +460,9 @@ module RepoInternal
460
460
  # Determines if a file is an encrypted file
461
461
  # @param key password to use (string, or array of bytes)
462
462
  # @param path path to file
463
- # Returns true iff the start of the file seems to decrypt correctly
463
+ # @return true iff the start of the file seems to decrypt correctly
464
464
  # for the given password, and the file is of the expected length.
465
- def is_file_encrypted(key, path)
465
+ def self.is_file_encrypted(key, path)
466
466
 
467
467
  # key = str_to_bytes(key)
468
468
 
@@ -10,7 +10,9 @@ class LEncApp
10
10
  req 'trollop'
11
11
  p = Trollop::Parser.new do
12
12
  opt :init, "create new encryption repository: KEY ENCDIR ", :type => :strings
13
- opt :orignames, "leave filenames unencrypted"
13
+ 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"
14
16
  opt :update, "update encrypted repository (default operation)"
15
17
  opt :recover, "recover files from an encrypted repository: KEY ENCDIR RECDIR", :type => :strings
16
18
  opt :where, "specify source directory (default = current directory)", :type => :string
@@ -42,7 +44,7 @@ class LEncApp
42
44
  if (a = options[:init])
43
45
  Trollop::die("Expecting: KEY ENCDIR") if a.size != 2
44
46
  pwd,encDir = a
45
- r.create(options[:where], pwd, encDir, options[:orignames])
47
+ r.create(options[:where], pwd, encDir, options[:orignames], options[:storekey])
46
48
  elsif (a = options[:recover])
47
49
  Trollop::die("Expecting: KEY ENCDIR RECDIR") if a.size != 3
48
50
  r.perform_recovery(a[0],a[1],a[2])
@@ -62,9 +64,20 @@ end
62
64
  if __FILE__ == $0
63
65
  args = ARGV
64
66
 
65
- # warn("trying special")
66
- # args = "-w /Users/jeff/Desktop/_testdirs_ -r feefiefoefumaldkjsdaflkj /Users/jeff/Desktop/_testdirs_/encr /Users/jeff/Desktop/_testdirs_/resc2".split
67
- # args = "-h".split
67
+ # if true && Dir.home.end_with?("/jeff")
68
+ # warn("trying special")
69
+ # bs = File.join(Dir.home,"Desktop/_testdirs_")
70
+ #
71
+ # if !File.file? "#{bs}/src/.lenc"
72
+ # s = "-w #{bs}/src -i onefishtwofishredfishbluefish #{bs}/encr"
73
+ # else
74
+ # s = "-w #{bs}/src"
75
+ # end
76
+ ## s = "-h"
77
+ # args = s.split
78
+ # # args = "-h".split
79
+ # end
80
+
68
81
  LEncApp.new().run(args)
69
82
  end
70
83
 
@@ -104,7 +104,7 @@ module LEnc
104
104
 
105
105
  # During recovery, we will use the first file we encounter to
106
106
  # verify if the supplied password is correct, and abort if not.
107
- @password_verified = false
107
+ @recovery_pwd_verified = false
108
108
 
109
109
  if options.size > 0
110
110
  raise ArgumentError, "Unrecognized options: " + d2(options)
@@ -119,10 +119,11 @@ module LEnc
119
119
  # @param enc_dir directory to store encrypted files; must not yet exist, and must
120
120
  # not represent a directory lying within the repo_dir tree
121
121
  # @param original_names if true, the filenames are not encrypted, only the file contents
122
- #
122
+ # @param store_key if true, the key is written to the repository configuration file; otherwise,
123
+ # user must supply the key every time the repository is updated
123
124
  # @raise ArgumentError if appropriate
124
125
  #
125
- def create(repo_dir, key, enc_dir, original_names=false)
126
+ def create(repo_dir, key, enc_dir, original_names=false, store_key=true)
126
127
  raise IllegalStateException if @state != STATE_CLOSED
127
128
 
128
129
  db = warndb 0
@@ -165,7 +166,19 @@ module LEnc
165
166
  + " is illegal"
166
167
  end
167
168
 
168
- @confFile.set('key', key)
169
+ if store_key
170
+ @confFile.set('key', key)
171
+ end
172
+
173
+ # Construct a string that verifies the password is correct
174
+ en = MyAES.new(true, key )
175
+ en.finish("!!!")
176
+ verifier_string = en.flush
177
+
178
+ # Store key verifier as an array of bytes, to avoid nonprintable problems
179
+ vs2 = Base64.urlsafe_encode64(verifier_string)
180
+ @confFile.set('key_verifier', vs2)
181
+
169
182
 
170
183
  # Create encryption directory
171
184
  if File.exists?(edir)
@@ -173,12 +186,13 @@ module LEnc
173
186
  "Encryption directory or file already exists: '#{edir}'"
174
187
  end
175
188
 
176
- @confFile.set('encDir', edir)
189
+ @confFile.set('enc_dir', edir)
177
190
 
178
191
  if not @dryrun
179
192
  Dir.mkdir(edir)
180
193
  @confFile.write()
181
194
  end
195
+
182
196
  end
183
197
 
184
198
 
@@ -186,11 +200,13 @@ module LEnc
186
200
  #
187
201
  # @param startDirectory directory lying within repository tree; if nil, uses
188
202
  # current directory
189
- #
203
+ # @param password repository password; if a password was stored with the repository
204
+ # when it was created, this parameter is ignored; if this parameter is null, and
205
+ # no password was stored with the repository, the user will be prompted for one
190
206
  # @raise IllegalStateException if repository is already open
191
207
  # @raise ArgumentError if directory doesn't exist, or does not lie in a repository
192
208
  #
193
- def open(startDirectory=nil)
209
+ def open(startDirectory=nil, password = nil)
194
210
  db = warndb 0
195
211
  !db || pr("Repo.open startDir=%s\n",d(startDirectory))
196
212
 
@@ -223,12 +239,30 @@ module LEnc
223
239
  end
224
240
 
225
241
  # Read values from configuration to instance vars
226
- @encrDir = @confFile.val('encDir')
227
- @encrKey = @confFile.val('key')
242
+ @encrDir = @confFile.val('enc_dir')
243
+ pwd = @confFile.val('key') || password
244
+ if !pwd
245
+ printf("Password: ")
246
+ pwd = gets
247
+ if pwd
248
+ pwd.strip!
249
+ pwd = nil if pwd.size == 0
250
+ end
251
+ if !pwd
252
+ raise DecryptionError, "No password given"
253
+ end
254
+ end
255
+
228
256
  @orignames = @confFile.val('orignames')
257
+ @encrKey = pwd
229
258
 
230
259
  prepareKeys()
231
-
260
+
261
+ key_verifier = @confFile.val('key_verifier')
262
+ key_verifier = Base64.urlsafe_decode64(key_verifier)
263
+
264
+ verify_encrypt_pwd(pwd, key_verifier)
265
+
232
266
  @state = STATE_OPEN
233
267
  end
234
268
 
@@ -494,7 +528,7 @@ module LEnc
494
528
  @confFile = nil
495
529
  @repoBaseDir = nil
496
530
  @encrDir = nil
497
- @version = 1.0
531
+ @version = 2.0
498
532
  @orignames = false
499
533
  initIgnoreList()
500
534
  end
@@ -612,14 +646,26 @@ module LEnc
612
646
  raise DecryptionError(e)
613
647
  end
614
648
 
615
- set_password_verified()
649
+ set_recovery_pwd_verified()
616
650
 
617
651
  return s
618
652
  end
619
653
 
620
- def set_password_verified
621
- if !@password_verified
622
- @password_verified = true
654
+ # Verify that a key is the correct password for this repository.
655
+ # @param key key to verify
656
+ # @param key_verifier an encrypted string that will decrypt correctly if the key is correct
657
+ def verify_encrypt_pwd(key, key_verifier)
658
+ db = warndb 0
659
+ !db || pr("verify_encrypt_pwd key %s, verifier %s\n",d(key),hex_dump_to_string(key_verifier))
660
+
661
+ if !MyAES.is_string_encrypted(key, key_verifier)
662
+ raise(DecryptionError, "#{key} is not the correct password for this repository")
663
+ end
664
+ end
665
+
666
+ def set_recovery_pwd_verified
667
+ if !@recovery_pwd_verified
668
+ @recovery_pwd_verified = true
623
669
  end
624
670
  end
625
671
 
@@ -881,7 +927,7 @@ module LEnc
881
927
  rescue DecryptionError
882
928
  encPath = File.absolute_path(File.join(encryptDir,f))
883
929
  pth = rel_path(encPath, @inputDir)
884
- if !@password_verified
930
+ if !@recovery_pwd_verified
885
931
  raise(DecryptionError,"Wrong password (cannot decrypt filename #{pth})")
886
932
  end
887
933
 
@@ -917,7 +963,7 @@ module LEnc
917
963
  end
918
964
  begin
919
965
  convertFile(encrFullPath, TEMPFILENAME, false, showProgress)
920
- set_password_verified()
966
+ set_recovery_pwd_verified()
921
967
  if not @dryrun
922
968
  if File.file?(origPath)
923
969
  remove_file_or_dir(origPath)
@@ -926,7 +972,7 @@ module LEnc
926
972
  end
927
973
  rescue DecryptionError => e
928
974
 
929
- if !@password_verified
975
+ if !@recovery_pwd_verified
930
976
  raise(DecryptionError,"Wrong password (cannot decrypt file #{pth})")
931
977
  end
932
978
 
@@ -530,7 +530,8 @@ end
530
530
 
531
531
  if defined? Test::Unit
532
532
 
533
- # Framework for suite testing
533
+ # A simple extension to Ruby's Test::Unit class that provides
534
+ # suite-level setup/teardown methods.
534
535
  #
535
536
  # If test suite functionality is desired within a script,
536
537
  # then require 'test/unit' before requiring 'tools.rb'.
@@ -568,8 +569,8 @@ if defined? Test::Unit
568
569
  # 2) The base class implementations of method_/suite_xxx do nothing.
569
570
  #
570
571
  # 3) The number of test cases reported may be higher than you expect, since
571
- # there are test methods defined by the TestSuite class to acheive this
572
- # functionality.
572
+ # there are additional test methods defined by the TestSuite class to
573
+ # implement the suite setup / teardown functionality.
573
574
  #
574
575
  # 4) Avoid naming test methods that fall outside of test_01 ... test_zz.
575
576
  #
@@ -227,7 +227,7 @@ end
227
227
  def create_repo
228
228
  clean
229
229
  rp = build_repo_obj
230
- rp.create(@@sourceDir,@@key,@@encryptDir)
230
+ rp.create(@@sourceDir,@@key,@@encryptDir, true)
231
231
  rp.close
232
232
  rp
233
233
  end
@@ -306,13 +306,13 @@ end
306
306
  # Create repo using lencrypt program
307
307
  def test_101_create_repo
308
308
  clean
309
- a = "-i #{@@key} #{@@encryptDir}"
309
+ a = "-i #{@@key} #{@@encryptDir} --storekey"
310
310
  ex(a)
311
311
  end
312
312
 
313
313
  def test_101_update_repo_with_verify
314
314
  clean
315
- ex("-i #{@@key} #{@@encryptDir}")
315
+ ex("-i #{@@key} #{@@encryptDir} --storekey")
316
316
  rp = build_repo_obj
317
317
  rp.open(@@sourceDir)
318
318
  rp.perform_update(true)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lenc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeff Sember