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 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: []