serket 0.0.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 +7 -0
- data/.gitignore +17 -0
- data/EncryptUtil.java +125 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +111 -0
- data/Rakefile +1 -0
- data/lib/serket/configuration.rb +11 -0
- data/lib/serket/decrypted_fields.rb +13 -0
- data/lib/serket/encrypted_fields.rb +13 -0
- data/lib/serket/field_decrypter.rb +52 -0
- data/lib/serket/field_encrypter.rb +59 -0
- data/lib/serket/version.rb +3 -0
- data/lib/serket.rb +20 -0
- data/serket.gemspec +24 -0
- data/spec/resources/test_private_key.pem +27 -0
- data/spec/resources/test_public_key.pem +9 -0
- data/spec/serket/decrypted_fields_spec.rb +15 -0
- data/spec/serket/encrypted_fields_spec.rb +16 -0
- data/spec/serket/field_decrypter_spec.rb +55 -0
- data/spec/serket/field_encrypter_spec.rb +33 -0
- data/spec/serket/models/encrypted_model.rb +15 -0
- data/spec/spec_helper.rb +13 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2ad79717d0198ee41fa6b33200a5e3981b38e23f
|
4
|
+
data.tar.gz: 4a81d8df831e731c119852f48d41ba1a9b697b97
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9fe9da5c68f650defa989dc9ff38b95e888919efce12c8095b1fd74135dd47aecb7e9b206b1f21dc035a0d20afb0551c1195e038f98ef64c54acab19f8d905eb
|
7
|
+
data.tar.gz: b422b32fbddeb028a070b758a4dec4d90406ed85b25a01aeb827ca175e6d778b66c1d26999ecd4c5cd23740e31aaacd4a46977b6bad90b7d5ead469378de3c6e
|
data/.gitignore
ADDED
data/EncryptUtil.java
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
package org.adaptlab.chpir.android.survey;
|
2
|
+
|
3
|
+
import java.nio.charset.Charset;
|
4
|
+
import java.security.InvalidKeyException;
|
5
|
+
import java.security.KeyFactory;
|
6
|
+
import java.security.NoSuchAlgorithmException;
|
7
|
+
import java.security.NoSuchProviderException;
|
8
|
+
import java.security.PublicKey;
|
9
|
+
import java.security.Security;
|
10
|
+
import java.security.spec.InvalidKeySpecException;
|
11
|
+
import java.security.spec.X509EncodedKeySpec;
|
12
|
+
|
13
|
+
import javax.crypto.BadPaddingException;
|
14
|
+
import javax.crypto.Cipher;
|
15
|
+
import javax.crypto.IllegalBlockSizeException;
|
16
|
+
import javax.crypto.KeyGenerator;
|
17
|
+
import javax.crypto.NoSuchPaddingException;
|
18
|
+
import javax.crypto.SecretKey;
|
19
|
+
import javax.crypto.spec.SecretKeySpec;
|
20
|
+
|
21
|
+
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
22
|
+
|
23
|
+
import android.util.Base64;
|
24
|
+
import android.util.Log;
|
25
|
+
|
26
|
+
/*
|
27
|
+
* Encrypts AES key with RSA using provided public key, and encrypts
|
28
|
+
* provided text with AES CBC.
|
29
|
+
*
|
30
|
+
* Returns string with delimited base 64 encoded iv, cipher key, and encrypted text.
|
31
|
+
*
|
32
|
+
* This is intended for using spongycastle with Android.
|
33
|
+
*/
|
34
|
+
public class EncryptUtil {
|
35
|
+
private final static String TAG = "EncryptUtil";
|
36
|
+
private final static String ASYMMETRIC_ALGO = "RSA/None/PKCS1Padding";
|
37
|
+
private final static String SYMMETRIC_ALGO = "AES/CBC/PKCS5Padding";
|
38
|
+
private final static int SYMMETRIC_KEY_SIZE = 256;
|
39
|
+
private final static String PROVIDER = "SC";
|
40
|
+
private final static String FIELD_DELIMITER = "::"; // delimits iv, cipher key, and encrypted text
|
41
|
+
static {
|
42
|
+
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
43
|
+
}
|
44
|
+
|
45
|
+
private static PublicKey PUBLIC_KEY;
|
46
|
+
private final static String KEY_STRING = "" +
|
47
|
+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr19m7jjA3"
|
48
|
+
+ "2IrF9io\ns9MWth119faF4Xc7clCtWWi9ncmMSBq0uQsASkpf/J"
|
49
|
+
+ "VdVi91TZbzdzhInhhZ\nomMh+26r8oiAibRHRcxGRyCIUhEUZQu"
|
50
|
+
+ "R8QeeaO5wugpg1zOYuYrevDH+MxuY\nebeAcqccW91ZQBDxsUKY"
|
51
|
+
+ "jSiVIhm/Cmas/J/g9m+k+HKUGHREVzNZSRIToxuV\nc2DHVylWT"
|
52
|
+
+ "0NPN14OUnOt4PHJZED/QwiiNFWo/UiovPkw1PjAC09gmV9sYmyK"
|
53
|
+
+ "\nwu57oeCVvm6xbHkjO30an0NbaGrRFkR7wzLbVK+r8uTbkKPcm"
|
54
|
+
+ "Mv0UPrG9hsg\nY4g5le8kVoDpdSYLIU7lc5LRDQIDAQAB"; // TEST public key
|
55
|
+
|
56
|
+
public static String encrypt(String text) {
|
57
|
+
if (text == null || text.isEmpty()) return "";
|
58
|
+
|
59
|
+
if (KEY_STRING == null || KEY_STRING.isEmpty()) {
|
60
|
+
throw new SecurityException("Public Key is not set for EncryptUtil");
|
61
|
+
}
|
62
|
+
|
63
|
+
try {
|
64
|
+
return encryptWithKey(text, generateAESKey());
|
65
|
+
} catch (NoSuchAlgorithmException nsae) {
|
66
|
+
Log.e(TAG, "NoSuchAlgorithmException: " + nsae);
|
67
|
+
} catch (InvalidKeyException ike) {
|
68
|
+
Log.e(TAG, "InvalidKeyException: " + ike);
|
69
|
+
} catch (NoSuchPaddingException nspe) {
|
70
|
+
Log.e(TAG, "NoSuchPaddingException: " + nspe);
|
71
|
+
} catch (IllegalBlockSizeException ibse) {
|
72
|
+
Log.e(TAG, "IllegalBlockSizeException: " + ibse);
|
73
|
+
} catch (BadPaddingException bpe) {
|
74
|
+
Log.e(TAG, "BadPaddingException: " + bpe);
|
75
|
+
} catch (NoSuchProviderException nspe) {
|
76
|
+
Log.e(TAG, "NoSuchProviderException: " + nspe);
|
77
|
+
}
|
78
|
+
|
79
|
+
return "";
|
80
|
+
}
|
81
|
+
|
82
|
+
/*
|
83
|
+
* Encrypt provided text with provided secret key. Secret key is used in the
|
84
|
+
* symmetric encryption algorithm.
|
85
|
+
*
|
86
|
+
* Returns format: IV::CIPHER_KEY::CIPHER_TEXT
|
87
|
+
*/
|
88
|
+
private static String encryptWithKey(String text, SecretKey key) throws NoSuchAlgorithmException,
|
89
|
+
NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
|
90
|
+
|
91
|
+
final Cipher symmetricCipher = Cipher.getInstance(SYMMETRIC_ALGO, PROVIDER);
|
92
|
+
symmetricCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key.getEncoded(), SYMMETRIC_ALGO));
|
93
|
+
String cipherText = Base64.encodeToString(symmetricCipher.doFinal(text.getBytes()), Base64.DEFAULT);
|
94
|
+
String iv = Base64.encodeToString(symmetricCipher.getIV(), Base64.DEFAULT);
|
95
|
+
|
96
|
+
final Cipher asymmetricCipher = Cipher.getInstance(ASYMMETRIC_ALGO, PROVIDER);
|
97
|
+
asymmetricCipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
|
98
|
+
String cipherKey = Base64.encodeToString(asymmetricCipher.doFinal(key.getEncoded()), Base64.DEFAULT);
|
99
|
+
|
100
|
+
return iv + FIELD_DELIMITER + cipherKey + FIELD_DELIMITER + cipherText;
|
101
|
+
}
|
102
|
+
|
103
|
+
private static PublicKey getPublicKey() throws NoSuchProviderException {
|
104
|
+
if (PUBLIC_KEY == null) {
|
105
|
+
try {
|
106
|
+
byte[] key = KEY_STRING.getBytes(Charset.forName("UTF-8"));
|
107
|
+
PUBLIC_KEY = KeyFactory.getInstance("RSA", PROVIDER).generatePublic(
|
108
|
+
new X509EncodedKeySpec(Base64.decode(key, Base64.DEFAULT)));
|
109
|
+
} catch (InvalidKeySpecException ikse) {
|
110
|
+
Log.e(TAG, "InvalidKeySpecException: " + ikse);
|
111
|
+
} catch (NoSuchAlgorithmException nsae) {
|
112
|
+
Log.e(TAG, "NoSuchAlgorithmException: " + nsae);
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
return PUBLIC_KEY;
|
117
|
+
}
|
118
|
+
|
119
|
+
private static SecretKey generateAESKey() throws NoSuchAlgorithmException, NoSuchProviderException {
|
120
|
+
KeyGenerator kgen = KeyGenerator.getInstance("AES", PROVIDER);
|
121
|
+
kgen.init(SYMMETRIC_KEY_SIZE);
|
122
|
+
return kgen.generateKey();
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Michael Nipper
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Serket
|
2
|
+
|
3
|
+
A gem for creating encrypted data using RSA and (by default) AES-256-CBC.
|
4
|
+
|
5
|
+
The envisioned use case for this is to encrypt data before saving it to a server or mobile device using a public key, and decrypting that data only when it is sent to another server that has the private key.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'serket'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install serket
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
To encrypt data, you must first tell serket where your public key is:
|
24
|
+
|
25
|
+
```
|
26
|
+
Serket.configure do |config|
|
27
|
+
config.public_key_path = "path/to/public_key.pem"
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
You can then use the FieldEncrypter class to encrypt some text:
|
32
|
+
``
|
33
|
+
Serket::FieldEncrypter.encrypt("Hello out there!")
|
34
|
+
``
|
35
|
+
|
36
|
+
By default, this will return a double-colon (::) delimited string. The first field is the initialization vector used for the symmetric encryption algorithm (by default, this is AES-256-CBC). The second field is the encrypted key for the symmetric algorithm. This key is encrypted using RSA, using the provided public key. The final field is the encrypted text ("Hello out there!" in this example).
|
37
|
+
|
38
|
+
|
39
|
+
To decrypt data, tell serket where to find your private key:
|
40
|
+
```
|
41
|
+
Serket.configure do |config|
|
42
|
+
config.private_key_path = "path/to/private_key.pem"
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
This expects the same format described for encryption, and is the inverse operation.
|
47
|
+
|
48
|
+
Quick Start:
|
49
|
+
|
50
|
+
```
|
51
|
+
Serket.configure do |config|
|
52
|
+
config.public_key_path = "spec/resources/test_public_key.pem"
|
53
|
+
config.private_key_path = "spec/resources/test_private_key.pem"
|
54
|
+
end
|
55
|
+
|
56
|
+
encrypted = Serket::FieldEncrypter.new.encrypt("Hello out there!")
|
57
|
+
puts "#{encrypted} can be decrypted to #{Serket::FieldDecrypter.new.decrypt(encrypted)}"
|
58
|
+
```
|
59
|
+
|
60
|
+
There are a few more configuration options.
|
61
|
+
|
62
|
+
format: :delimited (default), :json
|
63
|
+
symmetric_algorithm: AES-256-CBC (default)
|
64
|
+
delimiter: '::' (default)
|
65
|
+
|
66
|
+
These can all be modified in the configuration block, eg:
|
67
|
+
|
68
|
+
```
|
69
|
+
Serket.configure do |config|
|
70
|
+
config.format = :json
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
Serket.configure do |config|
|
75
|
+
config.delimiter = '**'
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
Note: trying to use a delimiter in the base64 character set throws an exception. This is because the iv/encrypted key/encrypted text are encoded in base64, and so it is a bad idea to use something in base64 as a delimiter.
|
80
|
+
|
81
|
+
There are also some helpers if you are using rails that make encryption/decryption straight forward. Assuming you have a model with a name field that you would like to encrypt before saving to the database, you could do so like this:
|
82
|
+
|
83
|
+
```
|
84
|
+
class EncryptedModel < ActiveRecord::Base
|
85
|
+
extend Serket::EncryptedFields
|
86
|
+
|
87
|
+
encrypted_fields :name
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
If you instead would like to decrypt a field before saving (for example, and encrypted value that is coming from an api), then you could do so like this:
|
92
|
+
|
93
|
+
```
|
94
|
+
class DecryptedModel < ActiveRecord::Base
|
95
|
+
extend Serket::DecryptedFields
|
96
|
+
|
97
|
+
decrypted_fields :name
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
This will automatically decrypt any values before saving assuming it matches your configurations.
|
102
|
+
|
103
|
+
You can see an example java client for use with Android in EncryptUtil.java
|
104
|
+
|
105
|
+
## Contributing
|
106
|
+
|
107
|
+
1. Fork it
|
108
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
109
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
110
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
111
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Serket
|
2
|
+
module DecryptedFields
|
3
|
+
def decrypted_fields(*fields)
|
4
|
+
field_decrypter = FieldDecrypter.new
|
5
|
+
|
6
|
+
fields.each do |field|
|
7
|
+
define_method("#{field}=") do |value|
|
8
|
+
write_attribute(field, field_decrypter.decrypt(value))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Serket
|
2
|
+
module EncryptedFields
|
3
|
+
def encrypted_fields(*fields)
|
4
|
+
field_encrypter = FieldEncrypter.new
|
5
|
+
|
6
|
+
fields.each do |field|
|
7
|
+
define_method("#{field}=") do |value|
|
8
|
+
write_attribute(field, field_encrypter.encrypt(value))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Serket
|
5
|
+
class FieldDecrypter
|
6
|
+
attr_accessor :field_delimiter, :private_key_filepath, :symmetric_algorithm
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
options ||= {}
|
10
|
+
|
11
|
+
@private_key_filepath = Serket.configuration.private_key_path
|
12
|
+
@field_delimiter = options[:field_delimiter] || Serket.configuration.delimiter
|
13
|
+
@symmetric_algorithm = options[:symmetric_algorithm] || Serket.configuration.symmetric_algorithm
|
14
|
+
@format = options[:format] || Serket.configuration.format
|
15
|
+
end
|
16
|
+
|
17
|
+
def decrypt(field)
|
18
|
+
return if field !~ /\S/
|
19
|
+
iv, encrypted_aes_key, encrypted_text = parse(field)
|
20
|
+
private_key = OpenSSL::PKey::RSA.new(File.read(private_key_filepath))
|
21
|
+
decrypted_aes_key = private_key.private_decrypt(Base64.decode64(encrypted_aes_key))
|
22
|
+
decrypt_data(iv, decrypted_aes_key, encrypted_text)
|
23
|
+
end
|
24
|
+
|
25
|
+
def field_delimiter=(delimiter)
|
26
|
+
if delimiter =~ /[A-Za-z0-9\/+]/
|
27
|
+
raise "This is not a valid delimiter! Must not be a character in Base64."
|
28
|
+
end
|
29
|
+
|
30
|
+
@field_delimiter = delimiter
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def decrypt_data(iv, key, encrypted_text)
|
35
|
+
aes = OpenSSL::Cipher.new(symmetric_algorithm)
|
36
|
+
aes.decrypt
|
37
|
+
aes.key = key
|
38
|
+
aes.iv = Base64.decode64(iv)
|
39
|
+
aes.update(Base64.decode64(encrypted_text)) + aes.final
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse(field)
|
43
|
+
case @format
|
44
|
+
when :delimited
|
45
|
+
field.split(field_delimiter)
|
46
|
+
when :json
|
47
|
+
parsed = JSON.parse(field)
|
48
|
+
[parsed['iv'], parsed['key'], parsed['message']]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Serket
|
5
|
+
class FieldEncrypter
|
6
|
+
attr_accessor :field_delimiter, :public_key_filepath, :symmetric_algorithm
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
options ||= {}
|
10
|
+
|
11
|
+
@public_key_filepath = Serket.configuration.public_key_path
|
12
|
+
@field_delimiter = options[:field_delimiter] || Serket.configuration.delimiter
|
13
|
+
@symmetric_algorithm = options[:symmetric_algorithm] || Serket.configuration.symmetric_algorithm
|
14
|
+
@format = options[:format] || Serket.configuration.format
|
15
|
+
end
|
16
|
+
|
17
|
+
def encrypt(field)
|
18
|
+
aes = OpenSSL::Cipher.new(symmetric_algorithm)
|
19
|
+
aes_key = aes.random_key
|
20
|
+
iv = aes.random_iv
|
21
|
+
encrypt_data(iv, aes_key, field)
|
22
|
+
end
|
23
|
+
|
24
|
+
def field_delimiter=(delimiter)
|
25
|
+
if delimiter =~ /[A-Za-z0-9\/+]/
|
26
|
+
raise "This is not a valid delimiter! Must not be a character in Base64."
|
27
|
+
end
|
28
|
+
|
29
|
+
@field_delimiter = delimiter
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def encrypt_data(iv, key, text)
|
34
|
+
public_key = OpenSSL::PKey::RSA.new(File.read(public_key_filepath))
|
35
|
+
encrypted_aes_key = public_key.public_encrypt(key)
|
36
|
+
|
37
|
+
aes = OpenSSL::Cipher.new(symmetric_algorithm)
|
38
|
+
aes.encrypt
|
39
|
+
aes.key = key
|
40
|
+
aes.iv = iv
|
41
|
+
encrypted_text = aes.update(text) + aes.final
|
42
|
+
|
43
|
+
parse(Base64.encode64(iv), Base64.encode64(encrypted_aes_key), Base64.encode64(encrypted_text))
|
44
|
+
end
|
45
|
+
|
46
|
+
def parse(iv, encrypted_key, encrypted_text)
|
47
|
+
case @format
|
48
|
+
when :delimited
|
49
|
+
[iv, field_delimiter, encrypted_key, field_delimiter, encrypted_text].join('')
|
50
|
+
when :json
|
51
|
+
hash = {}
|
52
|
+
hash['iv'] = iv
|
53
|
+
hash['key'] = encrypted_key
|
54
|
+
hash['message'] = encrypted_text
|
55
|
+
hash.to_json
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/serket.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "./serket/version"
|
2
|
+
require_relative "./serket/field_decrypter.rb"
|
3
|
+
require_relative "./serket/field_encrypter.rb"
|
4
|
+
require_relative "./serket/decrypted_fields.rb"
|
5
|
+
require_relative "./serket/encrypted_fields.rb"
|
6
|
+
require_relative "./serket/configuration.rb"
|
7
|
+
|
8
|
+
module Serket
|
9
|
+
class << self
|
10
|
+
attr_writer :configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.configure
|
18
|
+
yield(configuration)
|
19
|
+
end
|
20
|
+
end
|
data/serket.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'serket/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "serket"
|
8
|
+
spec.version = Serket::VERSION
|
9
|
+
spec.authors = ["Michael Nipper"]
|
10
|
+
spec.email = ["mjn4406@gmail.com"]
|
11
|
+
spec.description = %q{Automatically encrypt and decrypt fields.}
|
12
|
+
spec.summary = %q{Automatically encrypt and decrypt fields using RSA and AES-256-CBC.}
|
13
|
+
spec.homepage = "https://github.com/mnipper/serket"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpAIBAAKCAQEAr19m7jjA32IrF9ios9MWth119faF4Xc7clCtWWi9ncmMSBq0
|
3
|
+
uQsASkpf/JVdVi91TZbzdzhInhhZomMh+26r8oiAibRHRcxGRyCIUhEUZQuR8Qee
|
4
|
+
aO5wugpg1zOYuYrevDH+MxuYebeAcqccW91ZQBDxsUKYjSiVIhm/Cmas/J/g9m+k
|
5
|
+
+HKUGHREVzNZSRIToxuVc2DHVylWT0NPN14OUnOt4PHJZED/QwiiNFWo/UiovPkw
|
6
|
+
1PjAC09gmV9sYmyKwu57oeCVvm6xbHkjO30an0NbaGrRFkR7wzLbVK+r8uTbkKPc
|
7
|
+
mMv0UPrG9hsgY4g5le8kVoDpdSYLIU7lc5LRDQIDAQABAoIBAG31n5BWvXhTEToO
|
8
|
+
exjljiP6LPBf9mn8XKW8uDSLW/kHWpILTK2JnFD4eV7iOHfFogNYVqe1/rJCClGr
|
9
|
+
Xq9MITwdIps1EktNXfNTDqaGVwdUTdmXMVgRyVSdFUNZ8rTDwgy2O/DHqL8Is90v
|
10
|
+
srRXAZMODL1cSFKZ04hiJErdPjHW8138/0H1bfUx/roMSdNXC50NrNXmrSaHGOfD
|
11
|
+
nV1O3ybe9mfd1iyOqHzRjL6O56N4sh3okrF/woSAAhNDaQ2byRNcbQdlX60Ss/m8
|
12
|
+
AHZH7a0Mr+2g5QJ8f9NWwoeUsQn5pSVF7hTOAL7y1WWTkqOhsXRNB/09/23idQqY
|
13
|
+
Bh9iVXECgYEA2mhCrm67NdTZybqLiOPRab+PwsQ1ursrkRnKFjhykdSIeXCbwCg6
|
14
|
+
NSbaquJRNAmLaf1Rmf+tx6GuxMTb7GQJU+XmKkjWKQ8YlFWgfcw5Qr4ROe2ES7wy
|
15
|
+
JVn3PeYEvBLVLavhlJQqDSadTCNWk+0NlMCsJvPU+2eKz6p+h8Nvb48CgYEAzY7l
|
16
|
+
YsuspzzM93v3js8n5mSsCEoFrPXGgIMrfbfydgB8GtqEcuEKH0nxwBkA2s8a7uom
|
17
|
+
oEPL4TxVIsZZm6XxPWXOrRQMTJ94/qJ4sRUz63xBn+7fINRkm6OUJJtnRrQpM9f7
|
18
|
+
Gix6fsu/aXoFz+Ai8djvLtydvxSMFnoL/oxgJ6MCgYBviAx9PSi27bIl1PBKgGRB
|
19
|
+
R0SnpkD10X0HBQ9w13SSSeFDeqDeuOw4llXWK+ph03nxLx5LsQhSSJuR+iGAjvlA
|
20
|
+
ccde0oEiyIW5whxKAU9AaQUs+sCzWDCXaGDcqCjEzi00vHBeymbK/mwXJHii48wq
|
21
|
+
qVWAMsYReCensp4YwFGYuwKBgQC7IMPnzXyufiYlgkAaTLGJBsqpfSFvlAqSAVc9
|
22
|
+
SpC6JbTVCWqb3gvF8h3W/6wMhY+CQbzKFw3qTG9AigsXK9jSSPT+EQslUePnAucW
|
23
|
+
ZjPuwx5Gx0Fu9ItmOGLrdGFvNyFvJcZczHLzLO5iygeydtu5CQCsy3/7bGwfJhn3
|
24
|
+
L7l1OwKBgQC0HtX6Z8oDLVRS9dykvpFY9l5ZAcgmn9l9JuG2QfaemMfEdM+LwX3m
|
25
|
+
UA1XEKED1rE37J1oE3RcB1hVKg46n+ncy9NEKxysBoKQJA7Ineti6DnjRR1eSNjJ
|
26
|
+
LVCabSkE+G4Nwf4rE0z/qsN+BfUcz9cwRJVDTN6mnOfH+u6cCEXqdw==
|
27
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,9 @@
|
|
1
|
+
-----BEGIN PUBLIC KEY-----
|
2
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr19m7jjA32IrF9ios9MW
|
3
|
+
th119faF4Xc7clCtWWi9ncmMSBq0uQsASkpf/JVdVi91TZbzdzhInhhZomMh+26r
|
4
|
+
8oiAibRHRcxGRyCIUhEUZQuR8QeeaO5wugpg1zOYuYrevDH+MxuYebeAcqccW91Z
|
5
|
+
QBDxsUKYjSiVIhm/Cmas/J/g9m+k+HKUGHREVzNZSRIToxuVc2DHVylWT0NPN14O
|
6
|
+
UnOt4PHJZED/QwiiNFWo/UiovPkw1PjAC09gmV9sYmyKwu57oeCVvm6xbHkjO30a
|
7
|
+
n0NbaGrRFkR7wzLbVK+r8uTbkKPcmMv0UPrG9hsgY4g5le8kVoDpdSYLIU7lc5LR
|
8
|
+
DQIDAQAB
|
9
|
+
-----END PUBLIC KEY-----
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Serket::DecryptedFields do
|
4
|
+
describe 'field decryption' do
|
5
|
+
before :each do
|
6
|
+
@encrypted_model = Serket::EncryptedModel.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should decrypt an encrypted name" do
|
10
|
+
encrypted_name = 'bm5gkjwdfv6QBUqFPEnOaw==::CtQJyXdbuLeVzmIOIr0h/F9yh7xvz5KWUgxe/u4IkORGOW4KjU4bw+Wzve2vV1nLYUEWJJprr8sb+grm+Ao2sngNejiHzSkJKqZA/Pclw/Ok8KgHgN7olUz4BoCSdivIDRIT9ar06sNBrqOvLd4iGUlpMkpLdSJ69K08ebSvg5tED+PcK/oI6SJoVxRoUMYdYa9AfeIS9Ld5BgvhsaJgCKr089kfH2CzwpzlmRfdxb2qgyDXnk9PG/4WUEjjbamF/R74FNBdWkTLxZeLGdMImh87CQ6AOJ/v8l1JSzpPWwEjtmhTbFEzJPuA01tP5U5D07si0esJnab/B48iACEoLg==::iSmdDgnTzkEUv0yLbtFa8Q=='
|
11
|
+
@encrypted_model.name = encrypted_name
|
12
|
+
@encrypted_model.name.should == 'Kemba Walker'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Serket::EncryptedFields do
|
4
|
+
describe 'field encryption' do
|
5
|
+
before :each do
|
6
|
+
@encrypted_model = Serket::EncryptedModel.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should encrypt a plaintext field" do
|
10
|
+
field_decrypter = Serket::FieldDecrypter.new
|
11
|
+
@encrypted_model.email = 'kemba.walker@aol.com'
|
12
|
+
decrypted = field_decrypter.decrypt(@encrypted_model.email)
|
13
|
+
decrypted.should == 'kemba.walker@aol.com'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Serket::FieldDecrypter do
|
4
|
+
before :all do
|
5
|
+
@encrypted_message = 'RC78JOorP4EKyuh3Bh9atg==::iiaRmPaoFbke5d81FkL5SgQYudtnV2rl370QVlJel0f2+IniSugQ359gvsTqT2YPg1JcZ+9eky9GLGoDdAX90jsqYBG3CnawS6FgHNfO2HxBxTcgTWgfRmZv1lLo4JJ423lVXMdQv2mxgBTTMMO1ZNJsHtDjOoKlTGPh1lL3DARRrkz67J8LKN/zGDMIQXr9kfag/qkCJgJB36Owsj+9qIKKrzxtznsp/t8/Q2JGaKJmvh0srKr35hXJ6+cucI/+2uhEE78oKadUTyxxzgKrrmhKkKW2TvO4BPRsA7kpU6/X44UYYxQ2eqiMrhvklbzZKgtOuk84LZg4gXi9zCc80g==::gPYO7iDShmrk+RzO5ydjMfSUizmVEA6dAdrpsLeZvqI='
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
@field_decrypter = Serket::FieldDecrypter.new
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should correctly decrypt a message" do
|
14
|
+
@field_decrypter.decrypt(@encrypted_message).should == "Hello out there!"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return nil for a blank string" do
|
18
|
+
@field_decrypter.decrypt(' ').should be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should allow a change in field delimiter" do
|
22
|
+
@field_decrypter.field_delimiter = '**'
|
23
|
+
@field_decrypter.field_delimiter.should == '**'
|
24
|
+
asterik_delimited = @encrypted_message.gsub(/:/, '*')
|
25
|
+
@field_decrypter.decrypt(asterik_delimited).should == "Hello out there!"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should not allow a base64 character as a delimiter" do
|
29
|
+
[*('a'..'z'), *('A'..'Z'), *('0'..'9'), '+', '/'].each do |char|
|
30
|
+
expect { @field_decrypter.field_delimiter = char }.to raise_error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "json parsing" do
|
35
|
+
before :each do
|
36
|
+
@field_decrypter_json = Serket::FieldDecrypter.new(format: :json)
|
37
|
+
iv, key, message = @encrypted_message.split(@field_decrypter.field_delimiter)
|
38
|
+
@message = {}
|
39
|
+
@message['iv'] = iv
|
40
|
+
@message['key'] = key
|
41
|
+
@message['message'] = message
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should parse a json field" do
|
45
|
+
iv, key, m = @field_decrypter_json.send(:parse, @message.to_json)
|
46
|
+
iv.should == @message['iv']
|
47
|
+
key.should == @message['key']
|
48
|
+
m.should == @message['message']
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should decrypt a json value" do
|
52
|
+
@field_decrypter_json.decrypt(@message.to_json).should == "Hello out there!"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Serket::FieldEncrypter do
|
4
|
+
before :each do
|
5
|
+
@field_encrypter = Serket::FieldEncrypter.new
|
6
|
+
@field_decrypter = Serket::FieldDecrypter.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should correctly encrypt/decrypt a message" do
|
10
|
+
encrypted_text = @field_encrypter.encrypt("Hello out there!")
|
11
|
+
@field_decrypter.decrypt(encrypted_text).should == "Hello out there!"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should not allow a base64 character as a delimiter" do
|
15
|
+
[*('a'..'z'), *('A'..'Z'), *('0'..'9'), '+', '/'].each do |char|
|
16
|
+
expect { @field_encrypter.field_delimiter = char }.to raise_error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "json parsing" do
|
21
|
+
before :each do
|
22
|
+
@field_encrypter_json = Serket::FieldEncrypter.new(format: :json)
|
23
|
+
@encrypted_json = @field_encrypter_json.encrypt("Hello out there!")
|
24
|
+
@parsed_json = JSON.parse(@encrypted_json)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have the correct json fields" do
|
28
|
+
@parsed_json.has_key?('iv').should be_true
|
29
|
+
@parsed_json.has_key?('key').should be_true
|
30
|
+
@parsed_json.has_key?('message').should be_true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Serket::EncryptedModel
|
4
|
+
extend Serket::DecryptedFields
|
5
|
+
extend Serket::EncryptedFields
|
6
|
+
|
7
|
+
attr_accessor :name, :email
|
8
|
+
|
9
|
+
decrypted_fields :name
|
10
|
+
encrypted_fields :email
|
11
|
+
|
12
|
+
def write_attribute(field, value)
|
13
|
+
self.instance_variable_set("@#{field}", value)
|
14
|
+
end
|
15
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rspec"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
$:.unshift((Pathname(__FILE__).dirname.parent + "lib").to_s)
|
5
|
+
|
6
|
+
require "serket"
|
7
|
+
|
8
|
+
Serket.configure do |config|
|
9
|
+
config.private_key_path = "spec/resources/test_private_key.pem"
|
10
|
+
config.public_key_path = "spec/resources/test_public_key.pem"
|
11
|
+
end
|
12
|
+
|
13
|
+
require "serket/models/encrypted_model.rb"
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: serket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Nipper
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-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: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
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: rspec
|
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
|
+
description: Automatically encrypt and decrypt fields.
|
56
|
+
email:
|
57
|
+
- mjn4406@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- EncryptUtil.java
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- lib/serket.rb
|
69
|
+
- lib/serket/configuration.rb
|
70
|
+
- lib/serket/decrypted_fields.rb
|
71
|
+
- lib/serket/encrypted_fields.rb
|
72
|
+
- lib/serket/field_decrypter.rb
|
73
|
+
- lib/serket/field_encrypter.rb
|
74
|
+
- lib/serket/version.rb
|
75
|
+
- serket.gemspec
|
76
|
+
- spec/resources/test_private_key.pem
|
77
|
+
- spec/resources/test_public_key.pem
|
78
|
+
- spec/serket/decrypted_fields_spec.rb
|
79
|
+
- spec/serket/encrypted_fields_spec.rb
|
80
|
+
- spec/serket/field_decrypter_spec.rb
|
81
|
+
- spec/serket/field_encrypter_spec.rb
|
82
|
+
- spec/serket/models/encrypted_model.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
homepage: https://github.com/mnipper/serket
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.4.1
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Automatically encrypt and decrypt fields using RSA and AES-256-CBC.
|
108
|
+
test_files:
|
109
|
+
- spec/resources/test_private_key.pem
|
110
|
+
- spec/resources/test_public_key.pem
|
111
|
+
- spec/serket/decrypted_fields_spec.rb
|
112
|
+
- spec/serket/encrypted_fields_spec.rb
|
113
|
+
- spec/serket/field_decrypter_spec.rb
|
114
|
+
- spec/serket/field_encrypter_spec.rb
|
115
|
+
- spec/serket/models/encrypted_model.rb
|
116
|
+
- spec/spec_helper.rb
|