filesafe 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.
- data/README.txt +57 -0
- data/Rakefile +41 -0
- data/VERSION.txt +1 -0
- data/bin/filesafe +351 -0
- metadata +96 -0
data/README.txt
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
DESCRIPTION
|
2
|
+
|
3
|
+
FileSafe
|
4
|
+
|
5
|
+
Written by Aaron D. Gifford - http://www.aarongifford.com/
|
6
|
+
|
7
|
+
A simple Ruby script for encrypting/decrypting files using 256-bit AES
|
8
|
+
and a master key derived from a password/passphrase via the PBKDF2
|
9
|
+
function.
|
10
|
+
|
11
|
+
|
12
|
+
I wrote this script for use on several systems where I needed to
|
13
|
+
regularly encrypt/decrypt one or more files using a password or
|
14
|
+
passphrase. The method used should be reasonably secure for the uses I
|
15
|
+
required. I have NOT adapted the script (yet) for non-POSIX
|
16
|
+
environments (Windows) however.
|
17
|
+
|
18
|
+
This script was written and tested using Ruby 1.9.x. No attempts to
|
19
|
+
adapt or test it under earlier Ruby versions have been made.
|
20
|
+
|
21
|
+
|
22
|
+
LICENSE
|
23
|
+
|
24
|
+
This script is licensed under an MIT-style license. See the license header at the top of the script source code.
|
25
|
+
|
26
|
+
|
27
|
+
REQUIREMENTS
|
28
|
+
|
29
|
+
This script requires or relies on:
|
30
|
+
openssl -- encryption/HMAC/hash algorithms
|
31
|
+
securerandom -- cryptographically secure random data
|
32
|
+
tempfile -- for temporary file creation
|
33
|
+
|
34
|
+
It uses the following gems:
|
35
|
+
pbkdf2 -- for the password-based key derivitive function PBKDF2
|
36
|
+
highline -- for reading a password/passphrase from a terminal
|
37
|
+
|
38
|
+
|
39
|
+
WEB SITE
|
40
|
+
|
41
|
+
The latest version can be found at the author's web site:
|
42
|
+
|
43
|
+
* http://www.aarongifford.com/computers/filesafe/index.html
|
44
|
+
|
45
|
+
|
46
|
+
SUGGESTIONS / BUGS
|
47
|
+
|
48
|
+
Please report bugs by going to the author's web site and clicking on the
|
49
|
+
"Contact Me" link in the left-hand menu. The direct URL is:
|
50
|
+
|
51
|
+
* http://www.aarongifford.com/leaveanote.html
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
Thank you!
|
56
|
+
-- Aaron D. Gifford
|
57
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rake/gempackagetask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
gemspec = Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'filesafe'
|
6
|
+
spec.version = File.open('VERSION.txt','r').to_a.join.strip
|
7
|
+
spec.date = File.mtime('VERSION.txt')
|
8
|
+
spec.author = 'Aaron D. Gifford'
|
9
|
+
spec.homepage = 'http://www.aarongifford.com/computers/filesafe/'
|
10
|
+
spec.summary = 'Encrypt/decrypt files with a random 256-bit AES key secured by a passphrase derived master key using PBKDF2'
|
11
|
+
spec.description = 'A utility script for encrypting and decrypting files using a randomly generated 256-bit AES key and initialization vector secured using the PBKDF2 password/passphrase key derivation algorithm to secure the file key and IV.'
|
12
|
+
spec.has_rdoc = false ## No documentation yet
|
13
|
+
spec.extra_rdoc_files = [ 'README.txt' ]
|
14
|
+
spec.files = [
|
15
|
+
'README.txt',
|
16
|
+
'VERSION.txt',
|
17
|
+
'Rakefile',
|
18
|
+
'bin/filesafe'
|
19
|
+
]
|
20
|
+
spec.executables = [ 'filesafe' ]
|
21
|
+
spec.add_dependency('pbkdf2', '>= 0.1.0')
|
22
|
+
spec.add_dependency('highline', '>= 1.6.1')
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::GemPackageTask.new(gemspec) do |pkg|
|
26
|
+
pkg.need_zip = true
|
27
|
+
pkg.need_tar = true
|
28
|
+
end
|
29
|
+
|
30
|
+
Rake::RDocTask.new do |rdoc|
|
31
|
+
rdoc.name = 'rdoc'
|
32
|
+
rdoc.main = 'README.txt'
|
33
|
+
rdoc.rdoc_dir = 'doc'
|
34
|
+
rdoc.rdoc_files.include('README.txt')
|
35
|
+
end
|
36
|
+
|
37
|
+
task :default => [
|
38
|
+
'pkg/filesafe-' + File.open('VERSION.txt','r').to_a.join.strip + '.gem',
|
39
|
+
:rdoc
|
40
|
+
]
|
41
|
+
|
data/VERSION.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/filesafe
ADDED
@@ -0,0 +1,351 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# FileSafe
|
4
|
+
# Version: 1.0.0
|
5
|
+
# From: http://www.aarongifford.com/computers/filesafe/index.html
|
6
|
+
#
|
7
|
+
# A simple file encryption/decryption script that uses 256-bit AES encryption,
|
8
|
+
# a secure random number/data source, and the password/passphrase (via PBKDF2)
|
9
|
+
# to encrypt/decrypt one or more files.
|
10
|
+
#
|
11
|
+
# Written by Aaron D. Gifford - http://www.aarongifford.com/
|
12
|
+
# Copyright (c) 2010 Aaron D. Gifford
|
13
|
+
#
|
14
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
15
|
+
# of this software and associated documentation files (the "Software"), to deal
|
16
|
+
# in the Software without restriction, including without limitation the rights
|
17
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
18
|
+
# copies of the Software, and to permit persons to whom the Software is
|
19
|
+
# furnished to do so, subject to the following conditions:
|
20
|
+
#
|
21
|
+
# The above copyright notice and this permission notice shall be included in
|
22
|
+
# all copies or substantial portions of the Software.
|
23
|
+
#
|
24
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
25
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
26
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
27
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
28
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
29
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
30
|
+
# THE SOFTWARE.
|
31
|
+
|
32
|
+
require 'openssl' ## Encryption/HMAC/Hash algorithms
|
33
|
+
require 'securerandom' ## Cryptographically secure source of random data
|
34
|
+
require 'pbkdf2' ## PBKDF2 algorithm for key material generation
|
35
|
+
require 'highline' ## For reading a passphrase from a terminal
|
36
|
+
require 'tempfile' ## Temporary file creation
|
37
|
+
|
38
|
+
def usage(msg)
|
39
|
+
STDERR.puts <<-EOM
|
40
|
+
ERROR: #{msg}
|
41
|
+
|
42
|
+
Usage: #{$0} -e|-d [-n] [-p 'pass phrase'] [--] file [file [file...]]
|
43
|
+
-e Encrypt file(s)
|
44
|
+
-d Decrypt file(s)
|
45
|
+
-p 'pass phrase' Passphrase to use for operation
|
46
|
+
-n Do NOT create a temporary password HMAC/hash file (during decryption)
|
47
|
+
-- End of options -- all subsequent arguments are files
|
48
|
+
|
49
|
+
EOM
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
opt = {}
|
54
|
+
while ARGV.size > 0 && ARGV[0][0,1] == '-'
|
55
|
+
case ARGV[0]
|
56
|
+
when '-e'
|
57
|
+
ARGV.shift
|
58
|
+
usage "Please only specify ONE -d or -e option." if opt.key?(:op)
|
59
|
+
opt[:op] = :encrypt
|
60
|
+
when '-d'
|
61
|
+
ARGV.shift
|
62
|
+
usage "Please only specify ONE -d or -e option." if opt.key?(:op)
|
63
|
+
opt[:op] = :decrypt
|
64
|
+
when '-p'
|
65
|
+
ARGV.shift
|
66
|
+
usage "Please specify a passphrase and one or more files." if ARGV.size < 2
|
67
|
+
opt[:phrase] = ARGV.shift
|
68
|
+
when '-n'
|
69
|
+
ARV.shift
|
70
|
+
usage "This option only applies to decryption operations." unless opt.key?(:decrypt)
|
71
|
+
opt[:notemp] = true
|
72
|
+
when '--'
|
73
|
+
break
|
74
|
+
else
|
75
|
+
usage "Unrecognized option: #{ARGV[0].inspect}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
usage "You did not specify a -e encrypt or -d decrypt operation." unless opt.key?(:op)
|
79
|
+
usage "You must specify one or more files to operate on." if ARGV.size == 0
|
80
|
+
|
81
|
+
|
82
|
+
## CONFIGURATION ITEMS:
|
83
|
+
PASSHASH_SUFFIX = '.pass'
|
84
|
+
CIPHER = 'aes-256-cbc'
|
85
|
+
cipher = OpenSSL::Cipher::Cipher.new(CIPHER)
|
86
|
+
BLOCK_LEN = cipher.block_size
|
87
|
+
KEY_LEN = cipher.key_len
|
88
|
+
IV_LEN = cipher.iv_len
|
89
|
+
SALT_LEN = KEY_LEN + IV_LEN
|
90
|
+
HMAC_FUNC = 'sha512'
|
91
|
+
HMAC_LEN = OpenSSL::HMAC.new('', HMAC_FUNC).digest.bytesize
|
92
|
+
HEADER_LEN = KEY_LEN + IV_LEN + SALT_LEN + HMAC_LEN
|
93
|
+
ITERATIONS = 4096
|
94
|
+
FILE_CHUNK_LEN = 65536
|
95
|
+
|
96
|
+
def getphrase(check=false)
|
97
|
+
begin
|
98
|
+
phrase = HighLine.new.ask('Passphrase: '){|q| q.echo = '*' ; q.overwrite = true }
|
99
|
+
return phrase unless check
|
100
|
+
tmp = HighLine.new.ask('Retype passphrase: '){|q| q.echo = '*' ; q.overwrite = true }
|
101
|
+
return phrase if tmp == phrase
|
102
|
+
rescue Interrupt
|
103
|
+
exit -1
|
104
|
+
end while true
|
105
|
+
end
|
106
|
+
|
107
|
+
def encrypt_file(file, passphrase=nil)
|
108
|
+
raise "Cannot encrypt non-existent file: #{file.inspect}" unless File.exist?(file)
|
109
|
+
raise "Cannot encrypt unreadable file: #{file.inspect}" unless File.readable?(file)
|
110
|
+
raise "Cannot encrypt unwritable file: #{file.inspect}" unless File.writable?(file)
|
111
|
+
passhash = false
|
112
|
+
if File.exist?(file + PASSHASH_SUFFIX)
|
113
|
+
raise "Cannot read password hash temporary file: #{(file + PASSHASH_SUFFIX).inspect}" unless File.readable?(file + PASSHASH_SUFFIX)
|
114
|
+
raise "Password hash temporary file length is invalid: #{(file + PASSHASH_SUFFIX).inspect}" unless File.size(file + PASSHASH_SUFFIX) == SALT_LEN + HMAC_LEN
|
115
|
+
fp = File.open(file + PASSHASH_SUFFIX, File::RDONLY)
|
116
|
+
salt = fp.read(SALT_LEN)
|
117
|
+
passcheck = fp.read(HMAC_LEN)
|
118
|
+
loop do
|
119
|
+
passphrase = getphrase if passphrase.nil?
|
120
|
+
pbkdf2data = PBKDF2.new do |p|
|
121
|
+
p.hash_function = HMAC_FUNC
|
122
|
+
p.password = passphrase
|
123
|
+
p.salt = salt
|
124
|
+
p.iterations = ITERATIONS
|
125
|
+
p.key_length = HMAC_LEN
|
126
|
+
end.bin_string
|
127
|
+
break if passcheck == pbkdf2data
|
128
|
+
puts "*** ERROR: Passphrase mismatch. Try again, abort, or delete temporary file: #{file + PASSHASH_SUFFIX}"
|
129
|
+
passphrase = nil
|
130
|
+
end
|
131
|
+
passhash = true
|
132
|
+
elsif passphrase.nil?
|
133
|
+
puts "*** ALERT: Enter your NEW passphrase twice. DO NOT FORGET IT, or you may lose your data!"
|
134
|
+
passphrase = getphrase(true)
|
135
|
+
end
|
136
|
+
|
137
|
+
## Use secure random data to populate salt, key, and IV:
|
138
|
+
salt = SecureRandom.random_bytes(SALT_LEN) ## Acquire some fresh salt
|
139
|
+
file_key = SecureRandom.random_bytes(KEY_LEN) ## Get some random key material
|
140
|
+
file_iv = SecureRandom.random_bytes(IV_LEN) ## And a random initialization vector
|
141
|
+
|
142
|
+
## Encrypt the file key and IV using password-derived keying material:
|
143
|
+
keymaterial = PBKDF2.new do |p|
|
144
|
+
p.hash_function = HMAC_FUNC
|
145
|
+
p.password = passphrase
|
146
|
+
p.salt = salt
|
147
|
+
p.iterations = ITERATIONS
|
148
|
+
p.key_length = KEY_LEN + IV_LEN
|
149
|
+
end.bin_string
|
150
|
+
cipher = OpenSSL::Cipher::Cipher.new(CIPHER)
|
151
|
+
cipher.encrypt
|
152
|
+
## No padding required for this operation since the file key + IV is
|
153
|
+
## an exact multiple of the cipher block length:
|
154
|
+
cipher.padding = 0
|
155
|
+
cipher.key = keymaterial[0,KEY_LEN]
|
156
|
+
cipher.iv = keymaterial[KEY_LEN,IV_LEN]
|
157
|
+
encrypted_keymaterial = cipher.update(file_key + file_iv) + cipher.final
|
158
|
+
encrypted_file_key = encrypted_keymaterial[0,KEY_LEN]
|
159
|
+
encrypted_file_iv = encrypted_keymaterial[KEY_LEN,IV_LEN]
|
160
|
+
|
161
|
+
## Open the plaintext file for reading (and later overwriting):
|
162
|
+
rfp = File.open(file, File::RDWR|File::EXCL)
|
163
|
+
|
164
|
+
## Open a temporary ciphertext file for writing:
|
165
|
+
wfp = Tempfile.new(File.basename(rfp.path), File.dirname(rfp.path))
|
166
|
+
|
167
|
+
## Write the salt and encrypted file key + IV and
|
168
|
+
## temporarily fill the HMAC slot with zero-bytes:
|
169
|
+
wfp.write(salt + encrypted_file_key + encrypted_file_iv + (0.chr * HMAC_LEN))
|
170
|
+
|
171
|
+
## Start the HMAC:
|
172
|
+
hmac = OpenSSL::HMAC.new(passphrase, HMAC_FUNC)
|
173
|
+
hmac << salt
|
174
|
+
hmac << encrypted_file_key
|
175
|
+
hmac << encrypted_file_iv
|
176
|
+
|
177
|
+
## Encrypt file with file key + IV:
|
178
|
+
cipher = OpenSSL::Cipher::Cipher.new(CIPHER)
|
179
|
+
cipher.encrypt
|
180
|
+
## Encryption of file contents uses PCKS#5 padding which OpenSSL should
|
181
|
+
## have enabled by default. Nevertheless, we explicitly enable it here:
|
182
|
+
cipher.padding = 1
|
183
|
+
cipher.key = file_key
|
184
|
+
cipher.iv = file_iv
|
185
|
+
until rfp.eof?
|
186
|
+
data = rfp.read(FILE_CHUNK_LEN)
|
187
|
+
if data.bytesize > 0
|
188
|
+
data = cipher.update(data)
|
189
|
+
hmac << data
|
190
|
+
wfp.write(data)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
data = cipher.final
|
194
|
+
if data.bytesize > 0
|
195
|
+
## Save the last bit-o-data and update the HMAC:
|
196
|
+
wfp.write(data)
|
197
|
+
hmac << data
|
198
|
+
end
|
199
|
+
|
200
|
+
## Write HMAC digest to file:
|
201
|
+
wfp.pos = SALT_LEN + KEY_LEN + IV_LEN
|
202
|
+
wfp.write(hmac.digest)
|
203
|
+
|
204
|
+
## Overwrite the original plaintext file with zero bytes.
|
205
|
+
## This adds a small measure of security against recovering
|
206
|
+
## the original unencrypted contents. It would likely be
|
207
|
+
## better to overwrite the file multiple times with different
|
208
|
+
## bit patterns, including one or more iterations using
|
209
|
+
## high-quality random data.
|
210
|
+
rfp.seek(0,File::SEEK_END)
|
211
|
+
fsize = rfp.pos
|
212
|
+
rfp.pos = 0
|
213
|
+
while rfp.pos + FILE_CHUNK_LEN < fsize
|
214
|
+
rfp.write(0.chr * FILE_CHUNK_LEN)
|
215
|
+
end
|
216
|
+
rfp.write(0.chr * (fsize - rfp.pos)) if rfp.pos < fsize
|
217
|
+
rfp.close
|
218
|
+
|
219
|
+
## Copy file ownership/permissions:
|
220
|
+
stat = File.stat(rfp.path)
|
221
|
+
wfp.chown(stat.uid, stat.gid)
|
222
|
+
wfp.chmod(stat.mode)
|
223
|
+
|
224
|
+
## Close the ciphertext temporary file without deleting:
|
225
|
+
wfp.close(false)
|
226
|
+
|
227
|
+
## Rename temporary file to permanent name:
|
228
|
+
File.rename(wfp.path, rfp.path)
|
229
|
+
|
230
|
+
## Remove password hash temp. file:
|
231
|
+
File.delete(file + PASSHASH_SUFFIX) if passhash
|
232
|
+
|
233
|
+
puts "ENCRYPTED: #{file}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def decrypt_file(file, passphrase=nil, notemp=nil)
|
237
|
+
raise "Cannot decrypt non-existent file: #{file.inspect}" unless File.exist?(file)
|
238
|
+
raise "Cannot decrypt unreadable file: #{file.inspect}" unless File.readable?(file)
|
239
|
+
raise "Cannot decrypt unwritable file: #{file.inspect}" unless File.writable?(file)
|
240
|
+
fsize = File.size(file)
|
241
|
+
raise "File is not in valid encrypted format: #{file.inspect}" unless fsize > HEADER_LEN && (fsize - HEADER_LEN) % BLOCK_LEN == 0
|
242
|
+
salt = encrypted_file_key = encrypted_file_iv = nil
|
243
|
+
loop do
|
244
|
+
passphrase = getphrase if passphrase.nil?
|
245
|
+
fp = File.open(file, File::RDONLY)
|
246
|
+
salt = fp.read(SALT_LEN)
|
247
|
+
encrypted_file_key = fp.read(KEY_LEN)
|
248
|
+
encrypted_file_iv = fp.read(IV_LEN)
|
249
|
+
file_hmac = fp.read(HMAC_LEN)
|
250
|
+
test_hmac = OpenSSL::HMAC.new(passphrase, HMAC_FUNC)
|
251
|
+
test_hmac << salt
|
252
|
+
test_hmac << encrypted_file_key
|
253
|
+
test_hmac << encrypted_file_iv
|
254
|
+
until fp.eof?
|
255
|
+
data = fp.read(FILE_CHUNK_LEN)
|
256
|
+
test_hmac << data unless data.bytesize == 0
|
257
|
+
end
|
258
|
+
fp.close
|
259
|
+
break if test_hmac.digest == file_hmac
|
260
|
+
puts "*** ERROR: Incorrect passphrase, or file is not encrypted. Try again or abort."
|
261
|
+
passphrase = nil
|
262
|
+
end
|
263
|
+
|
264
|
+
## Extract and decrypt the encrypted file key + IV.
|
265
|
+
## First, regenerate the password-based key material that encrypts the
|
266
|
+
## file key + IV:
|
267
|
+
keymaterial = PBKDF2.new do |p|
|
268
|
+
p.hash_function = HMAC_FUNC
|
269
|
+
p.password = passphrase
|
270
|
+
p.salt = salt
|
271
|
+
p.iterations = ITERATIONS
|
272
|
+
p.key_length = KEY_LEN + IV_LEN
|
273
|
+
end.bin_string
|
274
|
+
cipher = OpenSSL::Cipher::Cipher.new(CIPHER)
|
275
|
+
cipher.decrypt
|
276
|
+
cipher.padding = 0 ## No padding is required for this operation
|
277
|
+
cipher.key = keymaterial[0,KEY_LEN]
|
278
|
+
cipher.iv = keymaterial[KEY_LEN,IV_LEN]
|
279
|
+
## Decrypt file key + IV:
|
280
|
+
keymaterial = cipher.update(encrypted_file_key + encrypted_file_iv) + cipher.final
|
281
|
+
file_key = keymaterial[0,KEY_LEN]
|
282
|
+
file_iv = keymaterial[KEY_LEN,IV_LEN]
|
283
|
+
|
284
|
+
## Decrypt file:
|
285
|
+
cipher = OpenSSL::Cipher::Cipher.new(CIPHER)
|
286
|
+
cipher.decrypt
|
287
|
+
cipher.padding = 1 ## File contents use PCKS#5 padding,OpenSSL's default method
|
288
|
+
cipher.key = file_key
|
289
|
+
cipher.iv = file_iv
|
290
|
+
|
291
|
+
## Open ciphertext file for reading:
|
292
|
+
rfp = File.open(file, File::RDONLY|File::EXCL)
|
293
|
+
|
294
|
+
## Open a temporary plaintext file for writing:
|
295
|
+
wfp = Tempfile.new(File.basename(rfp.path), File.dirname(rfp.path))
|
296
|
+
|
297
|
+
## Begin reading the ciphertext beyond the headers:
|
298
|
+
rfp.pos = HEADER_LEN ## Skip headers
|
299
|
+
until rfp.eof?
|
300
|
+
data = rfp.read(FILE_CHUNK_LEN)
|
301
|
+
if data.bytesize > 0
|
302
|
+
data = cipher.update(data)
|
303
|
+
wfp.write(data)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
data = cipher.final
|
307
|
+
wfp.write(data) if data.bytesize > 0
|
308
|
+
|
309
|
+
## Close the ciphertext source file:
|
310
|
+
rfp.close
|
311
|
+
|
312
|
+
## Copy file ownership/permissions:
|
313
|
+
stat = File.stat(rfp.path)
|
314
|
+
wfp.chown(stat.uid, stat.gid)
|
315
|
+
wfp.chmod(stat.mode)
|
316
|
+
|
317
|
+
## Close the plaintext temporary file without deleting:
|
318
|
+
wfp.close(false)
|
319
|
+
|
320
|
+
## Rename temporary file to permanent name:
|
321
|
+
File.rename(wfp.path, rfp.path)
|
322
|
+
|
323
|
+
if notemp.nil?
|
324
|
+
## Write password hash temp. file using PBKDF2 as an iterated hash of sorts of HMAC_LEN bytes:
|
325
|
+
salt = SecureRandom.random_bytes(SALT_LEN) ## Grab a new chunk of random data
|
326
|
+
pbkdf2data = PBKDF2.new do |p|
|
327
|
+
p.hash_function = HMAC_FUNC
|
328
|
+
p.password = passphrase
|
329
|
+
p.salt = salt
|
330
|
+
p.iterations = ITERATIONS
|
331
|
+
p.key_length = HMAC_LEN
|
332
|
+
end.bin_string
|
333
|
+
File.open(file + PASSHASH_SUFFIX, File::WRONLY|File::EXCL|File::CREAT) {|f| f.write(salt + pbkdf2data)}
|
334
|
+
end
|
335
|
+
|
336
|
+
puts "FILE DECRYPTED: #{file}"
|
337
|
+
end
|
338
|
+
|
339
|
+
ARGV.each do |file|
|
340
|
+
begin
|
341
|
+
if opt[:op] == :decrypt
|
342
|
+
decrypt_file(file, opt[:phrase], opt[:notemp])
|
343
|
+
else
|
344
|
+
encrypt_file(file, opt[:phrase])
|
345
|
+
end
|
346
|
+
rescue StandardError => e
|
347
|
+
STDERR.puts "*** Error while working on file #{file.inspect}: #{e}"
|
348
|
+
exit -1
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: filesafe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Aaron D. Gifford
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-30 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: pbkdf2
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 1
|
31
|
+
- 0
|
32
|
+
version: 0.1.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: highline
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 1
|
45
|
+
- 6
|
46
|
+
- 1
|
47
|
+
version: 1.6.1
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
description: A utility script for encrypting and decrypting files using a randomly generated 256-bit AES key and initialization vector secured using the PBKDF2 password/passphrase key derivation algorithm to secure the file key and IV.
|
51
|
+
email:
|
52
|
+
executables:
|
53
|
+
- filesafe
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files:
|
57
|
+
- README.txt
|
58
|
+
files:
|
59
|
+
- README.txt
|
60
|
+
- VERSION.txt
|
61
|
+
- Rakefile
|
62
|
+
- bin/filesafe
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://www.aarongifford.com/computers/filesafe/
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
version: "0"
|
88
|
+
requirements: []
|
89
|
+
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.3.7
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Encrypt/decrypt files with a random 256-bit AES key secured by a passphrase derived master key using PBKDF2
|
95
|
+
test_files: []
|
96
|
+
|