lockbox 0.4.9 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://
|
12
|
+
[![Build Status](https://github.com/ankane/lockbox/workflows/build/badge.svg?branch=master)](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: []
|