krypter 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7e0dfa5c8ff00e53057804420023fe37efb06f4b
4
+ data.tar.gz: b7daec95060bf484e2203fce88174f383375a0b6
5
+ SHA512:
6
+ metadata.gz: 8260c08cb857ee43e15a2652bfb3a17141cae99c6e9345a9772eb7b5e2dcf2d7f0e5fbf339a97674a901e7c9b9eb9a1a9a672bf9197c8556922d5128f9066d8a
7
+ data.tar.gz: f0a2c5226ea6b3add326e8760398d80f07c8a2073208aa4f60ff0d90acb91148cfecbd6c1c3feae3526f3232ca4754ef050ebb226a4190a67f9cb5ebad34f9d3
data/.gems ADDED
@@ -0,0 +1 @@
1
+ cutest -v 1.2.1
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Francesco Rodríguez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,54 @@
1
+ krypter
2
+ =======
3
+
4
+ Encrypts messages with authentication.
5
+
6
+ Usage
7
+ -----
8
+
9
+ Pass a secret token. This must be at least 32 bytes
10
+ long and should be really random. You can generate
11
+ a random secret with `SecureRandom.hex(32)`.
12
+
13
+ ```ruby
14
+ require "securerandom"
15
+ require "krypter"
16
+
17
+ secret = SecureRandom.hex(32)
18
+ encryptor = Krypter.new(secret)
19
+ encrypted = encryptor.encrypt("message")
20
+ encryptor.decrypt(encrypted) == "message"
21
+ # => true
22
+
23
+ # If the signature is invalid, it raises a `InvalidSignature` error.
24
+ encryptor.decrypt("")
25
+ # => Krypter::InvalidSignature
26
+
27
+ # If the message is changed, it raises a `InvalidMessage` error.
28
+ ciphertext, signature = encrypted.split("--")
29
+ ciphertext.reverse!
30
+
31
+ encryptor.decrypt([ciphertext, signature].join("--"))
32
+ # => Krypter::InvalidMessage
33
+ ```
34
+
35
+ By default, the messages are encrypted with 256-bit AES in CBC mode
36
+ (with random IV). The encrypted message is then signed with HMAC-SHA256,
37
+ to prevent tampering and chosen ciphertext attacks.
38
+
39
+ The defaults can be changed when instantiating the encryptor object.
40
+
41
+ ```ruby
42
+ encryptor = Encryptor.new(secret,
43
+ cipher: "aes-256-cbc",
44
+ digest: "SHA256",
45
+ separator: "--"
46
+ )
47
+ ```
48
+
49
+ Installation
50
+ ------------
51
+
52
+ ```
53
+ $ gem install krypter
54
+ ```
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "krypter"
3
+ s.version = "1.0.0"
4
+ s.summary = "Encrypts and signs messages."
5
+ s.description = s.summary
6
+ s.authors = ["Francesco Rodríguez", "Mayn Kjær"]
7
+ s.email = ["frodsan@me.com", "mayn.kjaer@gmail.com"]
8
+ s.homepage = "https://github.com/harmoni-io/krypter"
9
+ s.license = "MIT"
10
+
11
+ s.files = `git ls-files`.split("\n")
12
+
13
+ s.add_development_dependency "cutest"
14
+ end
@@ -0,0 +1,96 @@
1
+ require "base64"
2
+ require "openssl"
3
+
4
+ class Krypter
5
+ InvalidSignature = Class.new(StandardError)
6
+ InvalidMessage = Class.new(StandardError)
7
+
8
+ def initialize(secret, cipher: "aes-256-cbc", digest: "SHA256", separator: "--")
9
+ @cipher = cipher
10
+ @digest = digest
11
+ @separator = separator
12
+ @encrypt_secret = hmac(secret, "encryption key")
13
+ @sign_secret = hmac(secret, "signin key")
14
+ end
15
+
16
+ def encrypt(message)
17
+ return sign(_encrypt(message))
18
+ end
19
+
20
+ def decrypt(message)
21
+ ciphertext = verify(message)
22
+
23
+ if ciphertext
24
+ return _decrypt(ciphertext)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def hmac(secret, message)
31
+ return OpenSSL::HMAC.hexdigest(@digest, secret, message)
32
+ end
33
+
34
+ def _encrypt(message)
35
+ cipher = OpenSSL::Cipher.new(@cipher)
36
+ cipher.encrypt
37
+ cipher.key = @encrypt_secret
38
+
39
+ iv = cipher.random_iv
40
+ ciphertext = cipher.update(message)
41
+ ciphertext << cipher.final
42
+
43
+ return [ciphertext, iv].join(@separator)
44
+ end
45
+
46
+ def _decrypt(encrypted)
47
+ ciphertext, iv = encrypted.split(@separator)
48
+
49
+ decipher = OpenSSL::Cipher.new(@cipher)
50
+ decipher.decrypt
51
+ decipher.key = @encrypt_secret
52
+ decipher.iv = iv
53
+
54
+ decrypted = decipher.update(ciphertext)
55
+ decrypted << decipher.final
56
+
57
+ return decrypted
58
+ rescue OpenSSL::Cipher::CipherError
59
+ raise InvalidMessage
60
+ end
61
+
62
+ def sign(value)
63
+ encoded = Base64.strict_encode64(value)
64
+ signature = authenticate(encoded)
65
+
66
+ return [encoded, signature].join(@separator)
67
+ end
68
+
69
+ def authenticate(message)
70
+ return hmac(@sign_secret, message)
71
+ end
72
+
73
+ def verify(message)
74
+ value, signature = message.split(@separator)
75
+
76
+ if value && signature && secure_compare(signature, authenticate(value))
77
+ return Base64.strict_decode64(value)
78
+ else
79
+ raise InvalidSignature
80
+ end
81
+ end
82
+
83
+ # Prevents timing attacks: http://codahale.com/a-lesson-in-timing-attacks/.
84
+ def secure_compare(a, b)
85
+ return false unless a.bytesize == b.bytesize
86
+
87
+ cmp = b.bytes
88
+ result = 0
89
+
90
+ a.bytes.each_with_index do |char, index|
91
+ result |= char ^ cmp[index]
92
+ end
93
+
94
+ return result == 0
95
+ end
96
+ end
@@ -0,0 +1,4 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ cutest test/*.rb
@@ -0,0 +1,73 @@
1
+ require "cutest"
2
+ require "securerandom"
3
+ require_relative "../lib/krypter"
4
+
5
+ setup do
6
+ Krypter.new(SecureRandom.hex(32))
7
+ end
8
+
9
+ test "encrypts and decrypts" do |encryptor|
10
+ encrypted = encryptor.encrypt("message")
11
+ decrypted = encryptor.decrypt(encrypted)
12
+
13
+ assert_equal("message", decrypted)
14
+ end
15
+
16
+ test "encrypt returns different ciphertexts" do |encryptor|
17
+ encrypted1 = encryptor.encrypt("message")
18
+ encrypted2 = encryptor.encrypt("message")
19
+
20
+ assert encrypted1 != encrypted2
21
+ end
22
+
23
+ test "wrong signature" do |encryptor|
24
+ encrypted = encryptor.encrypt("message")
25
+ separator = encryptor.instance_variable_get(:@separator)
26
+ ciphertext, signature = encrypted.split(separator)
27
+
28
+ message = [signature, ciphertext] * separator
29
+ assert_raise(Krypter::InvalidSignature) do
30
+ encryptor.decrypt(message)
31
+ end
32
+
33
+ message = [ciphertext, signature.reverse] * separator
34
+ assert_raise(Krypter::InvalidSignature) do
35
+ encryptor.decrypt(message)
36
+ end
37
+
38
+ message = [ciphertext.reverse, signature] * separator
39
+ assert_raise(Krypter::InvalidSignature) do
40
+ encryptor.decrypt(message)
41
+ end
42
+
43
+ message = [ciphertext.reverse, signature.reverse] * separator
44
+ assert_raise(Krypter::InvalidSignature) do
45
+ encryptor.decrypt(message)
46
+ end
47
+ end
48
+
49
+ test "tampered data" do |encryptor|
50
+ encrypted = encryptor.encrypt("message")
51
+ separator = encryptor.instance_variable_get(:@separator)
52
+ ciphertext, iv = encryptor.send(:verify, encrypted).split(separator)
53
+
54
+ message = encryptor.send(:sign, [iv, ciphertext] * separator)
55
+ assert_raise(Krypter::InvalidMessage) do
56
+ encryptor.decrypt(message)
57
+ end
58
+
59
+ message = encryptor.send(:sign, [ciphertext, iv.reverse] * separator)
60
+ assert_raise(Krypter::InvalidMessage) do
61
+ encryptor.decrypt(message)
62
+ end
63
+
64
+ message = encryptor.send(:sign, [ciphertext.reverse, iv] * separator)
65
+ assert_raise(Krypter::InvalidMessage) do
66
+ encryptor.decrypt(message)
67
+ end
68
+
69
+ message = encryptor.send(:sign, [ciphertext.reverse, iv.reverse] * separator)
70
+ assert_raise(Krypter::InvalidMessage) do
71
+ encryptor.decrypt(message)
72
+ end
73
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: krypter
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Francesco Rodríguez
8
+ - Mayn Kjær
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-08-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cutest
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ description: Encrypts and signs messages.
29
+ email:
30
+ - frodsan@me.com
31
+ - mayn.kjaer@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".gems"
37
+ - LICENSE
38
+ - README.md
39
+ - krypter.gemspec
40
+ - lib/krypter.rb
41
+ - makefile
42
+ - test/krypter.rb
43
+ homepage: https://github.com/harmoni-io/krypter
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project:
63
+ rubygems_version: 2.2.2
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Encrypts and signs messages.
67
+ test_files: []