crypton 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/README.md +30 -0
- data/crypton.gemspec +22 -0
- data/lib/crypton.rb +1 -0
- data/lib/crypton/encrypted_attributes.rb +26 -0
- data/lib/crypton/errors.rb +3 -0
- data/lib/crypton/symmetric.rb +68 -0
- data/lib/crypton/version.rb +3 -0
- data/spec/crypton/encrypted_attributes_spec.rb +40 -0
- data/spec/crypton/symmetric_spec.rb +36 -0
- data/spec/spec_helper.rb +3 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7d7a624e5d492cfe0734cdef077413c3be95976f
|
4
|
+
data.tar.gz: f1c76cefb9da0012925257cc169f6fe2cd5ef592
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8f07fb5c09f7c61459e80e0c62ca8aaed6aa99767cbd286bb00dc7975dd3bb603a079549129f0c6bc26312bcdcee20e605c1879bdbd022991f7e13b1b81511a0
|
7
|
+
data.tar.gz: 11287884aa9e5b08310e9d1c53a9b2f241199841398d2aca16239287440f2aba9448f1942f40747db87d04c1b224bf6794f2ea990c1f6e4fcc55d7fae8dd4da8
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Crypton
|
2
|
+
|
3
|
+
Simple crypto.
|
4
|
+
|
5
|
+
Very alpha. APIs will change.
|
6
|
+
|
7
|
+
## Symmetric encryption
|
8
|
+
|
9
|
+
Uses 256-bit AES in GCM mode. A key of suitable size is derived from the password provided.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
encrypted_message = Crypton::Symmetric.encrypt("some secret text", "p4ssw0rd")
|
13
|
+
Crypton::Symmetric.decrypt(encrypted_message, "p4ssw0rd") == "some secret text"
|
14
|
+
```
|
15
|
+
|
16
|
+
## Encrypted Attributes
|
17
|
+
|
18
|
+
A helper mixing for transparently encrypting attributes on models.
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
class User
|
22
|
+
include Crypton::EncryptedAttributes
|
23
|
+
attr_accessor :encrypted_name
|
24
|
+
encrypted_attribute :name, password: ENV["NAME_ENCRYPTION_KEY"]
|
25
|
+
end
|
26
|
+
|
27
|
+
account = BankAccount.new
|
28
|
+
account.name = "Ben Hur" # encrypts "Ben Hur" and stores on encrypted_name
|
29
|
+
assert account.name == "Ben Hur" # decrypts encrypted_name to retrieve the value
|
30
|
+
```
|
data/crypton.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'crypton/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "crypton"
|
8
|
+
spec.version = Crypton::VERSION
|
9
|
+
spec.authors = ["Harry Marr"]
|
10
|
+
spec.email = ["engineering@gocardless.com"]
|
11
|
+
spec.description = %q{Simple crypto}
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = "https://github.com/gocardless/crypton"
|
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 "rspec", "~> 3.1"
|
22
|
+
end
|
data/lib/crypton.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'crypton/symmetric'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "crypton/symmetric"
|
2
|
+
|
3
|
+
module Crypton
|
4
|
+
module EncryptedAttributes
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def encrypted_attribute(name, password: nil)
|
11
|
+
raise AttributeError, "password must be specified" unless password
|
12
|
+
to_field = "encrypted_#{name}"
|
13
|
+
|
14
|
+
define_method("#{name}=") do |value|
|
15
|
+
send("#{to_field}=", Crypton::Symmetric.encrypt(value, password))
|
16
|
+
end
|
17
|
+
|
18
|
+
define_method(name) do
|
19
|
+
encrypted_value = send(to_field)
|
20
|
+
Crypton::Symmetric.decrypt(encrypted_value, password)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "openssl"
|
2
|
+
require "base64"
|
3
|
+
require "json"
|
4
|
+
require "crypton/errors"
|
5
|
+
|
6
|
+
module Crypton
|
7
|
+
module Symmetric
|
8
|
+
KEY_SIZE = 256
|
9
|
+
ALGORITHM = "aes-#{KEY_SIZE}-gcm"
|
10
|
+
KEY_DERIVATION_ROUNDS = 10000
|
11
|
+
SCHEME_NAME = "crypton.symmetric"
|
12
|
+
|
13
|
+
def self.encrypt(data, password)
|
14
|
+
salt = OpenSSL::Random.random_bytes(16)
|
15
|
+
|
16
|
+
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
17
|
+
cipher.encrypt
|
18
|
+
cipher.key = derive_key(password, salt, KEY_DERIVATION_ROUNDS, KEY_SIZE)
|
19
|
+
iv = Base64.strict_encode64(cipher.random_iv)
|
20
|
+
cipher.auth_data = "" if cipher.authenticated?
|
21
|
+
|
22
|
+
cipher_text = cipher.update(data) + cipher.final
|
23
|
+
|
24
|
+
message = {
|
25
|
+
scheme: SCHEME_NAME,
|
26
|
+
algo: ALGORITHM,
|
27
|
+
key_size: KEY_SIZE,
|
28
|
+
kd_rounds: KEY_DERIVATION_ROUNDS,
|
29
|
+
salt: Base64.strict_encode64(salt),
|
30
|
+
iv: iv,
|
31
|
+
cipher_text: Base64.strict_encode64(cipher_text),
|
32
|
+
}
|
33
|
+
if cipher.authenticated?
|
34
|
+
message[:auth_tag] = Base64.strict_encode64(cipher.auth_tag)
|
35
|
+
end
|
36
|
+
|
37
|
+
JSON.generate(message)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.decrypt(encrypted_data, password)
|
41
|
+
message = JSON.parse(encrypted_data, symbolize_names: true)
|
42
|
+
key = derive_key(password, Base64.strict_decode64(message[:salt]),
|
43
|
+
message[:kd_rounds], message[:key_size])
|
44
|
+
|
45
|
+
decipher = OpenSSL::Cipher.new(message[:algo])
|
46
|
+
decipher.decrypt
|
47
|
+
decipher.key = key
|
48
|
+
decipher.iv = Base64.strict_decode64(message[:iv])
|
49
|
+
|
50
|
+
if decipher.authenticated?
|
51
|
+
decipher.auth_tag = Base64.strict_decode64(message[:auth_tag])
|
52
|
+
decipher.auth_data = ""
|
53
|
+
end
|
54
|
+
|
55
|
+
cipher_text = Base64.strict_decode64(message[:cipher_text])
|
56
|
+
decipher.update(cipher_text) + decipher.final
|
57
|
+
rescue OpenSSL::Cipher::CipherError => err
|
58
|
+
raise DecryptionError, "Error decrypting message: #{err.message}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.derive_key(password, salt, rounds, size)
|
62
|
+
# Size is provided in bits, then gets converted to bytes for OpenSSL
|
63
|
+
OpenSSL::PKCS5.pbkdf2_hmac_sha1(password, salt, rounds, size/8)
|
64
|
+
end
|
65
|
+
|
66
|
+
private_class_method :derive_key
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "crypton/encrypted_attributes"
|
3
|
+
|
4
|
+
RSpec.describe Crypton::EncryptedAttributes do
|
5
|
+
let(:model) do
|
6
|
+
Class.new do
|
7
|
+
include Crypton::EncryptedAttributes
|
8
|
+
attr_accessor :encrypted_foo
|
9
|
+
encrypted_attribute :foo, password: "bar"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "defines a reader method" do
|
14
|
+
expect(model.new).to respond_to(:foo)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "defines a writer method" do
|
18
|
+
expect(model.new).to respond_to(:foo=)
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:instance) { model.new }
|
22
|
+
|
23
|
+
describe "reader method" do
|
24
|
+
before { allow(Crypton::Symmetric).to receive(:encrypt) { "{...}" } }
|
25
|
+
|
26
|
+
it "encrypts and stores the value" do
|
27
|
+
instance.foo = "baz"
|
28
|
+
expect(instance.encrypted_foo).to eq("{...}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "writer method" do
|
33
|
+
before { allow(Crypton::Symmetric).to receive(:decrypt) { "plaintext" } }
|
34
|
+
|
35
|
+
it "encrypts and stores the value" do
|
36
|
+
instance.encrypted_foo = "{...}"
|
37
|
+
expect(instance.foo).to eq("plaintext")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "crypton/symmetric"
|
3
|
+
|
4
|
+
RSpec.describe Crypton::Symmetric do
|
5
|
+
let(:message) { Crypton::Symmetric.encrypt("test", "s3cr3t") }
|
6
|
+
|
7
|
+
it "works end-to-end" do
|
8
|
+
expect(Crypton::Symmetric.decrypt(message, "s3cr3t")).to eq("test")
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ".encrypt" do
|
12
|
+
it "generates a JSON-encoded message" do
|
13
|
+
expect { JSON.parse(message) }.to_not raise_error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ".decrypt" do
|
18
|
+
context "given an invalid password" do
|
19
|
+
it "fails" do
|
20
|
+
expect { Crypton::Symmetric.decrypt(message, "wrong password") }.
|
21
|
+
to raise_error(Crypton::DecryptionError)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "given an invalid auth tag" do
|
26
|
+
let(:tampered_message) do
|
27
|
+
JSON.generate(JSON.parse(message).merge("auth_tag" => "0000"))
|
28
|
+
end
|
29
|
+
|
30
|
+
it "fails" do
|
31
|
+
expect { Crypton::Symmetric.decrypt(tampered_message, "s3cr3t") }.
|
32
|
+
to raise_error(Crypton::DecryptionError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: crypton
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Harry Marr
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.1'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.1'
|
27
|
+
description: Simple crypto
|
28
|
+
email:
|
29
|
+
- engineering@gocardless.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- .gitignore
|
35
|
+
- Gemfile
|
36
|
+
- README.md
|
37
|
+
- crypton.gemspec
|
38
|
+
- lib/crypton.rb
|
39
|
+
- lib/crypton/encrypted_attributes.rb
|
40
|
+
- lib/crypton/errors.rb
|
41
|
+
- lib/crypton/symmetric.rb
|
42
|
+
- lib/crypton/version.rb
|
43
|
+
- spec/crypton/encrypted_attributes_spec.rb
|
44
|
+
- spec/crypton/symmetric_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
homepage: https://github.com/gocardless/crypton
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - '>'
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.3.1
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 2.4.2
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: Simple crypto
|
70
|
+
test_files:
|
71
|
+
- spec/crypton/encrypted_attributes_spec.rb
|
72
|
+
- spec/crypton/symmetric_spec.rb
|
73
|
+
- spec/spec_helper.rb
|