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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ad5a754772ecb9d5f0a480cab88a63de9ed2fcfd973eef25f286fcf13da7694
4
- data.tar.gz: d38646c9d1aedee2bf12419a24064fa5b01e4ef4cb619e8cb48903c343ec67b1
3
+ metadata.gz: '00428f366a284e5e69806c18ef64a7bb66cdc0b2c1cb3e19528aa6bca99becea'
4
+ data.tar.gz: b5235c68771e0cb39653780dd4e08bdb27218f914b3889cecf959bc02efec3a8
5
5
  SHA512:
6
- metadata.gz: 5f6e78e05cb6788ad8188314694846f70c438e3e43f48bdf1e8e6356ac94e64226a3790ebaab6369121d1083d551a7203281979731443cfdb1c611d52a617493
7
- data.tar.gz: 30fc406d323dda8abdc0d5138880dc32b862abdd479e623180cf99c2531b466ef5a3be10620c975d0f2aee4b10e17b413cdc1cf21e03aa49ef5c6f0a7757cd82
6
+ metadata.gz: cd44c7b55ea270aa02fa5a4eda5aa38bde97eca39ce54093b6fc4907abe90e946827b4c12a8f5f43f21b6503ea9d1c713f505a86c9a6ea4075b1407b5630aeca
7
+ data.tar.gz: 0eba333d509de09a92cd1af74427cce30eca35d7eab41abddada5718227c5a1f3f013b3ad031f8015ca5e13c4b47f5aebe75c09d83f6bd91926ad7d55db6a1e4
@@ -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://travis-ci.org/ankane/lockbox.svg?branch=master)](https://travis-ci.org/ankane/lockbox)
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
- Generate a key
417
+ #### Models
418
+
419
+ Include the attachment as normal:
417
420
 
418
421
  ```ruby
419
- key = Lockbox.generate_key
422
+ class User < ApplicationRecord
423
+ include LicenseUploader::Attachment(:license)
424
+ end
420
425
  ```
421
426
 
422
- Create a lockbox
427
+ And encrypt in a controller (or background job, etc) with:
423
428
 
424
429
  ```ruby
425
- lockbox = Lockbox.new(key: key)
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
- Encrypt files before passing them to Shrine
435
+ To serve encrypted files, use a controller action.
429
436
 
430
437
  ```ruby
431
- LicenseUploader.upload(lockbox.encrypt_io(file), :store)
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
- And decrypt them after reading
445
+ #### Non-Models
446
+
447
+ Generate a key
435
448
 
436
449
  ```ruby
437
- lockbox.decrypt(uploaded_file.read)
450
+ key = Lockbox.generate_key
438
451
  ```
439
452
 
440
- For models, encrypt with:
453
+ Create a lockbox
441
454
 
442
455
  ```ruby
443
- license = params.require(:user).fetch(:license)
444
- user.license = lockbox.encrypt_io(license)
456
+ lockbox = Lockbox.new(key: key)
445
457
  ```
446
458
 
447
- To serve encrypted files, use a controller action.
459
+ Encrypt files before passing them to Shrine
448
460
 
449
461
  ```ruby
450
- def license
451
- user = User.find(params[:id])
452
- send_data lockbox.decrypt(user.license.read), type: user.license.mime_type
453
- end
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
@@ -4,6 +4,7 @@ require "openssl"
4
4
  require "securerandom"
5
5
 
6
6
  # modules
7
+ require "lockbox/aes_gcm"
7
8
  require "lockbox/box"
8
9
  require "lockbox/calculations"
9
10
  require "lockbox/encryptor"
@@ -43,11 +43,10 @@ module Lockbox
43
43
  cipher.auth_data = associated_data || ""
44
44
 
45
45
  begin
46
- if ciphertext.to_s.empty?
47
- cipher.final
48
- else
49
- cipher.update(ciphertext) + cipher.final
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
@@ -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 public/private key" if key && (encryption_key || decryption_key)
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 public key set" unless @encryption_box
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 private key set" unless @decryption_box
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)
@@ -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(&:first)
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
@@ -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
- # TODO raise ArgumentError in 0.5.0
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.5.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.5.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"
@@ -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
- options[:previous_versions] = options[:previous_versions].dup
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 32 bytes (64 hex digits)" if key.bytesize != size
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
- # TODO raise ArgumentError
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
@@ -1,3 +1,3 @@
1
1
  module Lockbox
2
- VERSION = "0.4.9"
2
+ VERSION = "0.5.0"
3
3
  end
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.9
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-10-02 00:00:00.000000000 Z
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.2
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: []