lockbox 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fa1a57ca8235a58fd8d638cab1ec624888e72b79f8a3f279588bb358ea8f6cb0
4
+ data.tar.gz: ffe1edd74efb5e8bf121b73816edd6b9209b8a217590816a2791adf6d1ad3dad
5
+ SHA512:
6
+ metadata.gz: 80b49150b2d01aafceaddf9bd6c47b6412ee4cfb5361ff2f7f11633a65847d1f09dd7377794ef68b58c87f0f29210fe981d4b08a981840b51a553d7fb18ccc66
7
+ data.tar.gz: b3e8ae7d2395cc13903d80da1e8f8f3f2f0416a9f9ec8b1fa29b6b6eb155f97724f0effd1a7afd7c5162421b1be35494b5a7d4a6a25344de202a1bab0ef15305
@@ -0,0 +1,3 @@
1
+ ## 0.1.0
2
+
3
+ - First release
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Andrew Kane
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,215 @@
1
+ # Lockbox
2
+
3
+ :lock: File encryption for Ruby and Rails
4
+
5
+ Supports Active Storage and CarrierWave
6
+
7
+ Uses AES-GCM by default for [authenticated encryption](https://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application’s Gemfile:
12
+
13
+ ```ruby
14
+ gem 'lockbox'
15
+ ```
16
+
17
+ ## Key Generation
18
+
19
+ Generate an encryption key.
20
+
21
+ ```ruby
22
+ SecureRandom.hex(32)
23
+ ```
24
+
25
+ Store the key with your other secrets (typically Rails secrets or an environment variable).
26
+
27
+ Alternatively, you can use a [key management service](#key-management) to manage your keys.
28
+
29
+ ## Files
30
+
31
+ Create a box
32
+
33
+ ```ruby
34
+ box = Lockbox.new(key: key)
35
+ ```
36
+
37
+ Encrypt
38
+
39
+ ```ruby
40
+ box.encrypt(File.binread("license.jpg"))
41
+ ```
42
+
43
+ Decrypt
44
+
45
+ ```ruby
46
+ box.decrypt(File.binread("license.jpg.enc"))
47
+ ```
48
+
49
+ ## Active Storage
50
+
51
+ Add to your model:
52
+
53
+ ```ruby
54
+ class User < ApplicationRecord
55
+ has_one_attached :license
56
+ attached_encrypted :license, key: key
57
+ end
58
+ ```
59
+
60
+ Works with multiple attachments as well.
61
+
62
+ ```ruby
63
+ class User < ApplicationRecord
64
+ has_many_attached :documents
65
+ attached_encrypted :documents, key: key
66
+ end
67
+ ```
68
+
69
+ There are a few limitations to be aware of:
70
+
71
+ - Metadata like image width and height are not extracted when encrypted
72
+ - Direct uploads cannot be encrypted
73
+
74
+ ## CarrierWave
75
+
76
+ Add to your uploader:
77
+
78
+ ```ruby
79
+ class LicenseUploader < CarrierWave::Uploader::Base
80
+ encrypt key: key
81
+ end
82
+ ```
83
+
84
+ Encryption is applied to all versions after processing.
85
+
86
+ ## Serving Files
87
+
88
+ To serve encrypted files, use a controller action.
89
+
90
+ ```ruby
91
+ def license
92
+ send_data @user.license.download, type: @user.license.content_type
93
+ end
94
+ ```
95
+
96
+ Use `read` instead of `download` for CarrierWave.
97
+
98
+ ## Key Rotation
99
+
100
+ To make key rotation easy, you can pass previous versions of keys that can decrypt.
101
+
102
+ ```ruby
103
+ Lockbox.new(key: key, previous_versions: [{key: previous_key}])
104
+ ```
105
+
106
+ For Active Storage use:
107
+
108
+ ```ruby
109
+ class User < ApplicationRecord
110
+ attached_encrypted :license, key: key, previous_versions: [{key: previous_key}]
111
+ end
112
+ ```
113
+
114
+ To rotate existing files, use:
115
+
116
+ ```ruby
117
+ user.license.rotate_encryption!
118
+ ```
119
+
120
+ For CarrierWave, use:
121
+
122
+ ```ruby
123
+ class LicenseUploader < CarrierWave::Uploader::Base
124
+ encrypt key: key, previous_versions: [{key: previous_key}]
125
+ end
126
+ ```
127
+
128
+ To rotate existing files, use:
129
+
130
+ ```ruby
131
+ user.license.rotate_encryption!
132
+ ```
133
+
134
+ ## Algorithms
135
+
136
+ ### AES-GCM
137
+
138
+ The default algorithm is AES-GCM with a 256-bit key. Rotate the key every 2 billion files to minimize the chance of a [nonce collision](https://www.cryptologie.net/article/402/is-symmetric-security-solved/).
139
+
140
+ ### XChaCha20
141
+
142
+ [Install Libsodium](https://github.com/crypto-rb/rbnacl/wiki/Installing-libsodium) and add [rbnacl](https://github.com/crypto-rb/rbnacl) to your application’s Gemfile:
143
+
144
+ ```ruby
145
+ gem 'rbnacl'
146
+ ```
147
+
148
+ Then pass the `algorithm` option:
149
+
150
+ ```ruby
151
+ # files
152
+ box = Lockbox.new(key: key, algorithm: "xchacha20")
153
+
154
+ # Active Storage
155
+ class User < ApplicationRecord
156
+ attached_encrypted :license, key: key, algorithm: "xchacha20"
157
+ end
158
+
159
+ # CarrierWave
160
+ class LicenseUploader < CarrierWave::Uploader::Base
161
+ encrypt key: key, algorithm: "xchacha20"
162
+ end
163
+ ```
164
+
165
+ Make it the default with:
166
+
167
+ ```ruby
168
+ Lockbox.default_options = {algorithm: "xchacha20"}
169
+ ```
170
+
171
+ You can also pass an algorithm to `previous_versions` for key rotation.
172
+
173
+ ## Key Management
174
+
175
+ You can use a key management service to manage your keys with [KMS Encrypted](https://github.com/ankane/kms_encrypted).
176
+
177
+ For Active Storage, use:
178
+
179
+ ```ruby
180
+ class User < ApplicationRecord
181
+ attached_encrypted :license, key: :kms_key
182
+ end
183
+ ```
184
+
185
+ For CarrierWave, use:
186
+
187
+ ```ruby
188
+ class LicenseUploader < CarrierWave::Uploader::Base
189
+ encrypt key: -> { model.kms_key }
190
+ end
191
+ ```
192
+
193
+ **Note:** KMS Encrypted’s key rotation does not know to rotate encrypted files, so avoid calling `record.rotate_kms_key!` on models with file uploads for now.
194
+
195
+ ## Reference
196
+
197
+ Pass associated data to encryption and decryption
198
+
199
+ ```ruby
200
+ box.encrypt(message, associated_data: "bingo")
201
+ box.decrypt(ciphertext, associated_data: "bingo")
202
+ ```
203
+
204
+ ## History
205
+
206
+ View the [changelog](https://github.com/ankane/lockbox/blob/master/CHANGELOG.md)
207
+
208
+ ## Contributing
209
+
210
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
211
+
212
+ - [Report bugs](https://github.com/ankane/lockbox/issues)
213
+ - Fix bugs and [submit pull requests](https://github.com/ankane/lockbox/pulls)
214
+ - Write, clarify, or fix documentation
215
+ - Suggest or add new features
@@ -0,0 +1,56 @@
1
+ # dependencies
2
+ require "openssl"
3
+ require "securerandom"
4
+
5
+ # modules
6
+ require "lockbox/box"
7
+ require "lockbox/utils"
8
+ require "lockbox/version"
9
+
10
+ # integrations
11
+ require "lockbox/carrier_wave_extensions" if defined?(CarrierWave)
12
+ require "lockbox/railtie" if defined?(Rails)
13
+
14
+ class Lockbox
15
+ class Error < StandardError; end
16
+ class DecryptionError < Error; end
17
+
18
+ class << self
19
+ attr_accessor :default_options
20
+ end
21
+ self.default_options = {algorithm: "aes-gcm"}
22
+
23
+ def initialize(key: nil, algorithm: nil, previous_versions: nil)
24
+ default_options = self.class.default_options
25
+ key ||= default_options[:key]
26
+ algorithm ||= default_options[:algorithm]
27
+ previous_versions ||= default_options[:previous_versions]
28
+
29
+ @boxes =
30
+ [Box.new(key, algorithm: algorithm)] +
31
+ Array(previous_versions).map { |v| Box.new(v[:key], algorithm: v[:algorithm]) }
32
+ end
33
+
34
+ def encrypt(*args)
35
+ @boxes.first.encrypt(*args)
36
+ end
37
+
38
+ def decrypt(ciphertext, **options)
39
+ raise TypeError, "can't convert ciphertext to string" unless ciphertext.respond_to?(:to_str)
40
+
41
+ # ensure binary
42
+ ciphertext = ciphertext.to_str
43
+ if ciphertext.encoding != Encoding::BINARY
44
+ # dup to prevent mutation
45
+ ciphertext = ciphertext.dup.force_encoding(Encoding::BINARY)
46
+ end
47
+
48
+ @boxes.each_with_index do |box, i|
49
+ begin
50
+ return box.decrypt(ciphertext, **options)
51
+ rescue DecryptionError, RbNaCl::LengthError, RbNaCl::CryptoError
52
+ raise DecryptionError, "Decryption failed" if i == @boxes.size - 1
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,148 @@
1
+ class Lockbox
2
+ module ActiveStorageExtensions
3
+ module Attached
4
+ protected
5
+
6
+ def encrypted?
7
+ # could use record_type directly
8
+ # but record should already be loaded most of the time
9
+ Utils.encrypted_options(record, name).present?
10
+ end
11
+
12
+ def encrypt_attachable(attachable)
13
+ options = Utils.encrypted_options(record, name)
14
+ box = Utils.build_box(record, options)
15
+
16
+ case attachable
17
+ when ActiveStorage::Blob
18
+ raise NotImplemented, "Not supported yet"
19
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
20
+ attachable = {
21
+ io: StringIO.new(box.encrypt(attachable.read)),
22
+ filename: attachable.original_filename,
23
+ content_type: attachable.content_type
24
+ }
25
+ when Hash
26
+ attachable = {
27
+ io: StringIO.new(box.encrypt(attachable[:io].read)),
28
+ filename: attachable[:filename],
29
+ content_type: attachable[:content_type]
30
+ }
31
+ when String
32
+ raise NotImplemented, "Not supported yet"
33
+ else
34
+ nil
35
+ end
36
+
37
+ attachable
38
+ end
39
+
40
+ def rebuild_attachable(attachment)
41
+ {
42
+ io: StringIO.new(attachment.download),
43
+ filename: attachment.filename,
44
+ content_type: attachment.content_type
45
+ }
46
+ end
47
+ end
48
+
49
+ module AttachedOne
50
+ def attach(attachable)
51
+ attachable = encrypt_attachable(attachable) if encrypted?
52
+ super(attachable)
53
+ end
54
+
55
+ def rotate_encryption!
56
+ raise "Not encrypted" unless encrypted?
57
+
58
+ attach(rebuild_attachable(self)) if attached?
59
+
60
+ true
61
+ end
62
+ end
63
+
64
+ module AttachedMany
65
+ def attach(*attachables)
66
+ if encrypted?
67
+ attachables =
68
+ attachables.flatten.collect do |attachable|
69
+ encrypt_attachable(attachable)
70
+ end
71
+ end
72
+
73
+ super(attachables)
74
+ end
75
+
76
+ def rotate_encryption!
77
+ raise "Not encrypted" unless encrypted?
78
+
79
+ # must call to_a - do not change
80
+ previous_attachments = attachments.to_a
81
+
82
+ attachables =
83
+ previous_attachments.map do |attachment|
84
+ rebuild_attachable(attachment)
85
+ end
86
+
87
+ ActiveStorage::Attachment.transaction do
88
+ attach(attachables)
89
+ previous_attachments.each(&:purge)
90
+ end
91
+
92
+ attachments.reload
93
+
94
+ true
95
+ end
96
+ end
97
+
98
+ module Attachment
99
+ extend ActiveSupport::Concern
100
+
101
+ def download
102
+ result = super
103
+
104
+ options = Utils.encrypted_options(record, name)
105
+ if options
106
+ result = Utils.build_box(record, options).decrypt(result)
107
+ end
108
+
109
+ result
110
+ end
111
+
112
+ def mark_analyzed
113
+ if Utils.encrypted_options(record, name)
114
+ blob.update!(metadata: blob.metadata.merge(analyzed: true))
115
+ end
116
+ end
117
+
118
+ included do
119
+ after_save :mark_analyzed
120
+ end
121
+ end
122
+
123
+ module Model
124
+ def attached_encrypted(name, **options)
125
+ class_eval do
126
+ @encrypted_attachments ||= {}
127
+
128
+ unless respond_to?(:encrypted_attachments)
129
+ def self.encrypted_attachments
130
+ parent_attachments =
131
+ if superclass.respond_to?(:encrypted_attachments)
132
+ superclass.encrypted_attachments
133
+ else
134
+ {}
135
+ end
136
+
137
+ parent_attachments.merge(@encrypted_attachments || {})
138
+ end
139
+ end
140
+
141
+ raise ArgumentError, "Duplicate encrypted attachment: #{name}" if encrypted_attachments[name]
142
+
143
+ @encrypted_attachments[name] = options
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,74 @@
1
+ class Lockbox
2
+ class AES_GCM
3
+ def initialize(key)
4
+ raise ArgumentError, "Key must be 32 bytes" unless key && key.bytesize == 32
5
+ raise ArgumentError, "Key must be binary" unless key.encoding == Encoding::BINARY
6
+
7
+ @key = key
8
+ end
9
+
10
+ def encrypt(nonce, message, associated_data)
11
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
12
+ cipher.encrypt
13
+ cipher.key = @key
14
+ cipher.iv = nonce
15
+ # From Ruby 2.5.3 OpenSSL::Cipher docs:
16
+ # If no associated data shall be used, this method must still be called with a value of ""
17
+ # In encryption mode, it must be set after calling #encrypt and setting #key= and #iv=
18
+ cipher.auth_data = associated_data || ""
19
+
20
+ ciphertext = cipher.update(message) + cipher.final
21
+ ciphertext << cipher.auth_tag
22
+
23
+ ciphertext
24
+ end
25
+
26
+ def decrypt(nonce, ciphertext, associated_data)
27
+ auth_tag, ciphertext = extract_auth_tag(ciphertext.to_s)
28
+
29
+ fail_decryption if nonce.to_s.bytesize != nonce_bytes
30
+ fail_decryption if auth_tag.to_s.bytesize != auth_tag_bytes
31
+ fail_decryption if ciphertext.to_s.bytesize == 0
32
+
33
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
34
+ cipher.decrypt
35
+ cipher.key = @key
36
+ cipher.iv = nonce
37
+ cipher.auth_tag = auth_tag
38
+ # From Ruby 2.5.3 OpenSSL::Cipher docs:
39
+ # If no associated data shall be used, this method must still be called with a value of ""
40
+ # When decrypting, set it only after calling #decrypt, #key=, #iv= and #auth_tag= first.
41
+ cipher.auth_data = associated_data || ""
42
+
43
+ begin
44
+ cipher.update(ciphertext) + cipher.final
45
+ rescue OpenSSL::Cipher::CipherError
46
+ fail_decryption
47
+ end
48
+ end
49
+
50
+ def nonce_bytes
51
+ 12
52
+ end
53
+
54
+ # protect key
55
+ def inspect
56
+ to_s
57
+ end
58
+
59
+ private
60
+
61
+ def auth_tag_bytes
62
+ 16
63
+ end
64
+
65
+ def extract_auth_tag(bytes)
66
+ auth_tag = bytes.slice(-auth_tag_bytes..-1)
67
+ [auth_tag, bytes.slice(0, bytes.bytesize - auth_tag_bytes)]
68
+ end
69
+
70
+ def fail_decryption
71
+ raise DecryptionError, "Decryption failed"
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,54 @@
1
+ class Lockbox
2
+ class Box
3
+ def initialize(key, algorithm: nil)
4
+ # decode hex key
5
+ if key.encoding != Encoding::BINARY && key =~ /\A[0-9a-f]{64}\z/i
6
+ key = [key].pack("H*")
7
+ end
8
+
9
+ algorithm ||= "aes-gcm"
10
+
11
+ case algorithm
12
+ when "aes-gcm"
13
+ require "lockbox/aes_gcm"
14
+ @box = AES_GCM.new(key)
15
+ when "xchacha20"
16
+ require "rbnacl"
17
+ @box = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
18
+ else
19
+ raise ArgumentError, "Unknown algorithm: #{algorithm}"
20
+ end
21
+ end
22
+
23
+ def encrypt(message, associated_data: nil)
24
+ nonce = generate_nonce
25
+ ciphertext = @box.encrypt(nonce, message, associated_data)
26
+ nonce + ciphertext
27
+ end
28
+
29
+ def decrypt(ciphertext, associated_data: nil)
30
+ nonce, ciphertext = extract_nonce(ciphertext)
31
+ @box.decrypt(nonce, ciphertext, associated_data)
32
+ end
33
+
34
+ # protect key for xchacha20
35
+ def inspect
36
+ to_s
37
+ end
38
+
39
+ private
40
+
41
+ def nonce_bytes
42
+ @box.nonce_bytes
43
+ end
44
+
45
+ def generate_nonce
46
+ SecureRandom.random_bytes(nonce_bytes)
47
+ end
48
+
49
+ def extract_nonce(bytes)
50
+ nonce = bytes.slice(0, nonce_bytes)
51
+ [nonce, bytes.slice(nonce_bytes..-1)]
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,46 @@
1
+ class Lockbox
2
+ module CarrierWaveExtensions
3
+ class FileIO < StringIO
4
+ attr_accessor :original_filename
5
+ end
6
+
7
+ def encrypt(**options)
8
+ class_eval do
9
+ before :cache, :encrypt
10
+
11
+ def encrypt(file)
12
+ @file = CarrierWave::SanitizedFile.new(StringIO.new(lockbox.encrypt(file.read)))
13
+ end
14
+
15
+ def read
16
+ r = super
17
+ lockbox.decrypt(r) if r
18
+ end
19
+
20
+ def size
21
+ read.bytesize
22
+ end
23
+
24
+ def rotate_encryption!
25
+ io = FileIO.new(read)
26
+ io.original_filename = file.filename
27
+ previous_value = enable_processing
28
+ begin
29
+ self.enable_processing = false
30
+ store!(io)
31
+ ensure
32
+ self.enable_processing = previous_value
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ define_method :lockbox do
39
+ @lockbox ||= Utils.build_box(self, options)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ CarrierWave::Uploader::Base.extend(Lockbox::CarrierWaveExtensions)
@@ -0,0 +1,21 @@
1
+ class Lockbox
2
+ class Railtie < Rails::Railtie
3
+ initializer "lockbox" do |app|
4
+ require "lockbox/carrier_wave_extensions" if defined?(CarrierWave)
5
+
6
+ if defined?(ActiveStorage)
7
+ require "lockbox/active_storage_extensions"
8
+ ActiveStorage::Attached.prepend(Lockbox::ActiveStorageExtensions::Attached)
9
+ ActiveStorage::Attached::One.prepend(Lockbox::ActiveStorageExtensions::AttachedOne)
10
+ ActiveStorage::Attached::Many.prepend(Lockbox::ActiveStorageExtensions::AttachedMany)
11
+ ActiveRecord::Base.extend(Lockbox::ActiveStorageExtensions::Model) if defined?(ActiveRecord)
12
+ end
13
+
14
+ app.config.to_prepare do
15
+ if defined?(ActiveStorage)
16
+ ActiveStorage::Attachment.include(Lockbox::ActiveStorageExtensions::Attachment)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ class Lockbox
2
+ class Utils
3
+ def self.build_box(context, options)
4
+ options = options.dup
5
+ options.each do |k, v|
6
+ if v.is_a?(Proc)
7
+ options[k] = context.instance_exec(&v) if v.respond_to?(:call)
8
+ elsif v.is_a?(Symbol)
9
+ options[k] = context.send(v)
10
+ end
11
+ end
12
+
13
+ Lockbox.new(options)
14
+ end
15
+
16
+ def self.encrypted_options(record, name)
17
+ record.class.respond_to?(:encrypted_attachments) && record.class.encrypted_attachments[name.to_sym]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ class Lockbox
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lockbox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Kane
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: carrierwave
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activestorage
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activejob
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: combustion
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rbnacl
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description:
140
+ email: andrew@chartkick.com
141
+ executables: []
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - CHANGELOG.md
146
+ - LICENSE.txt
147
+ - README.md
148
+ - lib/lockbox.rb
149
+ - lib/lockbox/active_storage_extensions.rb
150
+ - lib/lockbox/aes_gcm.rb
151
+ - lib/lockbox/box.rb
152
+ - lib/lockbox/carrier_wave_extensions.rb
153
+ - lib/lockbox/railtie.rb
154
+ - lib/lockbox/utils.rb
155
+ - lib/lockbox/version.rb
156
+ homepage: https://github.com/ankane/lockbox
157
+ licenses:
158
+ - MIT
159
+ metadata: {}
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '2.2'
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubyforge_project:
176
+ rubygems_version: 2.7.6
177
+ signing_key:
178
+ specification_version: 4
179
+ summary: File encryption for Ruby and Rails. Supports Active Storage and CarrierWave.
180
+ test_files: []