crypton 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1 @@
1
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -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
+ ```
@@ -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
@@ -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,3 @@
1
+ module Crypton
2
+ class DecryptionError < StandardError; end
3
+ end
@@ -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,3 @@
1
+ module Crypton
2
+ VERSION = "0.1.0.pre1".freeze
3
+ 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
@@ -0,0 +1,3 @@
1
+ RSpec.configure do |config|
2
+ config.disable_monkey_patching!
3
+ end
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