lockbox 0.1.0 → 0.1.1
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 +5 -0
- data/LICENSE.txt +1 -1
- data/README.md +108 -8
- data/lib/lockbox.rb +41 -17
- data/lib/lockbox/active_storage_extensions.rb +6 -2
- data/lib/lockbox/aes_gcm.rb +4 -0
- data/lib/lockbox/box.rb +48 -17
- data/lib/lockbox/encryptor.rb +17 -0
- data/lib/lockbox/version.rb +1 -1
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e4dcdb1c1115e0712d5d65dd8302c3d575a74a5456dd245692970d080d221fa0
|
4
|
+
data.tar.gz: 2278b9fe032f0159525a48a9a9c694c28a80bcbac371039a97729e6ec61087d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8afe130b6c4231667ea26f1ece4a25e4a577a535ec4930a5f85ac2b53b7b508fe77ec9e4b1720f1aab3f1fe513b03576646d8b24cf27bee3a6c5626c9484c35e
|
7
|
+
data.tar.gz: 6662d25470d89b327b2cddf45d9467cd7d2bd8e3e8664140a71c7ea4cb10f13a156d21c01c17b26f0c2dca928f6f3f0010403ba96e77349442ab185def552d10
|
data/CHANGELOG.md
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -2,9 +2,13 @@
|
|
2
2
|
|
3
3
|
:lock: File encryption for Ruby and Rails
|
4
4
|
|
5
|
-
Supports Active Storage and CarrierWave
|
5
|
+
- Supports Active Storage and CarrierWave
|
6
|
+
- Uses AES-GCM by default for [authenticated encryption](https://tonyarcieri.com/all-the-crypto-code-youve-ever-written-is-probably-broken)
|
7
|
+
- Makes key rotation easy
|
6
8
|
|
7
|
-
|
9
|
+
Check out [this post](https://ankane.org/sensitive-data-rails) for more info on securing sensitive data with Rails
|
10
|
+
|
11
|
+
[](https://travis-ci.org/ankane/lockbox)
|
8
12
|
|
9
13
|
## Installation
|
10
14
|
|
@@ -16,13 +20,13 @@ gem 'lockbox'
|
|
16
20
|
|
17
21
|
## Key Generation
|
18
22
|
|
19
|
-
Generate an encryption key
|
23
|
+
Generate an encryption key
|
20
24
|
|
21
25
|
```ruby
|
22
26
|
SecureRandom.hex(32)
|
23
27
|
```
|
24
28
|
|
25
|
-
Store the key with your other secrets
|
29
|
+
Store the key with your other secrets. This is typically Rails credentials or an environment variable ([dotenv](https://github.com/bkeepers/dotenv) is great for this). Be sure to use different keys in development and production. Keys don’t need to be hex-encoded, but it’s often easier to store them this way.
|
26
30
|
|
27
31
|
Alternatively, you can use a [key management service](#key-management) to manage your keys.
|
28
32
|
|
@@ -37,13 +41,13 @@ box = Lockbox.new(key: key)
|
|
37
41
|
Encrypt
|
38
42
|
|
39
43
|
```ruby
|
40
|
-
box.encrypt(File.binread("license.jpg"))
|
44
|
+
ciphertext = box.encrypt(File.binread("license.jpg"))
|
41
45
|
```
|
42
46
|
|
43
47
|
Decrypt
|
44
48
|
|
45
49
|
```ruby
|
46
|
-
box.decrypt(
|
50
|
+
box.decrypt(ciphertext)
|
47
51
|
```
|
48
52
|
|
49
53
|
## Active Storage
|
@@ -135,11 +139,11 @@ user.license.rotate_encryption!
|
|
135
139
|
|
136
140
|
### AES-GCM
|
137
141
|
|
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/).
|
142
|
+
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/), which will leak the key.
|
139
143
|
|
140
144
|
### XChaCha20
|
141
145
|
|
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:
|
146
|
+
[Install Libsodium](https://github.com/crypto-rb/rbnacl/wiki/Installing-libsodium) >= 1.0.12 and add [rbnacl](https://github.com/crypto-rb/rbnacl) to your application’s Gemfile:
|
143
147
|
|
144
148
|
```ruby
|
145
149
|
gem 'rbnacl'
|
@@ -170,6 +174,43 @@ Lockbox.default_options = {algorithm: "xchacha20"}
|
|
170
174
|
|
171
175
|
You can also pass an algorithm to `previous_versions` for key rotation.
|
172
176
|
|
177
|
+
## Hybrid Cryptography
|
178
|
+
|
179
|
+
[Hybrid cryptography](https://en.wikipedia.org/wiki/Hybrid_cryptosystem) allows servers to encrypt data without being able to decrypt it.
|
180
|
+
|
181
|
+
[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:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
gem 'rbnacl'
|
185
|
+
```
|
186
|
+
|
187
|
+
Generate a key pair with:
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
Lockbox.generate_key_pair
|
191
|
+
```
|
192
|
+
|
193
|
+
Store the keys with your other secrets. Then use:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
# files
|
197
|
+
box = Lockbox.new(algorithm: "hybrid", encryption_key: encryption_key, decryption_key: decryption_key)
|
198
|
+
|
199
|
+
# Active Storage
|
200
|
+
class User < ApplicationRecord
|
201
|
+
attached_encrypted :license, algorithm: "hybrid", encryption_key: encryption_key, decryption_key: decryption_key
|
202
|
+
end
|
203
|
+
|
204
|
+
# CarrierWave
|
205
|
+
class LicenseUploader < CarrierWave::Uploader::Base
|
206
|
+
encrypt algorithm: "hybrid", encryption_key: encryption_key, decryption_key: decryption_key
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
Make sure `decryption_key` is `nil` on servers that shouldn’t decrypt.
|
211
|
+
|
212
|
+
This uses X25519 for key exchange and XSalsa20-Poly1305 for encryption.
|
213
|
+
|
173
214
|
## Key Management
|
174
215
|
|
175
216
|
You can use a key management service to manage your keys with [KMS Encrypted](https://github.com/ankane/kms_encrypted).
|
@@ -192,6 +233,65 @@ end
|
|
192
233
|
|
193
234
|
**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
235
|
|
236
|
+
## Compatibility
|
237
|
+
|
238
|
+
It’s easy to read encrypted files in another language if needed.
|
239
|
+
|
240
|
+
Here are [some examples](docs/Compatibility.md).
|
241
|
+
|
242
|
+
The format for AES-GCM is:
|
243
|
+
|
244
|
+
- nonce (IV) - 12 bytes
|
245
|
+
- ciphertext - variable length
|
246
|
+
- authentication tag - 16 bytes
|
247
|
+
|
248
|
+
For XChaCha20, use the appropriate [Libsodium library](https://libsodium.gitbook.io/doc/bindings_for_other_languages).
|
249
|
+
|
250
|
+
## Database Fields
|
251
|
+
|
252
|
+
Lockbox can also be used with [attr_encrypted](https://github.com/attr-encrypted/attr_encrypted) for database fields. This gives you:
|
253
|
+
|
254
|
+
1. Easy key rotation
|
255
|
+
2. XChaCha20
|
256
|
+
3. Hybrid cryptography
|
257
|
+
4. No need for separate IV columns
|
258
|
+
|
259
|
+
Add to your Gemfile:
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
gem 'attr_encrypted'
|
263
|
+
```
|
264
|
+
|
265
|
+
Create a migration to add a new column for the encrypted data. We don’t need a separate IV column, as this will be included in the encrypted data.
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
class AddEncryptedPhoneToUsers < ActiveRecord::Migration[5.2]
|
269
|
+
def change
|
270
|
+
add_column :users, :encrypted_phone, :string
|
271
|
+
end
|
272
|
+
end
|
273
|
+
```
|
274
|
+
|
275
|
+
All Lockbox options are supported.
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
class User < ApplicationRecord
|
279
|
+
attr_encrypted :phone, encryptor: Lockbox::Encryptor, key: key, algorithm: "xchacha20", previous_versions: [{key: previous_key}]
|
280
|
+
|
281
|
+
attribute :encrypted_phone_iv # prevent attr_encrypted error
|
282
|
+
end
|
283
|
+
```
|
284
|
+
|
285
|
+
For hybrid cryptography, use:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
class User < ApplicationRecord
|
289
|
+
attr_encrypted :phone, encryptor: Lockbox::Encryptor, algorithm: "hybrid", encryption_key: encryption_key, decryption_key: decryption_key
|
290
|
+
|
291
|
+
attribute :encrypted_phone_iv # prevent attr_encrypted error
|
292
|
+
end
|
293
|
+
```
|
294
|
+
|
195
295
|
## Reference
|
196
296
|
|
197
297
|
Pass associated data to encryption and decryption
|
data/lib/lockbox.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
|
-
# dependencies
|
2
|
-
require "openssl"
|
3
|
-
require "securerandom"
|
4
|
-
|
5
1
|
# modules
|
6
2
|
require "lockbox/box"
|
3
|
+
require "lockbox/encryptor"
|
7
4
|
require "lockbox/utils"
|
8
5
|
require "lockbox/version"
|
9
6
|
|
@@ -20,26 +17,24 @@ class Lockbox
|
|
20
17
|
end
|
21
18
|
self.default_options = {algorithm: "aes-gcm"}
|
22
19
|
|
23
|
-
def initialize(
|
24
|
-
|
25
|
-
|
26
|
-
algorithm ||= default_options[:algorithm]
|
27
|
-
previous_versions ||= default_options[:previous_versions]
|
20
|
+
def initialize(**options)
|
21
|
+
options = self.class.default_options.merge(options)
|
22
|
+
previous_versions = options.delete(:previous_versions)
|
28
23
|
|
29
24
|
@boxes =
|
30
|
-
[Box.new(
|
31
|
-
Array(previous_versions).map { |v| Box.new(v
|
25
|
+
[Box.new(options)] +
|
26
|
+
Array(previous_versions).map { |v| Box.new(v) }
|
32
27
|
end
|
33
28
|
|
34
|
-
def encrypt(
|
35
|
-
|
29
|
+
def encrypt(message, **options)
|
30
|
+
message = check_string(message, "message")
|
31
|
+
@boxes.first.encrypt(message, **options)
|
36
32
|
end
|
37
33
|
|
38
34
|
def decrypt(ciphertext, **options)
|
39
|
-
|
35
|
+
ciphertext = check_string(ciphertext, "ciphertext")
|
40
36
|
|
41
37
|
# ensure binary
|
42
|
-
ciphertext = ciphertext.to_str
|
43
38
|
if ciphertext.encoding != Encoding::BINARY
|
44
39
|
# dup to prevent mutation
|
45
40
|
ciphertext = ciphertext.dup.force_encoding(Encoding::BINARY)
|
@@ -48,9 +43,38 @@ class Lockbox
|
|
48
43
|
@boxes.each_with_index do |box, i|
|
49
44
|
begin
|
50
45
|
return box.decrypt(ciphertext, **options)
|
51
|
-
rescue
|
52
|
-
|
46
|
+
rescue => e
|
47
|
+
error_classes = [DecryptionError]
|
48
|
+
error_classes << RbNaCl::LengthError if defined?(RbNaCl::LengthError)
|
49
|
+
error_classes << RbNaCl::CryptoError if defined?(RbNaCl::CryptoError)
|
50
|
+
if error_classes.any? { |ec| e.is_a?(ec) }
|
51
|
+
raise DecryptionError, "Decryption failed" if i == @boxes.size - 1
|
52
|
+
else
|
53
|
+
raise e
|
54
|
+
end
|
53
55
|
end
|
54
56
|
end
|
55
57
|
end
|
58
|
+
|
59
|
+
def self.generate_key_pair
|
60
|
+
require "rbnacl"
|
61
|
+
# encryption and decryption servers exchange public keys
|
62
|
+
# this produces smaller ciphertext than sealed box
|
63
|
+
alice = RbNaCl::PrivateKey.generate
|
64
|
+
bob = RbNaCl::PrivateKey.generate
|
65
|
+
# alice is sending message to bob
|
66
|
+
# use bob first in both cases to prevent keys being swappable
|
67
|
+
{
|
68
|
+
encryption_key: (bob.public_key.to_bytes + alice.to_bytes).unpack("H*").first,
|
69
|
+
decryption_key: (bob.to_bytes + alice.public_key.to_bytes).unpack("H*").first
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def check_string(str, name)
|
76
|
+
str = str.read if str.respond_to?(:read)
|
77
|
+
raise TypeError, "can't convert #{name} to string" unless str.respond_to?(:to_str)
|
78
|
+
str.to_str
|
79
|
+
end
|
56
80
|
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# ideally encrypt and decrypt would happen at the blob/service level
|
2
|
+
# however, there isn't really a great place to define encryption settings there
|
3
|
+
# instead, we encrypt and decrypt at the attachment level,
|
4
|
+
# and we define encryption settings at the model level
|
1
5
|
class Lockbox
|
2
6
|
module ActiveStorageExtensions
|
3
7
|
module Attached
|
@@ -15,7 +19,7 @@ class Lockbox
|
|
15
19
|
|
16
20
|
case attachable
|
17
21
|
when ActiveStorage::Blob
|
18
|
-
raise NotImplemented, "Not supported
|
22
|
+
raise NotImplemented, "Not supported"
|
19
23
|
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
20
24
|
attachable = {
|
21
25
|
io: StringIO.new(box.encrypt(attachable.read)),
|
@@ -29,7 +33,7 @@ class Lockbox
|
|
29
33
|
content_type: attachable[:content_type]
|
30
34
|
}
|
31
35
|
when String
|
32
|
-
raise NotImplemented, "Not supported
|
36
|
+
raise NotImplemented, "Not supported"
|
33
37
|
else
|
34
38
|
nil
|
35
39
|
end
|
data/lib/lockbox/aes_gcm.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "openssl"
|
2
|
+
|
1
3
|
class Lockbox
|
2
4
|
class AES_GCM
|
3
5
|
def initialize(key)
|
@@ -9,6 +11,7 @@ class Lockbox
|
|
9
11
|
|
10
12
|
def encrypt(nonce, message, associated_data)
|
11
13
|
cipher = OpenSSL::Cipher.new("aes-256-gcm")
|
14
|
+
# do not change order of operations
|
12
15
|
cipher.encrypt
|
13
16
|
cipher.key = @key
|
14
17
|
cipher.iv = nonce
|
@@ -31,6 +34,7 @@ class Lockbox
|
|
31
34
|
fail_decryption if ciphertext.to_s.bytesize == 0
|
32
35
|
|
33
36
|
cipher = OpenSSL::Cipher.new("aes-256-gcm")
|
37
|
+
# do not change order of operations
|
34
38
|
cipher.decrypt
|
35
39
|
cipher.key = @key
|
36
40
|
cipher.iv = nonce
|
data/lib/lockbox/box.rb
CHANGED
@@ -1,54 +1,85 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
1
3
|
class Lockbox
|
2
4
|
class Box
|
3
|
-
def initialize(key, algorithm: nil)
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
def initialize(key: nil, algorithm: nil, encryption_key: nil, decryption_key: nil)
|
6
|
+
raise ArgumentError, "Cannot pass both key and public/private key" if key && (encryption_key || decryption_key)
|
7
|
+
|
8
|
+
key = decode_key(key) if key
|
9
|
+
encryption_key = decode_key(encryption_key) if encryption_key
|
10
|
+
decryption_key = decode_key(decryption_key) if decryption_key
|
8
11
|
|
9
12
|
algorithm ||= "aes-gcm"
|
10
13
|
|
11
14
|
case algorithm
|
12
15
|
when "aes-gcm"
|
16
|
+
raise ArgumentError, "Missing key" unless key
|
13
17
|
require "lockbox/aes_gcm"
|
14
18
|
@box = AES_GCM.new(key)
|
15
19
|
when "xchacha20"
|
20
|
+
raise ArgumentError, "Missing key" unless key
|
16
21
|
require "rbnacl"
|
17
22
|
@box = RbNaCl::AEAD::XChaCha20Poly1305IETF.new(key)
|
23
|
+
when "hybrid"
|
24
|
+
raise ArgumentError, "Missing key" unless encryption_key || decryption_key
|
25
|
+
require "rbnacl"
|
26
|
+
@encryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(encryption_key.slice(0, 32), encryption_key.slice(32..-1)) if encryption_key
|
27
|
+
@decryption_box = RbNaCl::Boxes::Curve25519XSalsa20Poly1305.new(decryption_key.slice(32..-1), decryption_key.slice(0, 32)) if decryption_key
|
18
28
|
else
|
19
29
|
raise ArgumentError, "Unknown algorithm: #{algorithm}"
|
20
30
|
end
|
31
|
+
|
32
|
+
@algorithm = algorithm
|
21
33
|
end
|
22
34
|
|
23
35
|
def encrypt(message, associated_data: nil)
|
24
|
-
|
25
|
-
|
36
|
+
if @algorithm == "hybrid"
|
37
|
+
raise ArgumentError, "No public key set" unless @encryption_box
|
38
|
+
raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
|
39
|
+
nonce = generate_nonce(@encryption_box)
|
40
|
+
ciphertext = @encryption_box.encrypt(nonce, message)
|
41
|
+
else
|
42
|
+
nonce = generate_nonce(@box)
|
43
|
+
ciphertext = @box.encrypt(nonce, message, associated_data)
|
44
|
+
end
|
26
45
|
nonce + ciphertext
|
27
46
|
end
|
28
47
|
|
29
48
|
def decrypt(ciphertext, associated_data: nil)
|
30
|
-
|
31
|
-
|
49
|
+
if @algorithm == "hybrid"
|
50
|
+
raise ArgumentError, "No private key set" unless @decryption_box
|
51
|
+
raise ArgumentError, "Associated data not supported with this algorithm" if associated_data
|
52
|
+
nonce, ciphertext = extract_nonce(@decryption_box, ciphertext)
|
53
|
+
@decryption_box.decrypt(nonce, ciphertext)
|
54
|
+
else
|
55
|
+
nonce, ciphertext = extract_nonce(@box, ciphertext)
|
56
|
+
@box.decrypt(nonce, ciphertext, associated_data)
|
57
|
+
end
|
32
58
|
end
|
33
59
|
|
34
|
-
# protect key for xchacha20
|
60
|
+
# protect key for xchacha20 and hybrid
|
35
61
|
def inspect
|
36
62
|
to_s
|
37
63
|
end
|
38
64
|
|
39
65
|
private
|
40
66
|
|
41
|
-
def
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
def generate_nonce
|
46
|
-
SecureRandom.random_bytes(nonce_bytes)
|
67
|
+
def generate_nonce(box)
|
68
|
+
SecureRandom.random_bytes(box.nonce_bytes)
|
47
69
|
end
|
48
70
|
|
49
|
-
def extract_nonce(bytes)
|
71
|
+
def extract_nonce(box, bytes)
|
72
|
+
nonce_bytes = box.nonce_bytes
|
50
73
|
nonce = bytes.slice(0, nonce_bytes)
|
51
74
|
[nonce, bytes.slice(nonce_bytes..-1)]
|
52
75
|
end
|
76
|
+
|
77
|
+
# decode hex key
|
78
|
+
def decode_key(key)
|
79
|
+
if key.encoding != Encoding::BINARY && key =~ /\A[0-9a-f]{64,128}\z/i
|
80
|
+
key = [key].pack("H*")
|
81
|
+
end
|
82
|
+
key
|
83
|
+
end
|
53
84
|
end
|
54
85
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Lockbox
|
2
|
+
class Encryptor
|
3
|
+
def self.encrypt(options)
|
4
|
+
box(options).encrypt(options[:value])
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.decrypt(options)
|
8
|
+
box(options).decrypt(options[:value])
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.box(options)
|
12
|
+
options = options.slice(:key, :encryption_key, :decryption_key, :algorithm, :previous_versions)
|
13
|
+
options[:algorithm] = "aes-gcm" if options[:algorithm] == "aes-256-gcm"
|
14
|
+
Lockbox.new(options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/lockbox/version.rb
CHANGED
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.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01
|
11
|
+
date: 2019-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -110,6 +110,20 @@ dependencies:
|
|
110
110
|
version: '0'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.3.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.3.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rbnacl
|
113
127
|
requirement: !ruby/object:Gem::Requirement
|
114
128
|
requirements:
|
115
129
|
- - ">="
|
@@ -123,7 +137,7 @@ dependencies:
|
|
123
137
|
- !ruby/object:Gem::Version
|
124
138
|
version: '0'
|
125
139
|
- !ruby/object:Gem::Dependency
|
126
|
-
name:
|
140
|
+
name: attr_encrypted
|
127
141
|
requirement: !ruby/object:Gem::Requirement
|
128
142
|
requirements:
|
129
143
|
- - ">="
|
@@ -150,6 +164,7 @@ files:
|
|
150
164
|
- lib/lockbox/aes_gcm.rb
|
151
165
|
- lib/lockbox/box.rb
|
152
166
|
- lib/lockbox/carrier_wave_extensions.rb
|
167
|
+
- lib/lockbox/encryptor.rb
|
153
168
|
- lib/lockbox/railtie.rb
|
154
169
|
- lib/lockbox/utils.rb
|
155
170
|
- lib/lockbox/version.rb
|