lockbox 1.1.2 → 1.2.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 +8 -0
- data/README.md +23 -50
- data/lib/lockbox/box.rb +8 -3
- 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 +2 -1
- 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: f26c45499c84a35d3a4aaeb7043853a4090dd63b2268e128fc0f2713dfc25b09
|
4
|
+
data.tar.gz: 697626daf2a92ecf69eabc56390074f497ba51a31d1ee24a2a5644c9437d0f09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b0832e473ba43bb36cc9b11398d20385eae5f94b3a2d13f66a5cc4322605f28bf93a22b9fcb9ac5e171e7004b909b59e0f26d5a75d1be6561c61bbdf054c65e
|
7
|
+
data.tar.gz: 77995606e64719168bb0688215721e89c84b7ce9b758e49dbbb1beeecd460ca85c129089c8f1f160320ead16cbe64bd7eb2fb72491d7e9575ac392ef11ca492b
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 1.2.0 (2023-03-20)
|
2
|
+
|
3
|
+
- Made it easier to rotate master key
|
4
|
+
- Added `associated_data` option for database fields and files
|
5
|
+
- Added `decimal` type
|
6
|
+
- Added `encode_attributes` option
|
7
|
+
- Fixed deprecation warnings with Rails 7.1
|
8
|
+
|
1
9
|
## 1.1.2 (2023-02-01)
|
2
10
|
|
3
11
|
- 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}])
|
@@ -948,6 +915,12 @@ class User < ApplicationRecord
|
|
948
915
|
end
|
949
916
|
```
|
950
917
|
|
918
|
+
or set it globally:
|
919
|
+
|
920
|
+
```ruby
|
921
|
+
Lockbox.encode_attributes = false
|
922
|
+
```
|
923
|
+
|
951
924
|
## Compatibility
|
952
925
|
|
953
926
|
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"
|
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
@@ -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"]
|
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.2.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-
|
11
|
+
date: 2023-03-20 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.6
|
62
62
|
signing_key:
|
63
63
|
specification_version: 4
|
64
64
|
summary: Modern encryption for Ruby and Rails
|