lenc 1.0.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 +7 -0
- data/CHANGELOG.txt +2 -0
- data/README.txt +90 -0
- data/bin/lencrypt +5 -0
- data/lib/lenc.rb +1 -0
- data/lib/lenc/aes.rb +523 -0
- data/lib/lenc/config_file.rb +108 -0
- data/lib/lenc/lencrypt.rb +70 -0
- data/lib/lenc/repo.rb +1024 -0
- data/lib/lenc/tools.rb +632 -0
- data/lib/lenc/trollop.rb +782 -0
- data/test/test.rb +377 -0
- metadata +61 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a71c838f760ff31a7d853f32b8f9fdcc04621b56
|
4
|
+
data.tar.gz: e4ee1f83e5fd2b158ec5397703f02402f1b1a182
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1bc44af9c93ec3992406479f342e56146d8607f4f564ec594bdb67f23aa1f5e2b5afd31f0c2e32f76951d6e48ec615a0c24d86cadd50b703d98c2099c88a7da4
|
7
|
+
data.tar.gz: adf0c0379be49bbdb19ace972fa517f56bdf643ef19c51b5deb32376631dc19e098c0b6d301f1d62b5dc054078855efa69ebd8376f9ea806d94beefd2b5abb73
|
data/CHANGELOG.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
lenc : Maintains encrypted repositories of files, enabling secure, encrypted
|
2
|
+
backups to free cloud services such as Dropbox, Google Drive, and Microsoft SkyDrive.
|
3
|
+
|
4
|
+
Written and (c) by Jeff Sember, March 2013.
|
5
|
+
================================================================================
|
6
|
+
|
7
|
+
|
8
|
+
The program manipulates three distinct directories:
|
9
|
+
|
10
|
+
1) A <source> directory, which holds all the
|
11
|
+
files to be encrypted. The only file that the program modifies within this
|
12
|
+
directory tree is a hidden configuration file ".lenc" (on Windows, this file
|
13
|
+
is named "__lenc_repo__.txt").
|
14
|
+
|
15
|
+
2) An <encrypted> directory, where the program stores the encrypted
|
16
|
+
versions of all the files found in the <source> directory. This directory
|
17
|
+
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.
|
20
|
+
|
21
|
+
3) A <recover> directory. The program can recover a set of encrypted files here.
|
22
|
+
For safety, the this directory must not lie within an existing repository.
|
23
|
+
|
24
|
+
|
25
|
+
Running the program
|
26
|
+
================================================================================
|
27
|
+
|
28
|
+
The program can be asked to perform one of the following tasks:
|
29
|
+
|
30
|
+
1) Setting up a repository. Select a directory you wish to be the <source>
|
31
|
+
directory, and make it the current directory. Type:
|
32
|
+
|
33
|
+
lencrypt -i KEY ENCDIR
|
34
|
+
|
35
|
+
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
|
37
|
+
it cannot lie within the current directory's tree).
|
38
|
+
|
39
|
+
|
40
|
+
2) Updating a repository. From within a <source> directory tree, type:
|
41
|
+
|
42
|
+
lencrypt
|
43
|
+
|
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.
|
47
|
+
|
48
|
+
3) Recovering encrypted files. Type:
|
49
|
+
|
50
|
+
lencrypt -r KEY ENCDIR RECDIR
|
51
|
+
|
52
|
+
with KEY the key associated with a repository whose encrypted files are stored in ENCDIR.
|
53
|
+
The recovered files will be stored in RECDIR.
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
Ignore files
|
58
|
+
================================================================================
|
59
|
+
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
|
61
|
+
file '.lencignore' with a list of file or directory names (or patterns) to be
|
62
|
+
ignored. Example:
|
63
|
+
|
64
|
+
# This is a comment
|
65
|
+
#
|
66
|
+
log
|
67
|
+
*.mp3
|
68
|
+
_SKIP_*
|
69
|
+
|
70
|
+
This causes the program to ignore any file or directory named 'log', as well as
|
71
|
+
any ending with ".mp3" or starting with "_SKIP_".
|
72
|
+
|
73
|
+
Some files are automatically ignored, e.g. ".DS_Store".
|
74
|
+
|
75
|
+
The format of ignore files is similar to that of .gitignore files. Details:
|
76
|
+
|
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.
|
89
|
+
|
90
|
+
|
data/bin/lencrypt
ADDED
data/lib/lenc.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'lenc/lencrypt'
|
data/lib/lenc/aes.rb
ADDED
@@ -0,0 +1,523 @@
|
|
1
|
+
require_relative 'tools'
|
2
|
+
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module LEnc
|
6
|
+
class DecryptionError < Exception
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module RepoInternal
|
11
|
+
|
12
|
+
# --------------------------------------------------------------
|
13
|
+
|
14
|
+
AES_BLOCK_SIZE = 16
|
15
|
+
PAD_BYTE = 254
|
16
|
+
PAD_CHAR = PAD_BYTE.chr
|
17
|
+
|
18
|
+
CHUNK_HEADER_SIZE = 8
|
19
|
+
CHUNK_VERIFY_SIZE = CHUNK_HEADER_SIZE - 1
|
20
|
+
CHUNK_VERIFY_STR = 0.chr * CHUNK_VERIFY_SIZE
|
21
|
+
|
22
|
+
# Size of input chunks during decryption (they include space for a header);
|
23
|
+
# must be a multiple of AES_BLOCK_SIZE
|
24
|
+
CHUNK_SIZE_DECR = 1 << 16
|
25
|
+
|
26
|
+
# Size of input chunks during encryption
|
27
|
+
CHUNK_SIZE_ENCR = CHUNK_SIZE_DECR - CHUNK_HEADER_SIZE
|
28
|
+
|
29
|
+
# The size of nonce the underlying API expects
|
30
|
+
NONCE_SIZE_LARGE = 16
|
31
|
+
|
32
|
+
# The size of nonce we'll be using (we'll pad it out with zeros
|
33
|
+
# when a full size one is required)
|
34
|
+
NONCE_SIZE_SMALL = 8
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
=begin
|
39
|
+
Wrapper for OpenSSL AES cipher.
|
40
|
+
|
41
|
+
|
42
|
+
Usage for encryption:
|
43
|
+
----------------------------------------------------
|
44
|
+
original = "..." # bytes to encrypt (string)
|
45
|
+
|
46
|
+
key = "xxxx..." # encryption key (string of size 8..56)
|
47
|
+
|
48
|
+
en = MyAES.new(true, key) # construct an encryptor
|
49
|
+
|
50
|
+
en.finish(original) # add data to encrypt
|
51
|
+
|
52
|
+
encrypted = en.flush() # get encrypted bytes (string)
|
53
|
+
----------------------------------------------------
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
Usage for decryption:
|
58
|
+
----------------------------------------------------
|
59
|
+
encrypted = "..." # bytes to decrypt
|
60
|
+
|
61
|
+
key = "xxxx...."
|
62
|
+
|
63
|
+
de = MyAES.new(false, key) # construct a decryptor
|
64
|
+
|
65
|
+
de.finish(original) # add data to decrypt
|
66
|
+
|
67
|
+
decrypted = de.flush() # get decrypted bytes
|
68
|
+
|
69
|
+
----------------------------------------------------
|
70
|
+
|
71
|
+
|
72
|
+
Use of nonces:
|
73
|
+
--------------
|
74
|
+
The above encryption example generates a new (hopefully unique) 'nonce'
|
75
|
+
which is an added security feature. It uses the system clock to do this.
|
76
|
+
This means the same file will produce different encrypted byte streams on
|
77
|
+
repeated encryption attempts, which may be undesirable. A fixed nonce
|
78
|
+
(for a particular input file) can be specified as an additional input:
|
79
|
+
|
80
|
+
nonce = "nnnn.." # only the first 8 bytes are used
|
81
|
+
en = MyAES.new(true, key, nonce)
|
82
|
+
|
83
|
+
The nonce, whether explicitly given or randomly generated, is added to the
|
84
|
+
encrypted stream; hence it need not be specified when decrypting.
|
85
|
+
|
86
|
+
Stream mode:
|
87
|
+
------------
|
88
|
+
When processing large files, you may want to do them a chunk at a time.
|
89
|
+
Here's an example of encrypting using stream mode (decrypting is similar):
|
90
|
+
|
91
|
+
|
92
|
+
en = MyAES.new(true, key)
|
93
|
+
|
94
|
+
s = {size of input file}
|
95
|
+
n = 0
|
96
|
+
while n < s
|
97
|
+
c = [5000, s - n].min
|
98
|
+
en.add( {bytes n..n+c-1 from the input file} )
|
99
|
+
r = en.flush()
|
100
|
+
{append bytes r to output file}
|
101
|
+
end
|
102
|
+
|
103
|
+
en.finish()
|
104
|
+
r = en.flush()
|
105
|
+
{append bytes r to output file}
|
106
|
+
|
107
|
+
----------------------------------------------------
|
108
|
+
|
109
|
+
Format of encrypted data:
|
110
|
+
|
111
|
+
[8] nonce (only the first 8 bytes of the nonce are actually used)
|
112
|
+
|
113
|
+
Followed by one or more encrypted chunks of length [k], where k is 65536, unless it's the last
|
114
|
+
chunk in the file, in which case it must be a multiple of 16.
|
115
|
+
|
116
|
+
The first bytes of each decrypted chunk is a header:
|
117
|
+
[7] zeros
|
118
|
+
[1] number of padding bytes present at end of block
|
119
|
+
|
120
|
+
|
121
|
+
For example, suppose a file of 71980 'source' bytes has been encrypted. The encrypted file will contain:
|
122
|
+
[8] nonce
|
123
|
+
[65536] first chunk, consisting of
|
124
|
+
[7] zeros
|
125
|
+
[1] zero, since this chunk needed no padding
|
126
|
+
[65528] 65528 encrypted source bytes
|
127
|
+
[6464] second chunk, consisting of
|
128
|
+
[7] zeros
|
129
|
+
[1] 4, indicating 4 padding bytes
|
130
|
+
[6456] 6452 encrypted source bytes plus 4 padding bytes
|
131
|
+
|
132
|
+
Observe that 65528 + 6452 = 71980.
|
133
|
+
|
134
|
+
The purpose of the [7] zeros in the (decrypted) chunk header are to indicate
|
135
|
+
whether decryption was successful (e.g., if the password was correct). The assumption
|
136
|
+
is that an incorrect password will generate 7 zeros in these locations with extremely low probability.
|
137
|
+
|
138
|
+
The byte used as a padding byte is 254.
|
139
|
+
|
140
|
+
If a file has length zero, then when encrypted, it will have the following structure:
|
141
|
+
[8] nonce
|
142
|
+
[16] chunk:
|
143
|
+
[7] zeros
|
144
|
+
[1] 8, indicating 8 padding bytes
|
145
|
+
[8] 0 encrypted source bytes plus 8 padding bytes
|
146
|
+
|
147
|
+
=end
|
148
|
+
class MyAES
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
# decryptState values
|
153
|
+
DS_WAITNONCE = 0 # waiting for nonce to appear in input
|
154
|
+
DS_WAITCHUNK = 1 # waiting for encrypted chunk to appear
|
155
|
+
|
156
|
+
# A class variable that increments with each encryptor object constructed,
|
157
|
+
# to help generate unique nonces (in conjunction with system clock)
|
158
|
+
@@nonceHelper = 0
|
159
|
+
|
160
|
+
# Construct a MyAES object to encrypt/decrypt a sequence of bytes.
|
161
|
+
# @param encrypting true for encryption, false for decryption
|
162
|
+
# @param key a bytearray of 4..56 bytes
|
163
|
+
# @param nonce a string of up to 16 characters; if nil, one is
|
164
|
+
# generated from the system clock
|
165
|
+
def initialize(encrypting, key, nonce=nil)
|
166
|
+
|
167
|
+
@encrypting = encrypting
|
168
|
+
@inputBuffer = ''
|
169
|
+
@outputBuffer = ''
|
170
|
+
@finished = false
|
171
|
+
|
172
|
+
if nonce && !encrypting
|
173
|
+
raise ArgumentError, \
|
174
|
+
"nonce should not be supplied during decryption"
|
175
|
+
end
|
176
|
+
|
177
|
+
if @encrypting
|
178
|
+
@nonceWritten = false
|
179
|
+
else
|
180
|
+
@decryptState = DS_WAITNONCE
|
181
|
+
end
|
182
|
+
|
183
|
+
key = bytes_to_str(key)
|
184
|
+
|
185
|
+
if key.size < 4 || key.size > 56
|
186
|
+
raise ArgumentError, 'Key length not 4..56 bytes'
|
187
|
+
end
|
188
|
+
|
189
|
+
# expand the key to be at least 32 bytes
|
190
|
+
k = (32.0 / key.size).ceil.to_i
|
191
|
+
key = key * k
|
192
|
+
|
193
|
+
@key = key[0...32]
|
194
|
+
@chunkCount = 0
|
195
|
+
|
196
|
+
# If we are encrypting, set nonce; otherwise, we must wait for some data to be available
|
197
|
+
if @encrypting
|
198
|
+
setNonce(nonce)
|
199
|
+
else
|
200
|
+
@chunkExpected = true
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Set nonce
|
205
|
+
# @param nonce a string; if nil, uses system clock and an internal
|
206
|
+
# counter to generate (hopefully) a unique value
|
207
|
+
def setNonce(nonce=nil)
|
208
|
+
if !nonce
|
209
|
+
# use date-time if no counter provided
|
210
|
+
ni = int_to_bytes(Time.now.usec)
|
211
|
+
ni.concat(int_to_bytes(@@nonceHelper))
|
212
|
+
@@nonceHelper += 1
|
213
|
+
nonce = bytes_to_str(ni)
|
214
|
+
end
|
215
|
+
|
216
|
+
raise ArgumentError if !(nonce.is_a? String )
|
217
|
+
simple_str(nonce)
|
218
|
+
|
219
|
+
|
220
|
+
nonce = str_sized(nonce,NONCE_SIZE_SMALL)
|
221
|
+
|
222
|
+
@nonce = nonce
|
223
|
+
end
|
224
|
+
|
225
|
+
def incrNonce()
|
226
|
+
c = @nonce
|
227
|
+
dig = NONCE_SIZE_SMALL - 1
|
228
|
+
while true
|
229
|
+
raise ArgumentError, "Nonce overflow" if dig < 0
|
230
|
+
|
231
|
+
q = c[dig].ord
|
232
|
+
if q != 0xff
|
233
|
+
c[dig] = (q+1).chr
|
234
|
+
break
|
235
|
+
end
|
236
|
+
|
237
|
+
c[dig] = 0.chr
|
238
|
+
dig -= 1
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
# Convert nonce from our version to one the OpenSSL expects.
|
244
|
+
# This may involve padding or truncating it as necessary to a
|
245
|
+
# fixed length.
|
246
|
+
def cvtNonce()
|
247
|
+
str_sized(@nonce,NONCE_SIZE_LARGE)
|
248
|
+
end
|
249
|
+
|
250
|
+
def buildAES()
|
251
|
+
aes = OpenSSL::Cipher.new("AES-256-CBC")
|
252
|
+
aes.padding = 0 # Not sure this is required, as we are doing padding ourselves;
|
253
|
+
# we should only be asking it to process blocks that need no padding
|
254
|
+
aes
|
255
|
+
end
|
256
|
+
|
257
|
+
def processChunk()
|
258
|
+
|
259
|
+
if @encrypting
|
260
|
+
|
261
|
+
csize = [CHUNK_SIZE_ENCR, @inputBuffer.size].min
|
262
|
+
|
263
|
+
padBytes = (-(csize + CHUNK_HEADER_SIZE)) & (AES_BLOCK_SIZE - 1)
|
264
|
+
|
265
|
+
csize += padBytes
|
266
|
+
|
267
|
+
if padBytes
|
268
|
+
@inputBuffer << PAD_CHAR * padBytes
|
269
|
+
end
|
270
|
+
|
271
|
+
aes = buildAES()
|
272
|
+
aes.encrypt
|
273
|
+
aes.key = @key
|
274
|
+
nonceStr = cvtNonce
|
275
|
+
aes.iv = nonceStr
|
276
|
+
|
277
|
+
if not @nonceWritten
|
278
|
+
@nonceWritten = true
|
279
|
+
@outputBuffer << nonceStr[0...NONCE_SIZE_SMALL]
|
280
|
+
end
|
281
|
+
|
282
|
+
cdata = "\0" * CHUNK_VERIFY_SIZE
|
283
|
+
cdata << padBytes.chr
|
284
|
+
cdata << @inputBuffer.slice!(0,csize)
|
285
|
+
|
286
|
+
@outputBuffer << aes.update(cdata)
|
287
|
+
@outputBuffer << aes.final
|
288
|
+
|
289
|
+
else # Decrypting
|
290
|
+
|
291
|
+
csize = [CHUNK_SIZE_DECR,@inputBuffer.size].min
|
292
|
+
|
293
|
+
# verify that the chunk size is a nonzero multiple of AES_BLOCK_SIZE bytes
|
294
|
+
if not csize or 0 != (csize & (AES_BLOCK_SIZE - 1))
|
295
|
+
raise LEnc::DecryptionError, "chunk size not a multiple of block size"
|
296
|
+
end
|
297
|
+
|
298
|
+
aes = buildAES()
|
299
|
+
aes.decrypt
|
300
|
+
aes.key = @key
|
301
|
+
aes.iv = cvtNonce()
|
302
|
+
|
303
|
+
cdata = @inputBuffer[0...csize]
|
304
|
+
newData = aes.update(cdata)
|
305
|
+
newData << aes.final
|
306
|
+
|
307
|
+
if !newData.start_with? CHUNK_VERIFY_STR
|
308
|
+
raise LEnc::DecryptionError, "header doesn't verify"
|
309
|
+
end
|
310
|
+
|
311
|
+
nPadBytes = newData[CHUNK_VERIFY_SIZE].ord
|
312
|
+
actualEnd = csize - nPadBytes
|
313
|
+
if nPadBytes > 16 or actualEnd < CHUNK_HEADER_SIZE
|
314
|
+
raise LEnc::DecryptionError, "nPadBytes/actualEnd mismatch"
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
|
319
|
+
# Verify that the padding bytes have correct values
|
320
|
+
(actualEnd...csize).each do |i|
|
321
|
+
if newData[i] != PAD_CHAR
|
322
|
+
raise LEnc::DecryptionError,"padding char bad value"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
newData = newData[CHUNK_HEADER_SIZE ... actualEnd]
|
327
|
+
|
328
|
+
@decryptState = DS_WAITCHUNK
|
329
|
+
|
330
|
+
@inputBuffer.slice!(0,csize)
|
331
|
+
@outputBuffer << newData
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
incrNonce()
|
336
|
+
@chunkCount += 1
|
337
|
+
end
|
338
|
+
|
339
|
+
public
|
340
|
+
|
341
|
+
# Process additional input bytes, encrypting (or decrypting) its contents
|
342
|
+
# @param data string containing input bytes
|
343
|
+
def add(data)
|
344
|
+
|
345
|
+
raise IllegalStateException if @finished
|
346
|
+
|
347
|
+
simple_str(data)
|
348
|
+
|
349
|
+
@inputBuffer << data #.concat(data)
|
350
|
+
|
351
|
+
while true
|
352
|
+
|
353
|
+
if not @encrypting
|
354
|
+
|
355
|
+
# Extract nonce if we're waiting for it and it is now available
|
356
|
+
if @decryptState == DS_WAITNONCE
|
357
|
+
break if @inputBuffer.size < NONCE_SIZE_SMALL
|
358
|
+
setNonce(@inputBuffer.slice!(0...NONCE_SIZE_SMALL))
|
359
|
+
@decryptState = DS_WAITCHUNK
|
360
|
+
next
|
361
|
+
end
|
362
|
+
|
363
|
+
# If we don't have a full chunk, exit
|
364
|
+
# (the last chunk may be smaller; we'll test for this when finishing up)
|
365
|
+
break if @inputBuffer.size < CHUNK_SIZE_DECR
|
366
|
+
|
367
|
+
else
|
368
|
+
break if @inputBuffer.size < CHUNK_SIZE_ENCR
|
369
|
+
end
|
370
|
+
|
371
|
+
# Process chunk and repeat
|
372
|
+
processChunk()
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Stop the encryption/decryption process.
|
377
|
+
# Processes any bytes that may have been buffered (since encryption occurs in
|
378
|
+
# 16 byte blocks at a time).
|
379
|
+
#
|
380
|
+
# @param data optional final input string to process before finishing
|
381
|
+
#
|
382
|
+
def finish(data = nil)
|
383
|
+
|
384
|
+
add(data) if data
|
385
|
+
|
386
|
+
raise IllegalStateException if @finished
|
387
|
+
|
388
|
+
@finished = true
|
389
|
+
|
390
|
+
inpLen = @inputBuffer.size
|
391
|
+
|
392
|
+
if @encrypting
|
393
|
+
# If input buffer is not empty, or we haven't written a first chunk (which contains the nonce),
|
394
|
+
# encrypt a chunk
|
395
|
+
if inpLen or (not @nonceWritten)
|
396
|
+
processChunk()
|
397
|
+
end
|
398
|
+
else
|
399
|
+
|
400
|
+
# We must be at WAITCHUNK with an input buffer that is a multiple of _AES_BLOCK_SIZE bytes in length
|
401
|
+
if @decryptState != DS_WAITCHUNK or 0 != (inpLen & (AES_BLOCK_SIZE-1))
|
402
|
+
raise LEnc::DecryptionError, "decrypt state problem"
|
403
|
+
end
|
404
|
+
|
405
|
+
# We expect a chunk if there's more input, or if we've never processed a chunk.
|
406
|
+
if inpLen != 0 or @chunkCount == 0
|
407
|
+
processChunk()
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
|
413
|
+
# Return any output bytes that have been generated since the last call to flush()
|
414
|
+
# @return string containing bytes
|
415
|
+
def flush()
|
416
|
+
ret = @outputBuffer
|
417
|
+
@outputBuffer = ''
|
418
|
+
return ret
|
419
|
+
end
|
420
|
+
|
421
|
+
# Strip the header from an encrypted string
|
422
|
+
def strip_encryption_header( encr_str)
|
423
|
+
return encr_str[CHUNK_HEADER_SIZE..-1]
|
424
|
+
end
|
425
|
+
|
426
|
+
# Determines if a string is the start of an encrypted sequence
|
427
|
+
#
|
428
|
+
# @param key password to use (string)
|
429
|
+
# @param test_str the string to test
|
430
|
+
#
|
431
|
+
# Returns true iff the start of the string seems to decrypt correctly
|
432
|
+
# for the given password
|
433
|
+
def is_string_encrypted(key, test_str)
|
434
|
+
db = false
|
435
|
+
|
436
|
+
!db || hexDump(test_str, "areBytesEncrypted?")
|
437
|
+
|
438
|
+
simple_str(test_str)
|
439
|
+
|
440
|
+
lnth = test_str.size
|
441
|
+
lnth -= NONCE_SIZE_SMALL
|
442
|
+
if lnth < AES_BLOCK_SIZE
|
443
|
+
!db || pr(" insufficient # bytes\n")
|
444
|
+
return false
|
445
|
+
end
|
446
|
+
|
447
|
+
begin
|
448
|
+
de = MyAES(False, key)
|
449
|
+
de.finish(test_str[:_AES_BLOCK_SIZE + NONCE_SIZE_SMALL])
|
450
|
+
decr = de.flush()
|
451
|
+
!db || hexDump(decr,"decrypted successfully")
|
452
|
+
rescue LEnc::DecryptionError
|
453
|
+
!db || pr(" (caught DecryptionError)\n")
|
454
|
+
return false
|
455
|
+
end
|
456
|
+
|
457
|
+
true
|
458
|
+
end
|
459
|
+
|
460
|
+
# Determines if a file is an encrypted file
|
461
|
+
# @param key password to use (string, or array of bytes)
|
462
|
+
# @param path path to file
|
463
|
+
# Returns true iff the start of the file seems to decrypt correctly
|
464
|
+
# for the given password, and the file is of the expected length.
|
465
|
+
def is_file_encrypted(key, path)
|
466
|
+
|
467
|
+
# key = str_to_bytes(key)
|
468
|
+
|
469
|
+
if not File.file?(path)
|
470
|
+
return false
|
471
|
+
end
|
472
|
+
|
473
|
+
lnth = File.size(path)
|
474
|
+
minSize = NONCE_SIZE_SMALL + AES_BLOCK_SIZE
|
475
|
+
if lnth < minSize or ((lnth - minSize) % _AES_BLOCK_SIZE) != 0
|
476
|
+
return false
|
477
|
+
end
|
478
|
+
|
479
|
+
f = File.open(path,"rb")
|
480
|
+
return is_string_encrypted(key, f.read(minSize))
|
481
|
+
end
|
482
|
+
|
483
|
+
|
484
|
+
end # end of class MyAES
|
485
|
+
|
486
|
+
end # module RepoInternal
|
487
|
+
|
488
|
+
|
489
|
+
if main? __FILE__
|
490
|
+
|
491
|
+
s = ''
|
492
|
+
16.times {|x| s << (65+x).chr}
|
493
|
+
|
494
|
+
|
495
|
+
nonce = "abc" * 20
|
496
|
+
nonce = nonce[0...16]
|
497
|
+
key = "onefishtwofishredfishbluefish" * 3
|
498
|
+
key = key[0...32]
|
499
|
+
|
500
|
+
hex_dump(key,"key")
|
501
|
+
hex_dump(nonce,"nonce")
|
502
|
+
|
503
|
+
aes = OpenSSL::Cipher.new("AES-256-CBC")
|
504
|
+
|
505
|
+
aes.padding = 0
|
506
|
+
aes.encrypt
|
507
|
+
aes.key = key
|
508
|
+
|
509
|
+
aes.iv = nonce
|
510
|
+
|
511
|
+
enc = aes.update(s)
|
512
|
+
enc << aes.final
|
513
|
+
|
514
|
+
hex_dump(s,"calling aes.encrypt with")
|
515
|
+
hex_dump(enc,"aes.encrypt returned")
|
516
|
+
|
517
|
+
s = enc
|
518
|
+
|
519
|
+
require 'base64'
|
520
|
+
s = Base64.urlsafe_encode64(s)
|
521
|
+
hex_dump(s,"base64")
|
522
|
+
|
523
|
+
end
|