lockbox 0.4.9 → 0.5.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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +52 -20
- data/lib/lockbox.rb +1 -0
- data/lib/lockbox/aes_gcm.rb +4 -5
- data/lib/lockbox/box.rb +3 -4
- data/lib/lockbox/calculations.rb +2 -1
- data/lib/lockbox/model.rb +3 -4
- data/lib/lockbox/utils.rb +7 -5
- data/lib/lockbox/version.rb +1 -1
- metadata +36 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '00428f366a284e5e69806c18ef64a7bb66cdc0b2c1cb3e19528aa6bca99becea'
|
|
4
|
+
data.tar.gz: b5235c68771e0cb39653780dd4e08bdb27218f914b3889cecf959bc02efec3a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd44c7b55ea270aa02fa5a4eda5aa38bde97eca39ce54093b6fc4907abe90e946827b4c12a8f5f43f21b6503ea9d1c713f505a86c9a6ea4075b1407b5630aeca
|
|
7
|
+
data.tar.gz: 0eba333d509de09a92cd1af74427cce30eca35d7eab41abddada5718227c5a1f3f013b3ad031f8015ca5e13c4b47f5aebe75c09d83f6bd91926ad7d55db6a1e4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 0.5.0 (2020-11-22)
|
|
2
|
+
|
|
3
|
+
- Improved error messages for hybrid cryptography
|
|
4
|
+
- Changed warning to error when no attributes specified
|
|
5
|
+
- Fixed issue with `pluck` when migrating
|
|
6
|
+
- Fixed error with `key_table` and `key_attribute` options with `previous_versions`
|
|
7
|
+
|
|
1
8
|
## 0.4.9 (2020-10-01)
|
|
2
9
|
|
|
3
10
|
- Added `key_table` and `key_attribute` options to `previous_versions`
|
data/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# Lockbox
|
|
2
2
|
|
|
3
|
-
:package: Modern encryption for Rails
|
|
3
|
+
:package: Modern encryption for Ruby and Rails
|
|
4
4
|
|
|
5
5
|
- Works with database fields, files, and strings
|
|
6
6
|
- Maximizes compatibility with existing code and libraries
|
|
7
7
|
- Makes migrating existing data and key rotation easy
|
|
8
|
+
- Has zero dependencies and many integrations
|
|
8
9
|
|
|
9
10
|
Learn [the principles behind it](https://ankane.org/modern-encryption-rails), [how to secure emails with Devise](https://ankane.org/securing-user-emails-lockbox), and [how to secure sensitive data in Rails](https://ankane.org/sensitive-data-rails).
|
|
10
11
|
|
|
11
|
-
[](https://github.com/ankane/lockbox/actions)
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -413,44 +414,58 @@ Finally, delete the unencrypted files and drop the column for the original uploa
|
|
|
413
414
|
|
|
414
415
|
## Shrine
|
|
415
416
|
|
|
416
|
-
|
|
417
|
+
#### Models
|
|
418
|
+
|
|
419
|
+
Include the attachment as normal:
|
|
417
420
|
|
|
418
421
|
```ruby
|
|
419
|
-
|
|
422
|
+
class User < ApplicationRecord
|
|
423
|
+
include LicenseUploader::Attachment(:license)
|
|
424
|
+
end
|
|
420
425
|
```
|
|
421
426
|
|
|
422
|
-
|
|
427
|
+
And encrypt in a controller (or background job, etc) with:
|
|
423
428
|
|
|
424
429
|
```ruby
|
|
425
|
-
|
|
430
|
+
license = params.require(:user).fetch(:license)
|
|
431
|
+
lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license"))
|
|
432
|
+
user.license = lockbox.encrypt_io(license)
|
|
426
433
|
```
|
|
427
434
|
|
|
428
|
-
|
|
435
|
+
To serve encrypted files, use a controller action.
|
|
429
436
|
|
|
430
437
|
```ruby
|
|
431
|
-
|
|
438
|
+
def license
|
|
439
|
+
user = User.find(params[:id])
|
|
440
|
+
lockbox = Lockbox.new(key: Lockbox.attribute_key(table: "users", attribute: "license"))
|
|
441
|
+
send_data lockbox.decrypt(user.license.read), type: user.license.mime_type
|
|
442
|
+
end
|
|
432
443
|
```
|
|
433
444
|
|
|
434
|
-
|
|
445
|
+
#### Non-Models
|
|
446
|
+
|
|
447
|
+
Generate a key
|
|
435
448
|
|
|
436
449
|
```ruby
|
|
437
|
-
|
|
450
|
+
key = Lockbox.generate_key
|
|
438
451
|
```
|
|
439
452
|
|
|
440
|
-
|
|
453
|
+
Create a lockbox
|
|
441
454
|
|
|
442
455
|
```ruby
|
|
443
|
-
|
|
444
|
-
user.license = lockbox.encrypt_io(license)
|
|
456
|
+
lockbox = Lockbox.new(key: key)
|
|
445
457
|
```
|
|
446
458
|
|
|
447
|
-
|
|
459
|
+
Encrypt files before passing them to Shrine
|
|
448
460
|
|
|
449
461
|
```ruby
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
462
|
+
LicenseUploader.upload(lockbox.encrypt_io(file), :store)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
And decrypt them after reading
|
|
466
|
+
|
|
467
|
+
```ruby
|
|
468
|
+
lockbox.decrypt(uploaded_file.read)
|
|
454
469
|
```
|
|
455
470
|
|
|
456
471
|
## Local Files
|
|
@@ -655,6 +670,8 @@ This is the default algorithm. It’s:
|
|
|
655
670
|
- an IETF standard
|
|
656
671
|
- fast thanks to a [dedicated instruction set](https://en.wikipedia.org/wiki/AES_instruction_set)
|
|
657
672
|
|
|
673
|
+
Lockbox uses 256-bit keys.
|
|
674
|
+
|
|
658
675
|
**For users who do a lot of encryptions:** You should rotate an individual key after 2 billion encryptions to minimize the chance of a [nonce collision](https://www.cryptologie.net/article/402/is-symmetric-security-solved/), which will expose the key. Each database field and file uploader use a different key (derived from the master key) to extend this window.
|
|
659
676
|
|
|
660
677
|
### XSalsa20
|
|
@@ -708,6 +725,22 @@ For Ubuntu 16.04, use:
|
|
|
708
725
|
sudo apt-get install libsodium18
|
|
709
726
|
```
|
|
710
727
|
|
|
728
|
+
##### GitHub Actions
|
|
729
|
+
|
|
730
|
+
For Ubuntu 20.04 and 18.04, use:
|
|
731
|
+
|
|
732
|
+
```yml
|
|
733
|
+
- name: Install Libsodium
|
|
734
|
+
run: sudo apt-get install libsodium23
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
For Ubuntu 16.04, use:
|
|
738
|
+
|
|
739
|
+
```yml
|
|
740
|
+
- name: Install Libsodium
|
|
741
|
+
run: sudo apt-get install libsodium18
|
|
742
|
+
```
|
|
743
|
+
|
|
711
744
|
##### Travis CI
|
|
712
745
|
|
|
713
746
|
On Bionic, add to `.travis.yml`:
|
|
@@ -735,8 +768,7 @@ Add a step to `.circleci/config.yml`:
|
|
|
735
768
|
```yml
|
|
736
769
|
- run:
|
|
737
770
|
name: install Libsodium
|
|
738
|
-
command:
|
|
739
|
-
sudo apt-get install -y libsodium18
|
|
771
|
+
command: sudo apt-get install -y libsodium18
|
|
740
772
|
```
|
|
741
773
|
|
|
742
774
|
## Hybrid Cryptography
|
data/lib/lockbox.rb
CHANGED
data/lib/lockbox/aes_gcm.rb
CHANGED
|
@@ -43,11 +43,10 @@ module Lockbox
|
|
|
43
43
|
cipher.auth_data = associated_data || ""
|
|
44
44
|
|
|
45
45
|
begin
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
46
|
+
message = String.new
|
|
47
|
+
message << cipher.update(ciphertext) unless ciphertext.to_s.empty?
|
|
48
|
+
message << cipher.final
|
|
49
|
+
message
|
|
51
50
|
rescue OpenSSL::Cipher::CipherError
|
|
52
51
|
fail_decryption
|
|
53
52
|
end
|
data/lib/lockbox/box.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Lockbox
|
|
2
2
|
class Box
|
|
3
3
|
def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil, padding: false)
|
|
4
|
-
raise ArgumentError, "Cannot pass both key and
|
|
4
|
+
raise ArgumentError, "Cannot pass both key and encryption/decryption key" if key && (encryption_key || decryption_key)
|
|
5
5
|
|
|
6
6
|
key = Lockbox::Utils.decode_key(key) if key
|
|
7
7
|
encryption_key = Lockbox::Utils.decode_key(encryption_key, size: 64) if encryption_key
|
|
@@ -12,7 +12,6 @@ module Lockbox
|
|
|
12
12
|
case algorithm
|
|
13
13
|
when "aes-gcm"
|
|
14
14
|
raise ArgumentError, "Missing key" unless key
|
|
15
|
-
require "lockbox/aes_gcm"
|
|
16
15
|
@box = AES_GCM.new(key)
|
|
17
16
|
when "xchacha20"
|
|
18
17
|
raise ArgumentError, "Missing key" unless key
|
|
@@ -39,7 +38,7 @@ module Lockbox
|
|
|
39
38
|
message = Lockbox.pad(message, size: @padding) if @padding
|
|
40
39
|
case @algorithm
|
|
41
40
|
when "hybrid"
|
|
42
|
-
raise ArgumentError, "No
|
|
41
|
+
raise ArgumentError, "No encryption key set" unless defined?(@encryption_box)
|
|
43
42
|
raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
|
|
44
43
|
nonce = generate_nonce(@encryption_box)
|
|
45
44
|
ciphertext = @encryption_box.encrypt(nonce, message)
|
|
@@ -58,7 +57,7 @@ module Lockbox
|
|
|
58
57
|
message =
|
|
59
58
|
case @algorithm
|
|
60
59
|
when "hybrid"
|
|
61
|
-
raise ArgumentError, "No
|
|
60
|
+
raise ArgumentError, "No decryption key set" unless defined?(@decryption_box)
|
|
62
61
|
raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
|
|
63
62
|
nonce, ciphertext = extract_nonce(@decryption_box, ciphertext)
|
|
64
63
|
@decryption_box.decrypt(nonce, ciphertext)
|
data/lib/lockbox/calculations.rb
CHANGED
|
@@ -3,7 +3,8 @@ module Lockbox
|
|
|
3
3
|
def pluck(*column_names)
|
|
4
4
|
return super unless model.respond_to?(:lockbox_attributes)
|
|
5
5
|
|
|
6
|
-
lockbox_columns = column_names.map.with_index { |c, i| [model.lockbox_attributes[c.to_sym], i] }.select
|
|
6
|
+
lockbox_columns = column_names.map.with_index { |c, i| [model.lockbox_attributes[c.to_sym], i] }.select { |la, _i| la && !la[:migrating] }
|
|
7
|
+
|
|
7
8
|
return super unless lockbox_columns.any?
|
|
8
9
|
|
|
9
10
|
# replace column with ciphertext column
|
data/lib/lockbox/model.rb
CHANGED
|
@@ -27,8 +27,7 @@ module Lockbox
|
|
|
27
27
|
activerecord = defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
|
28
28
|
raise ArgumentError, "Type not supported yet with Mongoid" if options[:type] && !activerecord
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
warn "[lockbox] WARNING: No attributes specified" if attributes.empty?
|
|
30
|
+
raise ArgumentError, "No attributes specified" if attributes.empty?
|
|
32
31
|
|
|
33
32
|
raise ArgumentError, "Cannot use key_attribute with multiple attributes" if options[:key_attribute] && attributes.size > 1
|
|
34
33
|
|
|
@@ -353,7 +352,7 @@ module Lockbox
|
|
|
353
352
|
table = activerecord ? table_name : collection_name.to_s
|
|
354
353
|
|
|
355
354
|
unless message.nil?
|
|
356
|
-
# TODO use attribute type class in 0.
|
|
355
|
+
# TODO use attribute type class in 0.6.0
|
|
357
356
|
case options[:type]
|
|
358
357
|
when :boolean
|
|
359
358
|
message = ActiveRecord::Type::Boolean.new.serialize(message)
|
|
@@ -408,7 +407,7 @@ module Lockbox
|
|
|
408
407
|
end
|
|
409
408
|
|
|
410
409
|
unless message.nil?
|
|
411
|
-
# TODO use attribute type class in 0.
|
|
410
|
+
# TODO use attribute type class in 0.6.0
|
|
412
411
|
case options[:type]
|
|
413
412
|
when :boolean
|
|
414
413
|
message = message == "t"
|
data/lib/lockbox/utils.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Lockbox
|
|
2
2
|
class Utils
|
|
3
3
|
def self.build_box(context, options, table, attribute)
|
|
4
|
+
# dup options (with except) since keys are sometimes changed or deleted
|
|
4
5
|
options = options.except(:attribute, :encrypted_attribute, :migrating, :attached, :type)
|
|
5
6
|
options[:encode] = false unless options.key?(:encode)
|
|
6
7
|
options.each do |k, v|
|
|
@@ -26,9 +27,11 @@ module Lockbox
|
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
if options[:previous_versions].is_a?(Array)
|
|
29
|
-
|
|
30
|
+
# dup previous versions array (with map) since elements are updated
|
|
31
|
+
# dup each version (with dup) since keys are sometimes deleted
|
|
32
|
+
options[:previous_versions] = options[:previous_versions].map(&:dup)
|
|
30
33
|
options[:previous_versions].each_with_index do |version, i|
|
|
31
|
-
if !(version[:key] || version[:encryption_key] || version[:decryption_key]) && version[:master_key]
|
|
34
|
+
if !(version[:key] || version[:encryption_key] || version[:decryption_key]) && (version[:master_key] || version[:key_table] || version[:key_attribute])
|
|
32
35
|
# could also use key_table and key_attribute from options
|
|
33
36
|
# when specified, but keep simple for now
|
|
34
37
|
# also, this change isn't backward compatible
|
|
@@ -56,7 +59,7 @@ module Lockbox
|
|
|
56
59
|
key = [key].pack("H*")
|
|
57
60
|
end
|
|
58
61
|
|
|
59
|
-
raise Lockbox::Error, "#{name} must be
|
|
62
|
+
raise Lockbox::Error, "#{name} must be #{size} bytes (#{size * 2} hex digits)" if key.bytesize != size
|
|
60
63
|
raise Lockbox::Error, "#{name} must use binary encoding" if key.encoding != Encoding::BINARY
|
|
61
64
|
|
|
62
65
|
key
|
|
@@ -86,8 +89,7 @@ module Lockbox
|
|
|
86
89
|
attachable = attachable.dup
|
|
87
90
|
attachable[:io] = box.encrypt_io(io)
|
|
88
91
|
else
|
|
89
|
-
|
|
90
|
-
raise NotImplementedError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
|
|
92
|
+
raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
|
|
91
93
|
end
|
|
92
94
|
|
|
93
95
|
# don't analyze encrypted data
|
data/lib/lockbox/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lockbox
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-11-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -150,6 +150,34 @@ dependencies:
|
|
|
150
150
|
- - ">="
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
152
|
version: '0'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: shrine
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - ">="
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '0'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: shrine-mongoid
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - ">="
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - ">="
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0'
|
|
153
181
|
- !ruby/object:Gem::Dependency
|
|
154
182
|
name: benchmark-ips
|
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -164,7 +192,7 @@ dependencies:
|
|
|
164
192
|
- - ">="
|
|
165
193
|
- !ruby/object:Gem::Version
|
|
166
194
|
version: '0'
|
|
167
|
-
description:
|
|
195
|
+
description:
|
|
168
196
|
email: andrew@chartkick.com
|
|
169
197
|
executables: []
|
|
170
198
|
extensions: []
|
|
@@ -197,7 +225,7 @@ homepage: https://github.com/ankane/lockbox
|
|
|
197
225
|
licenses:
|
|
198
226
|
- MIT
|
|
199
227
|
metadata: {}
|
|
200
|
-
post_install_message:
|
|
228
|
+
post_install_message:
|
|
201
229
|
rdoc_options: []
|
|
202
230
|
require_paths:
|
|
203
231
|
- lib
|
|
@@ -212,8 +240,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
212
240
|
- !ruby/object:Gem::Version
|
|
213
241
|
version: '0'
|
|
214
242
|
requirements: []
|
|
215
|
-
rubygems_version: 3.1.
|
|
216
|
-
signing_key:
|
|
243
|
+
rubygems_version: 3.1.4
|
|
244
|
+
signing_key:
|
|
217
245
|
specification_version: 4
|
|
218
|
-
summary: Modern encryption for Rails
|
|
246
|
+
summary: Modern encryption for Ruby and Rails
|
|
219
247
|
test_files: []
|