lockbox 1.1.2 → 1.3.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 +12 -0
- data/README.md +31 -50
- data/lib/lockbox/box.rb +8 -3
- data/lib/lockbox/carrier_wave_extensions.rb +1 -1
- data/lib/lockbox/encryptor.rb +1 -1
- data/lib/lockbox/log_subscriber.rb +2 -2
- data/lib/lockbox/model.rb +28 -5
- data/lib/lockbox/utils.rb +4 -0
- data/lib/lockbox/version.rb +1 -1
- data/lib/lockbox.rb +16 -15
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db8c162439dc5376d1aabf48af3925fd2d7a5129e3902b49b973dce9eda16a77
|
|
4
|
+
data.tar.gz: 2e82dc5026e09fdaee0bb1baddabc9b0f8294f38cf613ee09368d62aa39c4ff4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8d6d9a2c4661767c01ab8874f29c3fd705712d4f3d6d153e09b4c7ad6441bb0812d45c6cf526d3a59177ae23c44289b1aa375e462303c17fcb952edd4641a8e
|
|
7
|
+
data.tar.gz: 2e5cd80ddca65447f10a666b5568bbdeff449bf6517c944a67f6fdfdd5ff253e7560569133216a5459d1519ad3bb72b5cabd17065240a2aa1091750e21b4c26c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 1.3.0 (2023-07-02)
|
|
2
|
+
|
|
3
|
+
- Added support for CarrierWave 3
|
|
4
|
+
|
|
5
|
+
## 1.2.0 (2023-03-20)
|
|
6
|
+
|
|
7
|
+
- Made it easier to rotate master key
|
|
8
|
+
- Added `associated_data` option for database fields and files
|
|
9
|
+
- Added `decimal` type
|
|
10
|
+
- Added `encode_attributes` option
|
|
11
|
+
- Fixed deprecation warnings with Rails 7.1
|
|
12
|
+
|
|
1
13
|
## 1.1.2 (2023-02-01)
|
|
2
14
|
|
|
3
15
|
- Fixed error when migrating to `array`, `hash`, and `json` types
|
data/README.md
CHANGED
|
@@ -117,6 +117,7 @@ class User < ApplicationRecord
|
|
|
117
117
|
has_encrypted :active, type: :boolean
|
|
118
118
|
has_encrypted :salary, type: :integer
|
|
119
119
|
has_encrypted :latitude, type: :float
|
|
120
|
+
has_encrypted :longitude, type: :decimal
|
|
120
121
|
has_encrypted :video, type: :binary
|
|
121
122
|
has_encrypted :properties, type: :json
|
|
122
123
|
has_encrypted :settings, type: :hash
|
|
@@ -566,51 +567,25 @@ Use `decrypt_str` get the value as UTF-8
|
|
|
566
567
|
|
|
567
568
|
To make key rotation easy, you can pass previous versions of keys that can decrypt.
|
|
568
569
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
Update your model:
|
|
570
|
+
Create `config/initializers/lockbox.rb` with:
|
|
572
571
|
|
|
573
572
|
```ruby
|
|
574
|
-
|
|
575
|
-
has_encrypted :email, previous_versions: [{master_key: previous_key}]
|
|
576
|
-
end
|
|
573
|
+
Lockbox.default_options[:previous_versions] = [{master_key: previous_key}]
|
|
577
574
|
```
|
|
578
575
|
|
|
579
|
-
To rotate existing records, use:
|
|
576
|
+
To rotate existing Active Record & Mongoid records, use:
|
|
580
577
|
|
|
581
578
|
```ruby
|
|
582
579
|
Lockbox.rotate(User, attributes: [:email])
|
|
583
580
|
```
|
|
584
581
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
### Action Text
|
|
588
|
-
|
|
589
|
-
Update your initializer:
|
|
590
|
-
|
|
591
|
-
```ruby
|
|
592
|
-
Lockbox.encrypts_action_text_body(previous_versions: [{master_key: previous_key}])
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
To rotate existing records, use:
|
|
582
|
+
To rotate existing Action Text records, use:
|
|
596
583
|
|
|
597
584
|
```ruby
|
|
598
585
|
Lockbox.rotate(ActionText::RichText, attributes: [:body])
|
|
599
586
|
```
|
|
600
587
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
### Active Storage
|
|
604
|
-
|
|
605
|
-
Update your model:
|
|
606
|
-
|
|
607
|
-
```ruby
|
|
608
|
-
class User < ApplicationRecord
|
|
609
|
-
encrypts_attached :license, previous_versions: [{master_key: previous_key}]
|
|
610
|
-
end
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
To rotate existing files, use:
|
|
588
|
+
To rotate existing Active Storage files, use:
|
|
614
589
|
|
|
615
590
|
```ruby
|
|
616
591
|
User.with_attached_license.find_each do |user|
|
|
@@ -618,39 +593,31 @@ User.with_attached_license.find_each do |user|
|
|
|
618
593
|
end
|
|
619
594
|
```
|
|
620
595
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
### CarrierWave
|
|
624
|
-
|
|
625
|
-
Update your model:
|
|
626
|
-
|
|
627
|
-
```ruby
|
|
628
|
-
class LicenseUploader < CarrierWave::Uploader::Base
|
|
629
|
-
encrypt previous_versions: [{master_key: previous_key}]
|
|
630
|
-
end
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
To rotate existing files, use:
|
|
596
|
+
To rotate existing CarrierWave files, use:
|
|
634
597
|
|
|
635
598
|
```ruby
|
|
636
599
|
User.find_each do |user|
|
|
637
600
|
user.license.rotate_encryption!
|
|
601
|
+
# or for multiple files
|
|
602
|
+
user.licenses.map(&:rotate_encryption!)
|
|
638
603
|
end
|
|
639
604
|
```
|
|
640
605
|
|
|
641
|
-
|
|
606
|
+
Once everything is rotated, you can remove `previous_versions` from the initializer.
|
|
607
|
+
|
|
608
|
+
### Individual Fields & Files
|
|
609
|
+
|
|
610
|
+
You can also pass previous versions to individual fields and files.
|
|
642
611
|
|
|
643
612
|
```ruby
|
|
644
|
-
User
|
|
645
|
-
|
|
613
|
+
class User < ApplicationRecord
|
|
614
|
+
has_encrypted :email, previous_versions: [{master_key: previous_key}]
|
|
646
615
|
end
|
|
647
616
|
```
|
|
648
617
|
|
|
649
|
-
Once all files are rotated, you can remove `previous_versions` from the model.
|
|
650
|
-
|
|
651
618
|
### Local Files & Strings
|
|
652
619
|
|
|
653
|
-
|
|
620
|
+
To rotate local files and strings, use:
|
|
654
621
|
|
|
655
622
|
```ruby
|
|
656
623
|
Lockbox.new(key: key, previous_versions: [{key: previous_key}])
|
|
@@ -928,6 +895,14 @@ lockbox.decrypt(ciphertext, associated_data: "somecontext") # success
|
|
|
928
895
|
lockbox.decrypt(ciphertext, associated_data: "othercontext") # fails
|
|
929
896
|
```
|
|
930
897
|
|
|
898
|
+
You can also use it with database fields and files.
|
|
899
|
+
|
|
900
|
+
```ruby
|
|
901
|
+
class User < ApplicationRecord
|
|
902
|
+
has_encrypted :email, associated_data: -> { code }
|
|
903
|
+
end
|
|
904
|
+
```
|
|
905
|
+
|
|
931
906
|
## Binary Columns
|
|
932
907
|
|
|
933
908
|
You can use `binary` columns for the ciphertext instead of `text` columns.
|
|
@@ -948,6 +923,12 @@ class User < ApplicationRecord
|
|
|
948
923
|
end
|
|
949
924
|
```
|
|
950
925
|
|
|
926
|
+
or set it globally:
|
|
927
|
+
|
|
928
|
+
```ruby
|
|
929
|
+
Lockbox.encode_attributes = false
|
|
930
|
+
```
|
|
931
|
+
|
|
951
932
|
## Compatibility
|
|
952
933
|
|
|
953
934
|
It’s easy to read encrypted data in another language if needed.
|
data/lib/lockbox/box.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Lockbox
|
|
2
2
|
class Box
|
|
3
|
-
|
|
3
|
+
NOT_SET = Object.new
|
|
4
|
+
|
|
5
|
+
def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil, padding: false, associated_data: nil)
|
|
4
6
|
raise ArgumentError, "Cannot pass both key and encryption/decryption key" if key && (encryption_key || decryption_key)
|
|
5
7
|
|
|
6
8
|
key = Lockbox::Utils.decode_key(key) if key
|
|
@@ -32,9 +34,11 @@ module Lockbox
|
|
|
32
34
|
|
|
33
35
|
@algorithm = algorithm
|
|
34
36
|
@padding = padding == true ? 16 : padding
|
|
37
|
+
@associated_data = associated_data
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
def encrypt(message, associated_data:
|
|
40
|
+
def encrypt(message, associated_data: NOT_SET)
|
|
41
|
+
associated_data = @associated_data if associated_data == NOT_SET
|
|
38
42
|
message = Lockbox.pad(message, size: @padding) if @padding
|
|
39
43
|
case @algorithm
|
|
40
44
|
when "hybrid"
|
|
@@ -53,7 +57,8 @@ module Lockbox
|
|
|
53
57
|
nonce + ciphertext
|
|
54
58
|
end
|
|
55
59
|
|
|
56
|
-
def decrypt(ciphertext, associated_data:
|
|
60
|
+
def decrypt(ciphertext, associated_data: NOT_SET)
|
|
61
|
+
associated_data = @associated_data if associated_data == NOT_SET
|
|
57
62
|
message =
|
|
58
63
|
case @algorithm
|
|
59
64
|
when "hybrid"
|
|
@@ -105,7 +105,7 @@ module Lockbox
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
if CarrierWave::VERSION.to_i >
|
|
108
|
+
if CarrierWave::VERSION.to_i > 3
|
|
109
109
|
raise Lockbox::Error, "CarrierWave #{CarrierWave::VERSION} not supported in this version of Lockbox"
|
|
110
110
|
elsif CarrierWave::VERSION.to_i < 1
|
|
111
111
|
raise Lockbox::Error, "CarrierWave #{CarrierWave::VERSION} not supported"
|
data/lib/lockbox/encryptor.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Lockbox
|
|
|
9
9
|
|
|
10
10
|
@boxes =
|
|
11
11
|
[Box.new(**options)] +
|
|
12
|
-
Array(previous_versions).map { |v| Box.new(key: options[:key], **v) }
|
|
12
|
+
Array(previous_versions).reject { |v| v.key?(:master_key) }.map { |v| Box.new(key: options[:key], **v) }
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def encrypt(message, **options)
|
|
@@ -6,7 +6,7 @@ module Lockbox
|
|
|
6
6
|
payload = event.payload
|
|
7
7
|
name = "Encrypt File (#{event.duration.round(1)}ms)"
|
|
8
8
|
|
|
9
|
-
debug " #{color(name, YELLOW, true)} Encrypted #{payload[:name]}"
|
|
9
|
+
debug " #{color(name, YELLOW, bold: true)} Encrypted #{payload[:name]}"
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def decrypt_file(event)
|
|
@@ -15,7 +15,7 @@ module Lockbox
|
|
|
15
15
|
payload = event.payload
|
|
16
16
|
name = "Decrypt File (#{event.duration.round(1)}ms)"
|
|
17
17
|
|
|
18
|
-
debug " #{color(name, YELLOW, true)} Decrypted #{payload[:name]}"
|
|
18
|
+
debug " #{color(name, YELLOW, bold: true)} Decrypted #{payload[:name]}"
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
data/lib/lockbox/model.rb
CHANGED
|
@@ -19,10 +19,12 @@ module Lockbox
|
|
|
19
19
|
# options[:type] = :integer
|
|
20
20
|
# when Float
|
|
21
21
|
# options[:type] = :float
|
|
22
|
+
# when BigDecimal
|
|
23
|
+
# options[:type] = :decimal
|
|
22
24
|
# end
|
|
23
25
|
|
|
24
26
|
custom_type = options[:type].respond_to?(:serialize) && options[:type].respond_to?(:deserialize)
|
|
25
|
-
valid_types = [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :binary, :json, :hash, :array, :inet]
|
|
27
|
+
valid_types = [nil, :string, :boolean, :date, :datetime, :time, :integer, :float, :decimal, :binary, :json, :hash, :array, :inet]
|
|
26
28
|
raise ArgumentError, "Unknown type: #{options[:type]}" unless custom_type || valid_types.include?(options[:type])
|
|
27
29
|
|
|
28
30
|
activerecord = defined?(ActiveRecord::Base) && self < ActiveRecord::Base
|
|
@@ -50,7 +52,7 @@ module Lockbox
|
|
|
50
52
|
|
|
51
53
|
options[:attribute] = name.to_s
|
|
52
54
|
options[:encrypted_attribute] = encrypted_attribute
|
|
53
|
-
options[:encode] =
|
|
55
|
+
options[:encode] = Lockbox.encode_attributes unless options.key?(:encode)
|
|
54
56
|
|
|
55
57
|
encrypt_method_name = "generate_#{encrypted_attribute}"
|
|
56
58
|
decrypt_method_name = "decrypt_#{encrypted_attribute}"
|
|
@@ -321,9 +323,15 @@ module Lockbox
|
|
|
321
323
|
|
|
322
324
|
attribute name, attribute_type
|
|
323
325
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
326
|
+
if ActiveRecord::VERSION::STRING.to_f >= 7.1
|
|
327
|
+
serialize name, coder: JSON if options[:type] == :json
|
|
328
|
+
serialize name, type: Hash if options[:type] == :hash
|
|
329
|
+
serialize name, type: Array if options[:type] == :array
|
|
330
|
+
else
|
|
331
|
+
serialize name, JSON if options[:type] == :json
|
|
332
|
+
serialize name, Hash if options[:type] == :hash
|
|
333
|
+
serialize name, Array if options[:type] == :array
|
|
334
|
+
end
|
|
327
335
|
elsif !attributes_to_define_after_schema_loads.key?(name.to_s)
|
|
328
336
|
# when migrating it's best to specify the type directly
|
|
329
337
|
# however, we can try to use the original type if its already defined
|
|
@@ -531,6 +539,19 @@ module Lockbox
|
|
|
531
539
|
message = ActiveRecord::Type::Float.new.serialize(message)
|
|
532
540
|
# double precision, big endian
|
|
533
541
|
message = [message].pack("G") unless message.nil?
|
|
542
|
+
when :decimal
|
|
543
|
+
message =
|
|
544
|
+
if ActiveRecord::VERSION::MAJOR >= 6
|
|
545
|
+
ActiveRecord::Type::Decimal.new.serialize(message)
|
|
546
|
+
else
|
|
547
|
+
# issue with serialize in Active Record < 6
|
|
548
|
+
# https://github.com/rails/rails/commit/a741208f80dd33420a56486bd9ed2b0b9862234a
|
|
549
|
+
ActiveRecord::Type::Decimal.new.cast(message)
|
|
550
|
+
end
|
|
551
|
+
# Postgres stores 4 decimal digits in 2 bytes
|
|
552
|
+
# plus 3 to 8 bytes of overhead
|
|
553
|
+
# but use string for simplicity
|
|
554
|
+
message = message.to_s("F") unless message.nil?
|
|
534
555
|
when :inet
|
|
535
556
|
unless message.nil?
|
|
536
557
|
ip = message.is_a?(IPAddr) ? message : (IPAddr.new(message) rescue nil)
|
|
@@ -579,6 +600,8 @@ module Lockbox
|
|
|
579
600
|
message = ActiveRecord::Type::Integer.new(limit: 8).deserialize(message.unpack1("q>"))
|
|
580
601
|
when :float
|
|
581
602
|
message = ActiveRecord::Type::Float.new.deserialize(message.unpack1("G"))
|
|
603
|
+
when :decimal
|
|
604
|
+
message = ActiveRecord::Type::Decimal.new.deserialize(message)
|
|
582
605
|
when :string
|
|
583
606
|
message.force_encoding(Encoding::UTF_8)
|
|
584
607
|
when :binary
|
data/lib/lockbox/utils.rb
CHANGED
|
@@ -26,6 +26,10 @@ module Lockbox
|
|
|
26
26
|
)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
unless options.key?(:previous_versions)
|
|
30
|
+
options[:previous_versions] = Lockbox.default_options[:previous_versions]
|
|
31
|
+
end
|
|
32
|
+
|
|
29
33
|
if options[:previous_versions].is_a?(Array)
|
|
30
34
|
# dup previous versions array (with map) since elements are updated
|
|
31
35
|
# dup each version (with dup) since keys are sometimes deleted
|
data/lib/lockbox/version.rb
CHANGED
data/lib/lockbox.rb
CHANGED
|
@@ -5,17 +5,17 @@ require "securerandom"
|
|
|
5
5
|
require "stringio"
|
|
6
6
|
|
|
7
7
|
# modules
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
require_relative "lockbox/aes_gcm"
|
|
9
|
+
require_relative "lockbox/box"
|
|
10
|
+
require_relative "lockbox/calculations"
|
|
11
|
+
require_relative "lockbox/encryptor"
|
|
12
|
+
require_relative "lockbox/key_generator"
|
|
13
|
+
require_relative "lockbox/io"
|
|
14
|
+
require_relative "lockbox/migrator"
|
|
15
|
+
require_relative "lockbox/model"
|
|
16
|
+
require_relative "lockbox/padding"
|
|
17
|
+
require_relative "lockbox/utils"
|
|
18
|
+
require_relative "lockbox/version"
|
|
19
19
|
|
|
20
20
|
module Lockbox
|
|
21
21
|
class Error < StandardError; end
|
|
@@ -27,10 +27,11 @@ module Lockbox
|
|
|
27
27
|
extend Padding
|
|
28
28
|
|
|
29
29
|
class << self
|
|
30
|
-
attr_accessor :default_options
|
|
30
|
+
attr_accessor :default_options, :encode_attributes
|
|
31
31
|
attr_writer :master_key
|
|
32
32
|
end
|
|
33
33
|
self.default_options = {}
|
|
34
|
+
self.encode_attributes = true
|
|
34
35
|
|
|
35
36
|
def self.master_key
|
|
36
37
|
@master_key ||= ENV["LOCKBOX_MASTER_KEY"]
|
|
@@ -87,11 +88,11 @@ module Lockbox
|
|
|
87
88
|
end
|
|
88
89
|
|
|
89
90
|
# integrations
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
require_relative "lockbox/carrier_wave_extensions" if defined?(CarrierWave)
|
|
92
|
+
require_relative "lockbox/railtie" if defined?(Rails)
|
|
92
93
|
|
|
93
94
|
if defined?(ActiveSupport::LogSubscriber)
|
|
94
|
-
|
|
95
|
+
require_relative "lockbox/log_subscriber"
|
|
95
96
|
Lockbox::LogSubscriber.attach_to :lockbox
|
|
96
97
|
end
|
|
97
98
|
|
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: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Kane
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-02
|
|
11
|
+
date: 2023-07-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description:
|
|
14
14
|
email: andrew@ankane.org
|
|
@@ -58,7 +58,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
version: '0'
|
|
60
60
|
requirements: []
|
|
61
|
-
rubygems_version: 3.4.
|
|
61
|
+
rubygems_version: 3.4.10
|
|
62
62
|
signing_key:
|
|
63
63
|
specification_version: 4
|
|
64
64
|
summary: Modern encryption for Ruby and Rails
|