crypt_keeper 0.22.0 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +5 -1
  4. data/Appraisals +6 -6
  5. data/README.md +8 -20
  6. data/Rakefile +7 -3
  7. data/crypt_keeper.gemspec +5 -5
  8. data/gemfiles/{activerecord_4_1.gemfile → activerecord_5_0.gemfile} +2 -2
  9. data/lib/crypt_keeper.rb +1 -2
  10. data/lib/crypt_keeper/helper.rb +0 -18
  11. data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +7 -9
  12. data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +7 -9
  13. data/lib/crypt_keeper/model.rb +14 -20
  14. data/lib/crypt_keeper/provider/aes_new.rb +1 -1
  15. data/lib/crypt_keeper/provider/base.rb +21 -0
  16. data/lib/crypt_keeper/provider/mysql_aes_new.rb +1 -1
  17. data/lib/crypt_keeper/provider/postgres_pgp.rb +2 -2
  18. data/lib/crypt_keeper/provider/postgres_pgp_public_key.rb +1 -1
  19. data/lib/crypt_keeper/version.rb +1 -1
  20. data/spec/crypt_keeper/log_subscriber/mysql_aes_spec.rb +56 -0
  21. data/spec/crypt_keeper/log_subscriber/postgres_pgp_spec.rb +94 -0
  22. data/spec/crypt_keeper/model_spec.rb +172 -0
  23. data/spec/crypt_keeper/provider/aes_new_spec.rb +41 -0
  24. data/spec/crypt_keeper/provider/mysql_aes_new_spec.rb +50 -0
  25. data/spec/crypt_keeper/provider/postgres_pgp_public_key_spec.rb +66 -0
  26. data/spec/crypt_keeper/provider/postgres_pgp_spec.rb +66 -0
  27. data/spec/spec_helper.rb +0 -1
  28. data/spec/support/encryptors.rb +9 -3
  29. data/spec/support/logging.rb +92 -0
  30. metadata +37 -44
  31. data/gemfiles/activerecord_4_1.gemfile.lock +0 -120
  32. data/gemfiles/activerecord_4_2.gemfile.lock +0 -120
  33. data/lib/crypt_keeper/provider/aes.rb +0 -66
  34. data/lib/crypt_keeper/provider/mysql_aes.rb +0 -47
  35. data/spec/log_subscriber/mysql_aes_spec.rb +0 -73
  36. data/spec/log_subscriber/postgres_pgp_spec.rb +0 -123
  37. data/spec/model_spec.rb +0 -169
  38. data/spec/provider/aes_new_spec.rb +0 -45
  39. data/spec/provider/aes_spec.rb +0 -67
  40. data/spec/provider/mysql_aes_new_spec.rb +0 -54
  41. data/spec/provider/mysql_aes_spec.rb +0 -35
  42. data/spec/provider/postgres_pgp_public_key_spec.rb +0 -70
  43. data/spec/provider/postgres_pgp_spec.rb +0 -70
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::LogSubscriber::PostgresPgp do
4
+ before do
5
+ CryptKeeper.silence_logs = false
6
+ end
7
+
8
+ use_postgres
9
+
10
+ context "Symmetric encryption" do
11
+ # Fire the ActiveSupport.on_load
12
+ before do
13
+ CryptKeeper::Provider::PostgresPgp.new key: 'secret'
14
+ end
15
+
16
+ let(:input_query) do
17
+ "SELECT pgp_sym_encrypt('encrypt_value', 'encrypt_key'), pgp_sym_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
18
+ end
19
+
20
+ let(:output_query) do
21
+ "SELECT encrypt([FILTERED]) FROM DUAL;"
22
+ end
23
+
24
+ let(:input_search_query) do
25
+ "SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE ((pgp_sym_decrypt('f'), 'tool') = 'blah')) AND secret = 'testing'"
26
+ end
27
+
28
+ let(:output_search_query) do
29
+ "SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE decrypt([FILTERED]) AND secret = 'testing'"
30
+ end
31
+
32
+ it "filters pgp functions" do
33
+ should_log_scrubbed_query(input: input_query, output: output_query)
34
+ end
35
+
36
+ it "filters pgp functions in lowercase" do
37
+ should_log_scrubbed_query(input: input_query.downcase, output: output_query.downcase.gsub(/filtered/, 'FILTERED'))
38
+ end
39
+
40
+ it "filters pgp functions when searching" do
41
+ should_log_scrubbed_query(input: input_search_query, output: output_search_query)
42
+ end
43
+
44
+ it "forces string encodings" do
45
+ input_query = "SELECT pgp_sym_encrypt('hi \255', 'test') FROM DUAL;"
46
+
47
+ should_log_scrubbed_query(input: input_query, output: output_query)
48
+ end
49
+
50
+ it "skips logging if CryptKeeper.silence_logs is set" do
51
+ CryptKeeper.silence_logs = true
52
+
53
+ should_not_log_query(input_query)
54
+ end
55
+ end
56
+
57
+ context "Public key encryption" do
58
+ let(:public_key) do
59
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
60
+ end
61
+
62
+ let(:private_key) do
63
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
64
+ end
65
+
66
+ # Fire the ActiveSupport.on_load
67
+ before do
68
+ CryptKeeper::Provider::PostgresPgpPublicKey.new key: 'secret', public_key: public_key, private_key: private_key
69
+ end
70
+
71
+ let(:input_query) do
72
+ "SELECT pgp_pub_encrypt('test', dearmor('#{public_key}
73
+ '))"
74
+ end
75
+
76
+ let(:output_query) do
77
+ "SELECT encrypt([FILTERED])"
78
+ end
79
+
80
+ it "filters pgp functions" do
81
+ should_log_scrubbed_query(input: input_query, output: output_query)
82
+ end
83
+
84
+ it "filters pgp functions in lowercase" do
85
+ should_log_scrubbed_query(input: input_query.downcase, output: output_query.downcase.gsub(/filtered/, 'FILTERED'))
86
+ end
87
+
88
+ it "skips logging if CryptKeeper.silence_logs is set" do
89
+ CryptKeeper.silence_logs = true
90
+
91
+ should_not_log_query(input_query)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe CryptKeeper::Model do
6
+ use_sqlite
7
+
8
+ subject { create_model }
9
+
10
+ after do
11
+ CryptKeeper.stub_encryption = false
12
+ end
13
+
14
+ describe "#crypt_keeper" do
15
+ context "Fields" do
16
+ it "enables encryption for the given fields" do
17
+ subject.crypt_keeper :storage, :secret, encryptor: :fake_encryptor
18
+ expect(subject.crypt_keeper_fields).to eq([:storage, :secret])
19
+ end
20
+
21
+ it "raises an exception for missing field" do
22
+ msg = "Column :none does not exist"
23
+ subject.crypt_keeper :none, encryptor: :fake_encryptor
24
+ expect { subject.new.save }.to raise_error(ArgumentError, msg)
25
+ end
26
+
27
+ it "raises an exception for non text fields" do
28
+ msg = "Column :name must be of type 'text' to be used for encryption"
29
+ subject.crypt_keeper :name, encryptor: :fake_encryptor
30
+ expect { subject.new.save }.to raise_error(ArgumentError, msg)
31
+ end
32
+ end
33
+
34
+ context "Options" do
35
+ it "accepts the class name as a string" do
36
+ subject.crypt_keeper :storage, :secret, key1: 1, key2: 2, encryptor: "FakeEncryptor"
37
+ expect(subject.send(:encryptor_klass)).to eq(CryptKeeper::Provider::FakeEncryptor)
38
+ end
39
+
40
+ it "raises an error on missing encryptor" do
41
+ expect { subject.crypt_keeper :storage, :secret }.
42
+ to raise_error(ArgumentError, /You must specify a valid encryptor/)
43
+ end
44
+
45
+ it "raises an error on encryptor without base" do
46
+ expect { subject.crypt_keeper :storage, encryptor: "InvalidEncryptor" }.
47
+ to raise_error(ArgumentError, /You must specify a valid encryptor/)
48
+ end
49
+ end
50
+ end
51
+
52
+ context "Encryption and Decryption" do
53
+ let(:plain_text) { 'plain_text' }
54
+ let(:cipher_text) { 'tooltxet_nialp' }
55
+
56
+ subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :encryptor }
57
+
58
+ it "encrypts the data" do
59
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:encrypt).with('testing')
60
+ subject.create!(storage: 'testing')
61
+ end
62
+
63
+ it "decrypts the data" do
64
+ record = subject.create!(storage: 'testing')
65
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:decrypt).at_least(1).times.with('toolgnitset')
66
+ subject.find(record.id).storage
67
+ end
68
+
69
+ it "returns the plaintext on decrypt" do
70
+ record = subject.create!(storage: 'testing')
71
+ expect(subject.find(record.id).storage).to eq('testing')
72
+ end
73
+
74
+ it "does not encrypt or decrypt nil" do
75
+ data = subject.create!(storage: nil)
76
+ expect(data.storage).to be_nil
77
+ end
78
+
79
+ it "does not encrypt or decrypt empty strings" do
80
+ data = subject.create!(storage: "")
81
+ expect(data.storage).to be_empty
82
+ end
83
+
84
+ it "converts numbers to strings" do
85
+ data = subject.create!(storage: 1)
86
+ expect(data.reload.storage).to eq("1")
87
+ end
88
+
89
+ it "does not decrypt when stubbing is enabled" do
90
+ CryptKeeper.stub_encryption = true
91
+ record = subject.create!(storage: "testing")
92
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to_not receive(:decrypt)
93
+ subject.find(record.id).storage
94
+ end
95
+
96
+ it "does not decrypt when stubbing is enabled after model is created" do
97
+ record = subject.create!(storage: "testing")
98
+ CryptKeeper.stub_encryption = true
99
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to_not receive(:decrypt)
100
+ subject.find(record.id).storage
101
+ end
102
+ end
103
+
104
+ context "Search" do
105
+ subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :search_encryptor }
106
+
107
+ it "searches if supported" do
108
+ expect { subject.search_by_plaintext(:storage, 'test1') }.to_not raise_error
109
+ end
110
+
111
+ it "complains about bad columns" do
112
+ expect { subject.search_by_plaintext(:what, 'test1') }.to raise_error(/what is not a crypt_keeper field/)
113
+ end
114
+ end
115
+
116
+ context "Encodings" do
117
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
118
+
119
+ it "forces the encoding on decrypt" do
120
+ record = subject.create!(storage: 'Tromsø')
121
+ record.reload
122
+ expect(record.storage).to eql('Tromsø')
123
+ end
124
+
125
+ it "converts from other encodings" do
126
+ plaintext = "\xC2\xA92011 AACR".force_encoding('ASCII-8BIT')
127
+ record = subject.create!(storage: plaintext)
128
+ record.reload
129
+ expect(record.storage.encoding.name).to eql('UTF-8')
130
+ end
131
+ end
132
+
133
+ context "Initial Table Encryption" do
134
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
135
+
136
+ before do
137
+ subject.delete_all
138
+ c = create_model
139
+ 5.times { |i| c.create! storage: "testing#{i}" }
140
+ end
141
+
142
+ it "encrypts the table" do
143
+ expect { subject.first(5).map(&:storage) }.to raise_error(OpenSSL::Cipher::CipherError)
144
+ subject.encrypt_table!
145
+ expect { subject.first(5).map(&:storage) }.not_to raise_error
146
+ end
147
+ end
148
+
149
+ context "Table Decryption (Reverse of Initial Table Encryption)" do
150
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
151
+ let!(:storage_entries) { 5.times.map { |i| "testing#{i}" } }
152
+
153
+ before do
154
+ subject.delete_all
155
+ storage_entries.each { |entry| subject.create! storage: entry}
156
+ end
157
+
158
+ it "decrypts the table" do
159
+ subject.decrypt_table!
160
+ expect( create_model.first(5).map(&:storage) ).to eq( storage_entries )
161
+ end
162
+ end
163
+
164
+ context "Missing Attributes" do
165
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
166
+
167
+ it "doesn't attempt decryption of missing attributes" do
168
+ subject.create!(storage: 'blah')
169
+ expect { subject.select(:id).first }.to_not raise_error
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::AesNew do
4
+ subject { described_class.new(key: 'cake', salt: 'salt') }
5
+
6
+ describe "#initialize" do
7
+ let(:digested_key) do
8
+ ::Armor.digest('cake', 'salt')
9
+ end
10
+
11
+ specify { expect(subject.key).to eq(digested_key) }
12
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
13
+ end
14
+
15
+ describe "#encrypt" do
16
+ let(:encrypted) do
17
+ subject.encrypt 'string'
18
+ end
19
+
20
+ specify { expect(encrypted).to_not eq('string') }
21
+ specify { expect(encrypted).to_not be_blank }
22
+ end
23
+
24
+ describe "#decrypt" do
25
+ let(:decrypted) do
26
+ subject.decrypt "V02ebRU2wLk25AizasROVg==$kE+IpRaUNdBfYqR+WjMqvA=="
27
+ end
28
+
29
+ specify { expect(decrypted).to eq('string') }
30
+ end
31
+
32
+ describe "#search" do
33
+ let(:records) do
34
+ [{ name: 'Bob' }, { name: 'Tim' }]
35
+ end
36
+
37
+ it "finds the matching record" do
38
+ expect(subject.search(records, :name, 'Bob')).to eql([records.first])
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::MysqlAesNew do
4
+ use_mysql
5
+
6
+ let(:plain_text) { 'test' }
7
+
8
+ # MySQL stores AES encrypted strings in binary which you can't paste
9
+ # into a spec :). This is a Base64 encoded string of 'test' AES encrypted
10
+ # by AES_ENCRYPT()
11
+ let(:cipher_text) do
12
+ "fBN8i7bx/DGAA4NJ4EWi0A=="
13
+ end
14
+
15
+ subject { described_class.new key: ENCRYPTION_PASSWORD, salt: 'salt' }
16
+
17
+ specify { expect(subject.key).to eq("825e8c5e8ca394818b307b22b8cb7d3df2735e9c1e5838b476e7719135a4f499f2133022c1a0e8597c9ac1507b0f0c44328a40049f9704fab3598c5dec120724") }
18
+
19
+ describe "#initialize" do
20
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
21
+ specify { expect { described_class.new(key: 'blah') }.to raise_error(ArgumentError, "Missing :salt") }
22
+ end
23
+
24
+ describe "#encrypt" do
25
+ specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
26
+ specify { expect(subject.encrypt(plain_text)).to_not be_blank }
27
+ end
28
+
29
+ describe "#decrypt" do
30
+ specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
31
+ end
32
+
33
+ describe "#search" do
34
+ subject { mysql_model }
35
+
36
+ it "finds the matching record" do
37
+ subject.create!(storage: 'blah2')
38
+ match = subject.create!(storage: 'blah')
39
+ expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
40
+ end
41
+
42
+ it "keeps the scope" do
43
+ subject.create!(storage: 'blah')
44
+ subject.create!(storage: 'blah')
45
+
46
+ scope = subject.limit(1)
47
+ expect(scope.search_by_plaintext(:storage, 'blah').count).to eq(1)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::PostgresPgpPublicKey do
4
+ use_postgres
5
+
6
+ let(:cipher_text) { '\xc1c04c036c401ad086beb9e3010800987d6c4ccd974322190caa75a3a01aba37bc1970182c4c1d3faec98edf186780520f0586101f286e0626096a1eca91a229ed4d4058a6913a8d13cdf49f29ea44e2b96d10347f9b1b860bb3c959f000a3b1b415a95d2cd07af8c74aa6df8cd10ab06b6a6f7db69cdf3185466d68c5b66b95b813acdfb3ddfb021cac92e0967d67e90df73332f27970c1d2b9a56ac74f602d4107b163ed73ef89fca560d9a0a0d2bc7a74005f29fa27babfbaf950ac07b1c809049db4ab126be4824cf76416c278571f7064f638edf830a1ae5ee1ab544d35fce0f974f21b9dcbbea3986077d27b0de34144dc23f369f471090b57e067a056901e680493ddf2a6b29e4af3462387d235010259556079d07daa249b6703e2bc79345da556cfb46f228cad40a8a5b569ac46f08865f9176acf89129a3e0ceb2a7b1991012f65' }
7
+
8
+ let(:integer_cipher_text) { '\xc1c04c036c401ad086beb9e30107ff59e674ba05958eb053c2427b44355e0f333f1726e18a0b851130130510c648f580b13b3f6a223eb26e397008596867c5a511a4f5bfbf2ecc852d8929814480d63166e525fa2b259b6a8d4474b5b1373b4e1a4fe70a491d25442e1c0046fd3d69466ad30153c8d8d920e9b4260d4e4e421ef3ead162b3aba5d85408c4ef9f9d342b5655c7568d1bdc61c27ddb419133bf091f22f42e7bc91ec6d279b7b25b87ea65119568b85ae81079dd0a6a7258b58fb219c6cc4580f33cb46de97770a1eb0880bdf87426fd0529576a1e791e521d9b3c426e393e63d83321f319b00f9dc4027ea5a81dd57c0f5ba868fb86d73179c34f2287c437266e8becc072b45a929562d2320194be54464e03854635d0f7d7fb10813adbc6efe51efa9095a9bacc2a03fb5c41d1c1896384e4f36b100c0f00e81d4cff7d' }
9
+
10
+ let(:integer_plain_text) { 1 }
11
+ let(:plain_text) { 'test' }
12
+
13
+ let(:public_key) do
14
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
15
+ end
16
+
17
+ let(:private_key) do
18
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
19
+ end
20
+
21
+ subject { described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key, private_key: private_key }
22
+
23
+
24
+ specify { expect(subject.key).to eq(ENCRYPTION_PASSWORD) }
25
+
26
+ describe "#initialize" do
27
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
28
+ end
29
+
30
+ describe "#encrypt" do
31
+ context "Strings" do
32
+ specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
33
+ specify { expect(subject.encrypt(plain_text)).to_not be_empty }
34
+
35
+ it "does not double encrypt" do
36
+ pgp = described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key
37
+ expect(pgp.encrypt(cipher_text)).to eq(cipher_text)
38
+ end
39
+ end
40
+
41
+ context "Integers" do
42
+ specify { expect(subject.encrypt(integer_plain_text)).to_not eq(integer_plain_text) }
43
+ specify { expect(subject.encrypt(integer_plain_text)).to_not be_empty }
44
+ end
45
+ end
46
+
47
+ describe "#decrypt" do
48
+ specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
49
+ specify { expect(subject.decrypt(integer_cipher_text)).to eq(integer_plain_text.to_s) }
50
+
51
+ it "does not decrypt w/o private key" do
52
+ pgp = described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key
53
+ expect(pgp.decrypt(cipher_text)).to eq(cipher_text)
54
+ end
55
+ end
56
+
57
+ describe "#encrypted?" do
58
+ it "returns true for encrypted strings" do
59
+ expect(subject.encrypted?(cipher_text)).to be_truthy
60
+ end
61
+
62
+ it "returns false for non-encrypted strings" do
63
+ expect(subject.encrypted?(plain_text)).to be_falsey
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::PostgresPgp do
4
+ use_postgres
5
+
6
+ let(:cipher_text) { '\xc30d04070302f1a092093988b26873d235017203ce086a53fce1925dc39b4e972e534f192d10b94af3dcf8589abc1f828456f5d3e20b225d56006ffd1e312e3b8a492a6010e9' }
7
+ let(:plain_text) { 'test' }
8
+
9
+ let(:integer_cipher_text) { '\xc30d04070302c8d266353bcf2fc07dd23201153f9d9c32fbb3c36b9b0db137bf8b6c609172210d89ded63f11dff23d1ddbf5111c0266549dde26175c4425e06bb4bd6f' }
10
+
11
+ let(:integer_plain_text) { 1 }
12
+
13
+ subject { described_class.new key: ENCRYPTION_PASSWORD }
14
+
15
+ specify { expect(subject.key).to eq(ENCRYPTION_PASSWORD) }
16
+
17
+ describe "#initialize" do
18
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
19
+ end
20
+
21
+ describe "#encrypt" do
22
+ context "Strings" do
23
+ specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
24
+ specify { expect(subject.encrypt(plain_text)).to_not be_empty }
25
+ end
26
+
27
+ context "Integers" do
28
+ specify { expect(subject.encrypt(integer_plain_text)).to_not eq(integer_plain_text) }
29
+ specify { expect(subject.encrypt(integer_plain_text)).to_not be_empty }
30
+ end
31
+ end
32
+
33
+ describe "#decrypt" do
34
+ specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
35
+ specify { expect(subject.decrypt(integer_cipher_text)).to eq(integer_plain_text.to_s) }
36
+ end
37
+
38
+ describe "#search" do
39
+ subject { postgres_model }
40
+
41
+ it "finds the matching record" do
42
+ subject.create!(storage: 'blah2')
43
+ match = subject.create!(storage: 'blah')
44
+ expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
45
+ end
46
+ end
47
+
48
+ describe "Custom pgcrypto options" do
49
+ let(:pgcrypto_options) { 'compress-level=0' }
50
+
51
+ subject { described_class.new key: 'candy', pgcrypto_options: pgcrypto_options }
52
+
53
+ it "reads and writes" do
54
+ queries = logged_queries do
55
+ encrypted = subject.encrypt(plain_text)
56
+ expect(subject.decrypt(encrypted)).to eq(plain_text)
57
+ end
58
+
59
+ expect(queries).to_not be_empty
60
+
61
+ queries.select { |query| query.include?("pgp_sym_encrypt") }.each do |q|
62
+ expect(q).to include(pgcrypto_options)
63
+ end
64
+ end
65
+ end
66
+ end