lockbox 1.1.2 → 1.3.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 +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
|