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.
- checksums.yaml +7 -0
- data/.gems +1 -0
- data/LICENSE +19 -0
- data/README.md +54 -0
- data/krypter.gemspec +14 -0
- data/lib/krypter.rb +96 -0
- data/makefile +4 -0
- data/test/krypter.rb +73 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
```
|
data/krypter.gemspec
ADDED
@@ -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
|
data/lib/krypter.rb
ADDED
@@ -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
|
data/makefile
ADDED
data/test/krypter.rb
ADDED
@@ -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: []
|