serket 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +10 -0
- data/lib/serket/configuration.rb +13 -1
- data/lib/serket/decrypted_fields.rb +19 -0
- data/lib/serket/encrypted_fields.rb +20 -0
- data/lib/serket/field_decrypter.rb +19 -2
- data/lib/serket/field_encrypter.rb +11 -3
- data/lib/serket/version.rb +1 -1
- data/spec/serket/field_encrypter_spec.rb +20 -0
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9add635d8b06b59c7bc47526c0b22b90b9bedd17
|
4
|
+
data.tar.gz: b357446a204828c351bee22546f4e4f1365d8fe6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c613a58ca1b059019288a0240a44901d97697ab5fa6f76755a6c31816dcffe0b2ebaaf4e3f58c7487e20336ed927229bd77e282e1423cf2be890e24e7db91b1
|
7
|
+
data.tar.gz: d203cd2cc7363a52aee031cf942d96d01e8eb37d66f8080111d881a619217e5e90f0db4d99acf67d228dc7dc5e1e541503c47dfc326eb2447d7fb57599cd3335
|
data/README.md
CHANGED
@@ -76,6 +76,7 @@ There are a few more configuration options.
|
|
76
76
|
| format | :delimited | :delimited, :json |
|
77
77
|
| symmetric_algorithm | AES-256-CBC | Any valid cipher from OpenSSL::Cipher |
|
78
78
|
| delimiter | :: | Anything not base64 |
|
79
|
+
| encoding | utf-8 | Any valid ruby encoding |
|
79
80
|
|
80
81
|
These can all be modified in the configuration block, eg:
|
81
82
|
|
@@ -126,6 +127,15 @@ Serket.configure do |config|
|
|
126
127
|
end
|
127
128
|
```
|
128
129
|
|
130
|
+
You can do a sanity-check with your api endpoint by sending a post request for a model with decrypted_fields:
|
131
|
+
```
|
132
|
+
curl -v -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"decrypted_model": {"name": "bm5gkjwdfv6QBUqFPEnOaw==::CtQJyXdbuLeVzmIOIr0h/F9yh7xvz5KWUgxe/u4IkORGOW4KjU4bw+Wzve2vV1nLYUEWJJprr8sb+grm+Ao2sngNejiHzSkJKqZA/Pclw/Ok8KgHgN7olUz4BoCSdivIDRIT9ar06sNBrqOvLd4iGUlpMkpLdSJ69K08ebSvg5tED+PcK/oI6SJoVxRoUMYdYa9AfeIS9Ld5BgvhsaJgCKr089kfH2CzwpzlmRfdxb2qgyDXnk9PG/4WUEjjbamF/R74FNBdWkTLxZeLGdMImh87CQ6AOJ/v8l1JSzpPWwEjtmhTbFEzJPuA01tP5U5D07si0esJnab/B48iACEoLg==::iSmdDgnTzkEUv0yLbtFa8Q=="}}' http://localhost:3000/api/v1/decrypted_models
|
133
|
+
```
|
134
|
+
|
135
|
+
This should automatically decrypt the name for the decrypted_model before saving if you use the provided private key located in spec/resources/test_private_key.pem (and keep the default configs for delimited with ::)
|
136
|
+
|
137
|
+
**Just be sure to use your own keys in production!**
|
138
|
+
|
129
139
|
### Android Java Client
|
130
140
|
|
131
141
|
You can see an example java client for use with Android in EncryptUtil.java
|
data/lib/serket/configuration.rb
CHANGED
@@ -1,11 +1,23 @@
|
|
1
1
|
module Serket
|
2
|
+
# Controls various configuration options such as key locations, desired
|
3
|
+
# algorithms and expected cipher text format.
|
4
|
+
#
|
5
|
+
# * +private_key_path+ - The filepath to a RSA private key
|
6
|
+
# * +public_key_path+ - The filepath to a RSA public key
|
7
|
+
# * +delimiter+ - If the format is set to :delimited, use this delimiter.
|
8
|
+
# Must not be a character in base64.
|
9
|
+
# * +format+ - May be :delimited or :json
|
10
|
+
# * +symmetric_algorithm+ - May be any algorithm that may be used with OpenSSL::Cipher
|
11
|
+
# * +encoding+ - Any valid ruby encoding
|
12
|
+
#
|
2
13
|
class Configuration
|
3
|
-
attr_accessor :private_key_path, :public_key_path, :delimiter, :format, :symmetric_algorithm
|
14
|
+
attr_accessor :private_key_path, :public_key_path, :delimiter, :format, :symmetric_algorithm, :encoding
|
4
15
|
|
5
16
|
def initialize
|
6
17
|
@delimiter = "::"
|
7
18
|
@format = :delimited
|
8
19
|
@symmetric_algorithm = 'AES-256-CBC'
|
20
|
+
@encoding = 'utf-8'
|
9
21
|
end
|
10
22
|
end
|
11
23
|
end
|
@@ -1,4 +1,23 @@
|
|
1
1
|
module Serket
|
2
|
+
# If a class extends DecryptedFields, then the getter method will be overridden
|
3
|
+
# for each attribute that is listed.
|
4
|
+
#
|
5
|
+
# For example:
|
6
|
+
#
|
7
|
+
# class Person
|
8
|
+
# extend Serket::DecryptedFields
|
9
|
+
#
|
10
|
+
# decrypted_fields :name, :email
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# This will create a method name=(value) and email=(value)
|
14
|
+
#
|
15
|
+
# It assumes the encrypted name and email will be assigned to an
|
16
|
+
# instance of person, and will automatically decrypt the encrypted
|
17
|
+
# value using the configured private key.
|
18
|
+
#
|
19
|
+
# This currently relies on the write_attribute, which is available in
|
20
|
+
# ActiveRecord::Base. This currently is only intended for use with rails.
|
2
21
|
module DecryptedFields
|
3
22
|
def decrypted_fields(*fields)
|
4
23
|
fields.each do |field|
|
@@ -1,4 +1,24 @@
|
|
1
1
|
module Serket
|
2
|
+
# If a class extends EncryptedFields, then the getter method will be overridden
|
3
|
+
# for each attribute that is listed.
|
4
|
+
#
|
5
|
+
# For example:
|
6
|
+
#
|
7
|
+
# class Person
|
8
|
+
# extend Serket::EncryptedFields
|
9
|
+
#
|
10
|
+
# encrypted_fields :name, :email
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# This will create a method name=(value) and email=(value)
|
14
|
+
#
|
15
|
+
# When a value is assigned to name or email for a given person instance,
|
16
|
+
# it will automatically generate an encrypted version using the
|
17
|
+
# currently configured public key, and will store the cipher text instead
|
18
|
+
# of the plaintext value that was assigned.
|
19
|
+
#
|
20
|
+
# This currently relies on the write_attribute, which is available in
|
21
|
+
# ActiveRecord::Base. This currently is only intended for use with rails.
|
2
22
|
module EncryptedFields
|
3
23
|
def encrypted_fields(*fields)
|
4
24
|
fields.each do |field|
|
@@ -2,8 +2,10 @@ require 'openssl'
|
|
2
2
|
require 'base64'
|
3
3
|
|
4
4
|
module Serket
|
5
|
+
# Used to decrypt a field given a private key, field delimiter, symmetric
|
6
|
+
# algorithm, encoding, and format (:json or :delimited)
|
5
7
|
class FieldDecrypter
|
6
|
-
attr_accessor :field_delimiter, :private_key_filepath, :symmetric_algorithm
|
8
|
+
attr_accessor :field_delimiter, :private_key_filepath, :symmetric_algorithm, :encoding
|
7
9
|
|
8
10
|
def initialize(options = {})
|
9
11
|
options ||= {}
|
@@ -12,16 +14,23 @@ module Serket
|
|
12
14
|
@field_delimiter = options[:field_delimiter] || Serket.configuration.delimiter
|
13
15
|
@symmetric_algorithm = options[:symmetric_algorithm] || Serket.configuration.symmetric_algorithm
|
14
16
|
@format = options[:format] || Serket.configuration.format
|
17
|
+
@encoding = options[:encoding] || Serket.configuration.encoding
|
15
18
|
end
|
16
19
|
|
20
|
+
# Decrypt the provided cipher text, and return the plaintext
|
21
|
+
# Return nil if whitespace
|
17
22
|
def decrypt(field)
|
18
23
|
return if field !~ /\S/
|
19
24
|
iv, encrypted_aes_key, encrypted_text = parse(field)
|
20
25
|
private_key = OpenSSL::PKey::RSA.new(File.read(private_key_filepath))
|
21
26
|
decrypted_aes_key = private_key.private_decrypt(Base64.decode64(encrypted_aes_key))
|
22
|
-
decrypt_data(iv, decrypted_aes_key, encrypted_text)
|
27
|
+
decrypted_field = decrypt_data(iv, decrypted_aes_key, encrypted_text)
|
28
|
+
decrypted_field.force_encoding(encoding)
|
23
29
|
end
|
24
30
|
|
31
|
+
# What delimiter to use if the format is :delimited.
|
32
|
+
#
|
33
|
+
# Allow anything that is not base64.
|
25
34
|
def field_delimiter=(delimiter)
|
26
35
|
if delimiter =~ /[A-Za-z0-9\/+]/
|
27
36
|
raise "This is not a valid delimiter! Must not be a character in Base64."
|
@@ -39,6 +48,14 @@ module Serket
|
|
39
48
|
aes.update(Base64.decode64(encrypted_text)) + aes.final
|
40
49
|
end
|
41
50
|
|
51
|
+
# Extracts the initialization vector, encrypted key, and
|
52
|
+
# cipher text according to the specified format.
|
53
|
+
#
|
54
|
+
# delimited:
|
55
|
+
# * Expected format: iv::encrypted-key::ciphertext
|
56
|
+
#
|
57
|
+
# json:
|
58
|
+
# * Expected keys: iv, key, message
|
42
59
|
def parse(field)
|
43
60
|
case @format
|
44
61
|
when :delimited
|
@@ -2,26 +2,32 @@ require 'openssl'
|
|
2
2
|
require 'base64'
|
3
3
|
|
4
4
|
module Serket
|
5
|
+
# Used to encrypt a field given a public key, field delimiter, symmetric
|
6
|
+
# algorithm, encoding and format (:json or :delimited)
|
5
7
|
class FieldEncrypter
|
6
|
-
attr_accessor :field_delimiter, :public_key_filepath, :symmetric_algorithm
|
8
|
+
attr_accessor :field_delimiter, :public_key_filepath, :symmetric_algorithm, :encoding
|
7
9
|
|
8
10
|
def initialize(options = {})
|
9
11
|
options ||= {}
|
10
12
|
|
11
|
-
@public_key_filepath
|
13
|
+
@public_key_filepath = Serket.configuration.public_key_path
|
12
14
|
@field_delimiter = options[:field_delimiter] || Serket.configuration.delimiter
|
13
15
|
@symmetric_algorithm = options[:symmetric_algorithm] || Serket.configuration.symmetric_algorithm
|
14
16
|
@format = options[:format] || Serket.configuration.format
|
17
|
+
@encoding = options[:encoding] || Serket.configuration.encoding
|
15
18
|
end
|
16
19
|
|
20
|
+
# Return encrypted string according to specified format.
|
21
|
+
# Return nil if field is whitespace.
|
17
22
|
def encrypt(field)
|
18
23
|
return if field !~ /\S/
|
19
24
|
aes = OpenSSL::Cipher.new(symmetric_algorithm)
|
20
25
|
aes_key = aes.random_key
|
21
26
|
iv = aes.random_iv
|
22
|
-
encrypt_data(iv, aes_key, field)
|
27
|
+
encrypt_data(iv, aes_key, field.force_encoding(encoding))
|
23
28
|
end
|
24
29
|
|
30
|
+
# Allow any field delimiter except a base64 character.
|
25
31
|
def field_delimiter=(delimiter)
|
26
32
|
if delimiter =~ /[A-Za-z0-9\/+]/
|
27
33
|
raise "This is not a valid delimiter! Must not be a character in Base64."
|
@@ -44,6 +50,8 @@ module Serket
|
|
44
50
|
parse(Base64.encode64(iv), Base64.encode64(encrypted_aes_key), Base64.encode64(encrypted_text))
|
45
51
|
end
|
46
52
|
|
53
|
+
# Format the final encrypted string to be returned depending
|
54
|
+
# on specified format.
|
47
55
|
def parse(iv, encrypted_key, encrypted_text)
|
48
56
|
case @format
|
49
57
|
when :delimited
|
data/lib/serket/version.rb
CHANGED
@@ -34,4 +34,24 @@ describe Serket::FieldEncrypter do
|
|
34
34
|
@parsed_json.has_key?('message').should be_true
|
35
35
|
end
|
36
36
|
end
|
37
|
+
|
38
|
+
describe "encoding" do
|
39
|
+
it "should encrypt and decrypt an accent character" do
|
40
|
+
characters = %w(á é í ó ú ü ñ ¿ ¡)
|
41
|
+
characters.each do |c|
|
42
|
+
encrypted = @field_encrypter.encrypt(c)
|
43
|
+
decrypted = @field_decrypter.decrypt(encrypted)
|
44
|
+
c.should == decrypted
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should encrypt and decrypt khmer" do
|
49
|
+
characters = %w(ឃ ង ទ ម វ ស )
|
50
|
+
characters.each do |c|
|
51
|
+
encrypted = @field_encrypter.encrypt(c)
|
52
|
+
decrypted = @field_decrypter.decrypt(encrypted)
|
53
|
+
c.should == decrypted
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
37
57
|
end
|