ecies 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +7 -0
- data/.travis.yml +10 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +19 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +105 -0
- data/Rakefile +25 -0
- data/certs/jamoes.pem +21 -0
- data/ecies.gemspec +28 -0
- data/lib/ecies.rb +8 -0
- data/lib/ecies/crypt.rb +149 -0
- data/lib/ecies/version.rb +4 -0
- data/spec/crypt_spec.rb +106 -0
- data/spec/spec_helper.rb +6 -0
- metadata +181 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 33b1046a11dbb7f36abc039da9e124a16f3db9751cfedbc869bde97b01b08091
|
4
|
+
data.tar.gz: 2138b78219252211b76b8f63db73489c94ec7be3cef8c922c689d1cb5ca29a94
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 233a031a01b0a1db77727ce486b9a419361b69b0a894c2e333c4769772abfc4850d2715db01bccffd8c714b5ae99266e45c9af880313d66c4eabeb24cc1aae99
|
7
|
+
data.tar.gz: 644a95d9c983e7c2b3711f0f89369867cacbd3aa4674237864c35bf312e37e98d58680854d73e118fbb72e21d9cbb1f8e4da60cb5bc5f7ea5b51959604f0e757
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Change log
|
2
|
+
====
|
3
|
+
|
4
|
+
This gem follows [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html).
|
5
|
+
All classes and public methods are part of the public API.
|
6
|
+
|
7
|
+
0.1.0
|
8
|
+
----
|
9
|
+
Released on 2018-04-18
|
10
|
+
|
11
|
+
All core functionality is implemented:
|
12
|
+
|
13
|
+
- `ECIES::Crypt` class:
|
14
|
+
- `encrypt` method
|
15
|
+
- `decrypt` method
|
16
|
+
- `kdf` method
|
17
|
+
- `DIGESTS` constant
|
18
|
+
- `CIPHERS` constant
|
19
|
+
- `IV` constant
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2018 Stephen McCarthy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# ECIES - Elliptical Curve Integrated Encryption System
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/jamoes/ecies.svg?branch=master)](https://travis-ci.org/jamoes/ecies)
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
This library implements Elliptical Curve Integrated Encryption System (ECIES), as specified by [SEC 1: Elliptic Curve Cryptography, Version 2.0](http://www.secg.org/sec1-v2.pdf).
|
8
|
+
|
9
|
+
ECIES is a public-key encryption scheme based on ECC. It is designed to be semantically secure in the presence of an adversary capable of launching chosen-plaintext and chosen-ciphertext attacks.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
This library is distributed as a gem named [ecies](https://rubygems.org/gems/ecies) at RubyGems.org. To install it, run:
|
14
|
+
|
15
|
+
gem install ecies
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
First, require the gem:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'ecies'
|
23
|
+
```
|
24
|
+
|
25
|
+
Intitlialize a key and a `Crypt` object.
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
key = OpenSSL::PKey::EC.new('secp256k1').generate_key
|
29
|
+
crypt = ECIES::Crypt.new
|
30
|
+
```
|
31
|
+
|
32
|
+
Next, we'll encrypt a message. Although in this example our key contains both the private and public components, you only need the key to contain the public component to encrypt a message.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
encrypted = crypt.encrypt(key, 'secret message')
|
36
|
+
```
|
37
|
+
|
38
|
+
Finally, decrypt the message. In order to decrypt, the key must contain the private component.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
crypt.decrypt(key, encrypted) # => 'secret message'
|
42
|
+
```
|
43
|
+
|
44
|
+
When constructing a `Crypt` object, the default hash digest function is 'SHA256', and the default cipher algorithm is 'AES-256-CTR'. You can also specify alternative cipher or digest algorithms. For example:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
crypt = ECIES::Crypt.new(cipher: 'AES-256-CBC', digest: 'SHA512')
|
48
|
+
```
|
49
|
+
|
50
|
+
The `Crypt` object must be initialized with the same parameters when encrypting and decrypting messages.
|
51
|
+
|
52
|
+
## Compatibility
|
53
|
+
|
54
|
+
The sec1-v2 document allows for a many combinations of various algorithms for ECIES. This library only supports a subset of the allowable algorithms.
|
55
|
+
|
56
|
+
- Key Derivation Functions
|
57
|
+
- Supported:
|
58
|
+
- ANSI-X9.63-KDF
|
59
|
+
- Not supported:
|
60
|
+
- IKEv2-KDF
|
61
|
+
- TLS-KDF
|
62
|
+
- NIST-800-56-Concatenation-KDF
|
63
|
+
- Hash Functions
|
64
|
+
- Supported:
|
65
|
+
- SHA-224
|
66
|
+
- SHA-256
|
67
|
+
- SHA-384
|
68
|
+
- SHA-512
|
69
|
+
- Not supported:
|
70
|
+
- SHA-1
|
71
|
+
- MAC Schemes
|
72
|
+
- Supported:
|
73
|
+
- HMAC-SHA-224-112
|
74
|
+
- HMAC-SHA-224-224
|
75
|
+
- HMAC-SHA-256-128
|
76
|
+
- HMAC-SHA-256-256
|
77
|
+
- HMAC-SHA-384-192
|
78
|
+
- HMAC-SHA-384-384 (I believe sec1-v2 has a typo here, they state "HMAC-SHA-384-284". 284 bits would be 35.5 bytes, which is non-sensical)
|
79
|
+
- HMAC-SHA-512-256
|
80
|
+
- HMAC-SHA-512-512
|
81
|
+
- Not supported:
|
82
|
+
- HMAC-SHA-1-160
|
83
|
+
- HMAC-SHA-1-80
|
84
|
+
- CMAC-AES-128
|
85
|
+
- CMAC-AES-192
|
86
|
+
- CMAC-AES-256
|
87
|
+
- Symmetric Encryption Schemes
|
88
|
+
- Supported:
|
89
|
+
- AES-128-CBC
|
90
|
+
- AES-192-CBC
|
91
|
+
- AES-256-CBC
|
92
|
+
- AES-128-CTR
|
93
|
+
- AES-192-CTR
|
94
|
+
- AES-256-CTR
|
95
|
+
- Not supported:
|
96
|
+
- 3-key TDES in CBC mode
|
97
|
+
- XOR encryption scheme
|
98
|
+
|
99
|
+
## Supported platforms
|
100
|
+
|
101
|
+
Ruby 2.0 and above.
|
102
|
+
|
103
|
+
## Documentation
|
104
|
+
|
105
|
+
For complete documentation, see the [ECIES page on RubyDoc.info](http://rubydoc.info/gems/ecies).
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
task 'default' => 'spec'
|
2
|
+
|
3
|
+
desc 'Run specs'
|
4
|
+
task 'spec' do
|
5
|
+
sh 'rspec'
|
6
|
+
end
|
7
|
+
|
8
|
+
desc 'Run specs and generate coverage report'
|
9
|
+
task 'coverage' do
|
10
|
+
ENV['COVERAGE'] = 'Y'
|
11
|
+
Rake::Task['spec'].invoke
|
12
|
+
end
|
13
|
+
|
14
|
+
desc 'Print out lines of code and related statistics.'
|
15
|
+
task 'stats' do
|
16
|
+
puts 'Lines of code and comments (including blank lines):'
|
17
|
+
sh "find lib -type f | xargs wc -l"
|
18
|
+
puts "\nLines of code (excluding comments and blank lines):"
|
19
|
+
sh "find lib -type f | xargs cat | sed '/^\s*#/d;/^\s*$/d' | wc -l"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'Generate documentation'
|
23
|
+
task 'doc' do
|
24
|
+
sh 'yardoc'
|
25
|
+
end
|
data/certs/jamoes.pem
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApzam1j
|
3
|
+
Y2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
4
|
+
b20wHhcNMTgwNDExMDAxMzI4WhcNMTkwNDExMDAxMzI4WjBBMRMwEQYDVQQDDApz
|
5
|
+
am1jY2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
6
|
+
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOk2n/6xgIgrG7
|
7
|
+
avtiI8I9DtcdA326qWYpdQSDLhpSsLNiqiIpo8KF1Zfy3lnAj6JBBIbjiaUsbA/i
|
8
|
+
Wcip0307dHNXZjr+AgYcL7OEp8EBkfAeZaYWMcVBbjiSxkzYesDxm7nvTOaD317h
|
9
|
+
cThBfB9KW1vGEzazomTxSI9sgqCDtWrogMLGag7uTDJ7fKRK6YXz2xncI0uCsmGb
|
10
|
+
7vekXpfn0xb6tr4ljSseCsPJHnXK7SKB4dzHsmQJ12A57aaV7C/bGqbQAC6odb6k
|
11
|
+
V8dw0fnmHC9OSYjV1b2Xr0VmoiT3YA4XsR0/LbeZvGOyQj8S4eHxgFg7wTVhCkCZ
|
12
|
+
D89+p8H5AgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
13
|
+
BBQffCJK6PE+9XaH56VJoFoCl3ECeDAfBgNVHREEGDAWgRRzam1jY2FydGh5QGdt
|
14
|
+
YWlsLmNvbTAfBgNVHRIEGDAWgRRzam1jY2FydGh5QGdtYWlsLmNvbTANBgkqhkiG
|
15
|
+
9w0BAQUFAAOCAQEApvFGCB9uyF1mh1UV77YICagARejAIOhzOcZXjlpulI9xXjQY
|
16
|
+
0QK6P1GdwwE/pgT7YjfJR7VNFobare4WdfCzoWCFc34t2vJwrqkkOB3U7v3TjB+p
|
17
|
+
z/o2pZKLpNEL4bYJBEbd+vAad/nP1v5e2sCmLm86vSoOwiyQnifmP6PSORObbJF4
|
18
|
+
455zxYw1un6NfN0m+pnIKwvshKoOCgI05VJGtEolJoo42fnolmNxa2t6B30Mfmf+
|
19
|
+
kts216EGG4oP6dVuZmf2Ii2F4lQTBDdZM/cisW8jCkO7KeEzJAPhIw1JJwHltHya
|
20
|
+
0TpOI3t2Mz/FJ+rudtz9PJ/d8QvhrF7M7+qH4w==
|
21
|
+
-----END CERTIFICATE-----
|
data/ecies.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../lib/ecies/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'ecies'
|
5
|
+
s.version = ECIES::VERSION
|
6
|
+
s.authors = ['Stephen McCarthy']
|
7
|
+
s.email = 'sjmccarthy@gmail.com'
|
8
|
+
s.summary = 'Elliptical Curve Integrated Encryption System (ECIES), as specified by SEC 1 - Ver. 2.0'
|
9
|
+
s.homepage = 'https://github.com/jamoes/ecies'
|
10
|
+
s.license = 'MIT'
|
11
|
+
|
12
|
+
s.cert_chain = ['certs/jamoes.pem']
|
13
|
+
s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
|
19
|
+
s.required_ruby_version = '>= 2.0'
|
20
|
+
|
21
|
+
s.add_development_dependency 'bundler', '~> 1'
|
22
|
+
s.add_development_dependency 'rake', '~> 12'
|
23
|
+
s.add_development_dependency 'rspec', '~> 3.7'
|
24
|
+
s.add_development_dependency 'simplecov', '~> 0'
|
25
|
+
s.add_development_dependency 'yard', '~> 0.9.12'
|
26
|
+
s.add_development_dependency 'markdown', '~> 1'
|
27
|
+
s.add_development_dependency 'redcarpet', '~> 3' unless RUBY_PLATFORM == 'java'
|
28
|
+
end
|
data/lib/ecies.rb
ADDED
data/lib/ecies/crypt.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
module ECIES
|
2
|
+
# Provides functionality for encrypting and decrypting messages using ECIES.
|
3
|
+
# Encapsulates the configuration parameters chosen for ECIES.
|
4
|
+
class Crypt
|
5
|
+
|
6
|
+
# The allowed digest algorithms for ECIES.
|
7
|
+
DIGESTS = %w{SHA224 SHA256 SHA384 SHA512}
|
8
|
+
|
9
|
+
# The allowed cipher algorithms for ECIES.
|
10
|
+
CIPHERS = %w{AES-128-CBC AES-192-CBC AES-256-CBC AES-128-CTR AES-192-CTR AES-256-CTR}
|
11
|
+
|
12
|
+
# The initialization vector used in ECIES. Quoting from sec1-v2:
|
13
|
+
# "When using ECIES, some exception are made. For the CBC and CTR modes, the
|
14
|
+
# initial value or initial counter are set to be zero and are omitted from
|
15
|
+
# the ciphertext. In general this practice is not advisable, but in the case
|
16
|
+
# of ECIES it is acceptable because the definition of ECIES implies the
|
17
|
+
# symmetric block cipher key is only to be used once.
|
18
|
+
IV = ("\x00" * 16).force_encoding(Encoding::BINARY)
|
19
|
+
|
20
|
+
# Creates a new instance of {Crypt}.
|
21
|
+
#
|
22
|
+
# @param cipher [String] The cipher algorithm to use. Must be one of
|
23
|
+
# {CIPHERS}.
|
24
|
+
# @param digest [String,OpenSSL::Digest] The digest algorithm to use for
|
25
|
+
# HMAC and KDF. Must be one of {DIGESTS}.
|
26
|
+
# @param mac_length [:half,:full] The length of the mac. If :half, the mac
|
27
|
+
# length will be equal to half the mac_digest's digest_legnth. If
|
28
|
+
# :full, the mac length will be equal to the mac_digest's
|
29
|
+
# digest_length.
|
30
|
+
# @param kdf_digest [String,OpenSSL::Digest,nil] The digest algorithm to
|
31
|
+
# use for KDF. If not specified, the `digest` argument will be used.
|
32
|
+
# @param mac_digest [String,OpenSSL::Digest,nil] The digest algorithm to
|
33
|
+
# use for HMAC. If not specified, the `digest` argument will be used.
|
34
|
+
# @param kdf_shared_info [String] Optional. A string containing the shared
|
35
|
+
# info used for KDF, also known as SharedInfo1.
|
36
|
+
# @param mac_shared_info [String] Optional. A string containing the shared
|
37
|
+
# info used for MAC, also known as SharedInfo2.
|
38
|
+
def initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_length: :half, kdf_digest: nil, mac_digest: nil, kdf_shared_info: '', mac_shared_info: '')
|
39
|
+
@cipher = OpenSSL::Cipher.new(cipher)
|
40
|
+
@mac_digest = OpenSSL::Digest.new(mac_digest || digest)
|
41
|
+
@kdf_digest = OpenSSL::Digest.new(kdf_digest || digest)
|
42
|
+
@kdf_shared_info = kdf_shared_info
|
43
|
+
@mac_shared_info = mac_shared_info
|
44
|
+
|
45
|
+
CIPHERS.include?(@cipher.name) or raise "Cipher must be one of #{CIPHERS}"
|
46
|
+
DIGESTS.include?(@mac_digest.name) or raise "Digest must be one of #{DIGESTS}"
|
47
|
+
DIGESTS.include?(@kdf_digest.name) or raise "Digest must be one of #{DIGESTS}"
|
48
|
+
[:half, :full].include?(mac_length) or raise "mac_length must be :half or :full"
|
49
|
+
|
50
|
+
@mac_length = @mac_digest.digest_length
|
51
|
+
@mac_length /= 2 if mac_length == :half
|
52
|
+
end
|
53
|
+
|
54
|
+
# Encrypts a message to a public key using ECIES.
|
55
|
+
#
|
56
|
+
# # @param key [OpenSSL::EC:PKey] The public key.
|
57
|
+
# @param message [String] The plain-text message.
|
58
|
+
# @return [String] The octet string of the encrypted message.
|
59
|
+
def encrypt(key, message)
|
60
|
+
key.public_key? or raise "Must have public key to encrypt"
|
61
|
+
@cipher.reset
|
62
|
+
|
63
|
+
group_copy = OpenSSL::PKey::EC::Group.new(key.group)
|
64
|
+
group_copy.point_conversion_form = :compressed
|
65
|
+
ephemeral_key = OpenSSL::PKey::EC.new(group_copy).generate_key
|
66
|
+
|
67
|
+
shared_secret = ephemeral_key.dh_compute_key(key.public_key)
|
68
|
+
|
69
|
+
key_pair = kdf(shared_secret, @cipher.key_len + @mac_length)
|
70
|
+
cipher_key = key_pair.byteslice(0, @cipher.key_len)
|
71
|
+
hmac_key = key_pair.byteslice(-@mac_length, @mac_length)
|
72
|
+
|
73
|
+
@cipher.encrypt
|
74
|
+
@cipher.iv = IV
|
75
|
+
@cipher.key = cipher_key
|
76
|
+
ciphertext = @cipher.update(message) + @cipher.final
|
77
|
+
|
78
|
+
mac = OpenSSL::HMAC.digest(@mac_digest, hmac_key, ciphertext + @mac_shared_info).byteslice(0, @mac_length)
|
79
|
+
|
80
|
+
ephemeral_key.public_key.to_bn.to_s(2) + ciphertext + mac
|
81
|
+
end
|
82
|
+
|
83
|
+
# Decrypts a message with a private key using ECIES.
|
84
|
+
#
|
85
|
+
# @param key [OpenSSL::EC:PKey] The private key.
|
86
|
+
# @param encrypted_message [String] Octet string of the encrypted message.
|
87
|
+
# @return [String] The plain-text message.
|
88
|
+
def decrypt(key, encrypted_message)
|
89
|
+
key.private_key? or raise "Must have private key to decrypt"
|
90
|
+
@cipher.reset
|
91
|
+
|
92
|
+
group_copy = OpenSSL::PKey::EC::Group.new(key.group)
|
93
|
+
group_copy.point_conversion_form = :compressed
|
94
|
+
|
95
|
+
ephemeral_public_key_length = group_copy.generator.to_bn.to_s(2).bytesize
|
96
|
+
ciphertext_length = encrypted_message.bytesize - ephemeral_public_key_length - @mac_length
|
97
|
+
ciphertext_length > 0 or raise OpenSSL::PKey::ECError, "Encrypted message too short"
|
98
|
+
|
99
|
+
ephemeral_public_key_text = encrypted_message.byteslice(0, ephemeral_public_key_length)
|
100
|
+
ciphertext = encrypted_message.byteslice(ephemeral_public_key_length, ciphertext_length)
|
101
|
+
mac = encrypted_message.byteslice(-@mac_length, @mac_length)
|
102
|
+
|
103
|
+
ephemeral_public_key = OpenSSL::PKey::EC::Point.new(group_copy, OpenSSL::BN.new(ephemeral_public_key_text, 2))
|
104
|
+
|
105
|
+
shared_secret = key.dh_compute_key(ephemeral_public_key)
|
106
|
+
|
107
|
+
key_pair = kdf(shared_secret, @cipher.key_len + @mac_length)
|
108
|
+
cipher_key = key_pair.byteslice(0, @cipher.key_len)
|
109
|
+
hmac_key = key_pair.byteslice(-@mac_length, @mac_length)
|
110
|
+
|
111
|
+
computed_mac = OpenSSL::HMAC.digest(@mac_digest, hmac_key, ciphertext + @mac_shared_info).byteslice(0, @mac_length)
|
112
|
+
computed_mac == mac or raise OpenSSL::PKey::ECError, "Invalid Message Authenticaton Code"
|
113
|
+
|
114
|
+
@cipher.decrypt
|
115
|
+
@cipher.iv = IV
|
116
|
+
@cipher.key = cipher_key
|
117
|
+
|
118
|
+
@cipher.update(ciphertext) + @cipher.final
|
119
|
+
end
|
120
|
+
|
121
|
+
# Key-derivation function, compatible with ANSI-X9.63-KDF
|
122
|
+
#
|
123
|
+
# @param shared_secret [String] The shared secret from which the key will be
|
124
|
+
# derived.
|
125
|
+
# @param length [Integer] The length of the key to generate.
|
126
|
+
# @return [String] Octet string of the derived key.
|
127
|
+
def kdf(shared_secret, length)
|
128
|
+
length >=0 or raise "length cannot be negative"
|
129
|
+
return "" if length == 0
|
130
|
+
|
131
|
+
if length / @kdf_digest.digest_length >= 0xFF_FF_FF_FF
|
132
|
+
raise "length too large"
|
133
|
+
end
|
134
|
+
|
135
|
+
io = StringIO.new(String.new)
|
136
|
+
counter = 0
|
137
|
+
|
138
|
+
loop do
|
139
|
+
counter += 1
|
140
|
+
counter_bytes = [counter].pack('N')
|
141
|
+
|
142
|
+
io << @kdf_digest.digest(shared_secret + counter_bytes + @kdf_shared_info)
|
143
|
+
if io.pos >= length
|
144
|
+
return io.string.byteslice(0, length)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/spec/crypt_spec.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ECIES::Crypt do
|
4
|
+
|
5
|
+
describe 'Encryption and decryption' do
|
6
|
+
|
7
|
+
it 'Encrypts and decrypts' do
|
8
|
+
key = OpenSSL::PKey::EC.new('secp256k1').generate_key
|
9
|
+
crypt = ECIES::Crypt.new
|
10
|
+
|
11
|
+
encrypted = crypt.encrypt(key, 'secret')
|
12
|
+
expect(crypt.decrypt(key, encrypted)).to eq 'secret'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'Encrypts to known values' do
|
16
|
+
OpenSSL::PKey::EC.class_eval do
|
17
|
+
# Overwrites `generate_key` for both the test code below, and the
|
18
|
+
# ephemeral_key generated in the `encrypt` method.
|
19
|
+
def generate_key
|
20
|
+
self.private_key = 2
|
21
|
+
self.public_key = group.generator.mul(private_key)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
key = OpenSSL::PKey::EC.new('secp256k1').generate_key
|
27
|
+
|
28
|
+
crypt = ECIES::Crypt.new
|
29
|
+
crypt_full = ECIES::Crypt.new(mac_length: :full)
|
30
|
+
crypt_sha512 = ECIES::Crypt.new(digest: 'sha512', mac_length: :full)
|
31
|
+
crypt_cbc = ECIES::Crypt.new(cipher: 'aes-256-cbc', mac_length: :full)
|
32
|
+
crypt_mixed = ECIES::Crypt.new(mac_digest: 'sha256', kdf_digest: 'sha512')
|
33
|
+
|
34
|
+
encrypted = crypt.encrypt(key, 'secret')
|
35
|
+
expect(encrypted).to eq "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5B;\x12:\x17\xCE\x84\xAB\x9F\x0E%\xCD\x94~\x1E\xBC\x89$\x11\xEE6\xE4".force_encoding(Encoding::BINARY)
|
36
|
+
expect(crypt.decrypt(key, encrypted)).to eq 'secret'
|
37
|
+
expect{ crypt_full.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
38
|
+
|
39
|
+
encrypted = crypt_full.encrypt(key, 'secret')
|
40
|
+
expect(encrypted).to eq "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5B;\x12:\x17\xCEo\x9B\xA7\x955\x89\x9FR\xDF\x1C\xED\x00\x86<\n\x04\v\xD6(\x9D\xF5\xF9\x13\xC8/\xD7os(ZsF".force_encoding(Encoding::BINARY)
|
41
|
+
expect(crypt_full.decrypt(key, encrypted)).to eq 'secret'
|
42
|
+
expect{ crypt_sha512.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
43
|
+
|
44
|
+
encrypted = crypt_sha512.encrypt(key, 'secret')
|
45
|
+
expect(encrypted).to eq "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5%\r^\xA9\xD9\xDC\xFB\xD7\x14b\xB4\x00\x84\xABl\xEAh\x0Fc\x805\xAF\x1DT\x05\x87`\xA5\xC4\xB7\xB5r\xF6\x89\xB1U0\x0E \xD4\x1E\x16\x184\xE9:\xE7\x951\xF4\xB3\x93\"A\x85\x1F\x9A\x8E\xAD\xE1(\x1D\xB3\xC4\x15\xD3\xB1\xA8\xFB\x1D".force_encoding(Encoding::BINARY)
|
46
|
+
expect(crypt_sha512.decrypt(key, encrypted)).to eq 'secret'
|
47
|
+
expect{ crypt_full.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
48
|
+
|
49
|
+
encrypted = crypt_cbc.encrypt(key, 'secret')
|
50
|
+
expect(encrypted).to eq "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5\x1Fj\x04N\eg($\xD4\xFBD\xFFd\xA1\xA3z\x90T#\x1D\x12{3IM\x93!|\xA5\xAF&\xD4+;\e\xA6i.wD\x1F\xCB\xE1\x90{\xB6\x8B\xAF".force_encoding(Encoding::BINARY)
|
51
|
+
expect(crypt_cbc.decrypt(key, encrypted)).to eq 'secret'
|
52
|
+
expect{ crypt_mixed.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
53
|
+
|
54
|
+
encrypted = crypt_mixed.encrypt(key, 'secret')
|
55
|
+
expect(encrypted).to eq "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5%\r^\xA9\xD9\xDC\xF5\\\x9A\x04\xE0T\x91\xEA=\xA6W\x84X\xBB\xCA\xB4".force_encoding(Encoding::BINARY)
|
56
|
+
expect(crypt_mixed.decrypt(key, encrypted)).to eq 'secret'
|
57
|
+
expect{ crypt_full.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'Raises on unknown cipher or digest' do
|
61
|
+
key = OpenSSL::PKey::EC.new('secp256k1').generate_key
|
62
|
+
|
63
|
+
expect{ ECIES::Crypt.new(digest: 'foo') }.to raise_error(RuntimeError)
|
64
|
+
expect{ ECIES::Crypt.new(digest: 'md5') }.to raise_error(RuntimeError)
|
65
|
+
expect{ ECIES::Crypt.new(cipher: 'foo') }.to raise_error(RuntimeError)
|
66
|
+
expect{ ECIES::Crypt.new(cipher: 'aes-256-gcm') }.to raise_error(RuntimeError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'Raises when key is missing' do
|
70
|
+
key = OpenSSL::PKey::EC.new
|
71
|
+
|
72
|
+
expect{ ECIES::Crypt.new.encrypt(key, 'secret') }.to raise_error(RuntimeError)
|
73
|
+
expect{ ECIES::Crypt.new.decrypt(key, 'secret') }.to raise_error(RuntimeError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
describe '#kdf' do
|
80
|
+
it 'derivates keys correctly' do
|
81
|
+
sha256_test_vectors = [
|
82
|
+
# [shared_secret, shared_info, expected_key]
|
83
|
+
['96c05619d56c328ab95fe84b18264b08725b85e33fd34f08', '', '443024c3dae66b95e6f5670601558f71'],
|
84
|
+
['96f600b73ad6ac5629577eced51743dd2c24c21b1ac83ee4', '', 'b6295162a7804f5667ba9070f82fa522'],
|
85
|
+
['22518b10e70f2a3f243810ae3254139efbee04aa57c7af7d', '75eef81aa3041e33b80971203d2c0c52', 'c498af77161cc59f2962b9a713e2b215152d139766ce34a776df11866a69bf2e52a13d9c7c6fc878c50c5ea0bc7b00e0da2447cfd874f6cf92f30d0097111485500c90c3af8b487872d04685d14c8d1dc8d7fa08beb0ce0ababc11f0bd496269142d43525a78e5bc79a17f59676a5706dc54d54d4d1f0bd7e386128ec26afc21'],
|
86
|
+
['7e335afa4b31d772c0635c7b0e06f26fcd781df947d2990a', 'd65a4812733f8cdbcdfb4b2f4c191d87', 'c0bd9e38a8f9de14c2acd35b2f3410c6988cf02400543631e0d6a4c1d030365acbf398115e51aaddebdc9590664210f9aa9fed770d4c57edeafa0b8c14f93300865251218c262d63dadc47dfa0e0284826793985137e0a544ec80abf2fdf5ab90bdaea66204012efe34971dc431d625cd9a329b8217cc8fd0d9f02b13f2f6b0b'],
|
87
|
+
]
|
88
|
+
|
89
|
+
sha256_test_vectors.each do |shared_secret, shared_info, expected_key|
|
90
|
+
shared_secret = [shared_secret].pack('H*')
|
91
|
+
shared_info = [shared_info].pack('H*')
|
92
|
+
expected_key = [expected_key].pack('H*')
|
93
|
+
|
94
|
+
computed_key = ECIES::Crypt.new(kdf_shared_info: shared_info).kdf(shared_secret, expected_key.size)
|
95
|
+
|
96
|
+
expect(computed_key).to eq expected_key
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'raises when size is invalid' do
|
101
|
+
expect{ ECIES::Crypt.new.kdf('a', -1) }.to raise_error(RuntimeError)
|
102
|
+
expect{ ECIES::Crypt.new.kdf('a', 32 * 2**32) }.to raise_error(RuntimeError)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ecies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephen McCarthy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIDeDCCAmCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBBMRMwEQYDVQQDDApzam1j
|
14
|
+
Y2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
15
|
+
b20wHhcNMTgwNDExMDAxMzI4WhcNMTkwNDExMDAxMzI4WjBBMRMwEQYDVQQDDApz
|
16
|
+
am1jY2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
17
|
+
FgNjb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDOk2n/6xgIgrG7
|
18
|
+
avtiI8I9DtcdA326qWYpdQSDLhpSsLNiqiIpo8KF1Zfy3lnAj6JBBIbjiaUsbA/i
|
19
|
+
Wcip0307dHNXZjr+AgYcL7OEp8EBkfAeZaYWMcVBbjiSxkzYesDxm7nvTOaD317h
|
20
|
+
cThBfB9KW1vGEzazomTxSI9sgqCDtWrogMLGag7uTDJ7fKRK6YXz2xncI0uCsmGb
|
21
|
+
7vekXpfn0xb6tr4ljSseCsPJHnXK7SKB4dzHsmQJ12A57aaV7C/bGqbQAC6odb6k
|
22
|
+
V8dw0fnmHC9OSYjV1b2Xr0VmoiT3YA4XsR0/LbeZvGOyQj8S4eHxgFg7wTVhCkCZ
|
23
|
+
D89+p8H5AgMBAAGjezB5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
24
|
+
BBQffCJK6PE+9XaH56VJoFoCl3ECeDAfBgNVHREEGDAWgRRzam1jY2FydGh5QGdt
|
25
|
+
YWlsLmNvbTAfBgNVHRIEGDAWgRRzam1jY2FydGh5QGdtYWlsLmNvbTANBgkqhkiG
|
26
|
+
9w0BAQUFAAOCAQEApvFGCB9uyF1mh1UV77YICagARejAIOhzOcZXjlpulI9xXjQY
|
27
|
+
0QK6P1GdwwE/pgT7YjfJR7VNFobare4WdfCzoWCFc34t2vJwrqkkOB3U7v3TjB+p
|
28
|
+
z/o2pZKLpNEL4bYJBEbd+vAad/nP1v5e2sCmLm86vSoOwiyQnifmP6PSORObbJF4
|
29
|
+
455zxYw1un6NfN0m+pnIKwvshKoOCgI05VJGtEolJoo42fnolmNxa2t6B30Mfmf+
|
30
|
+
kts216EGG4oP6dVuZmf2Ii2F4lQTBDdZM/cisW8jCkO7KeEzJAPhIw1JJwHltHya
|
31
|
+
0TpOI3t2Mz/FJ+rudtz9PJ/d8QvhrF7M7+qH4w==
|
32
|
+
-----END CERTIFICATE-----
|
33
|
+
date: 2018-04-18 00:00:00.000000000 Z
|
34
|
+
dependencies:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: bundler
|
37
|
+
requirement: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '1'
|
42
|
+
type: :development
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rake
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '12'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '12'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rspec
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.7'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '3.7'
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: simplecov
|
79
|
+
requirement: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
type: :development
|
85
|
+
prerelease: false
|
86
|
+
version_requirements: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: yard
|
93
|
+
requirement: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: 0.9.12
|
98
|
+
type: :development
|
99
|
+
prerelease: false
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 0.9.12
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: markdown
|
107
|
+
requirement: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '1'
|
112
|
+
type: :development
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '1'
|
119
|
+
- !ruby/object:Gem::Dependency
|
120
|
+
name: redcarpet
|
121
|
+
requirement: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '3'
|
126
|
+
type: :development
|
127
|
+
prerelease: false
|
128
|
+
version_requirements: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - "~>"
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '3'
|
133
|
+
description:
|
134
|
+
email: sjmccarthy@gmail.com
|
135
|
+
executables: []
|
136
|
+
extensions: []
|
137
|
+
extra_rdoc_files: []
|
138
|
+
files:
|
139
|
+
- ".gitignore"
|
140
|
+
- ".travis.yml"
|
141
|
+
- ".yardopts"
|
142
|
+
- CHANGELOG.md
|
143
|
+
- Gemfile
|
144
|
+
- LICENSE
|
145
|
+
- README.md
|
146
|
+
- Rakefile
|
147
|
+
- certs/jamoes.pem
|
148
|
+
- ecies.gemspec
|
149
|
+
- lib/ecies.rb
|
150
|
+
- lib/ecies/crypt.rb
|
151
|
+
- lib/ecies/version.rb
|
152
|
+
- spec/crypt_spec.rb
|
153
|
+
- spec/spec_helper.rb
|
154
|
+
homepage: https://github.com/jamoes/ecies
|
155
|
+
licenses:
|
156
|
+
- MIT
|
157
|
+
metadata: {}
|
158
|
+
post_install_message:
|
159
|
+
rdoc_options: []
|
160
|
+
require_paths:
|
161
|
+
- lib
|
162
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '2.0'
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
version: '0'
|
172
|
+
requirements: []
|
173
|
+
rubyforge_project:
|
174
|
+
rubygems_version: 2.7.6
|
175
|
+
signing_key:
|
176
|
+
specification_version: 4
|
177
|
+
summary: Elliptical Curve Integrated Encryption System (ECIES), as specified by SEC
|
178
|
+
1 - Ver. 2.0
|
179
|
+
test_files:
|
180
|
+
- spec/crypt_spec.rb
|
181
|
+
- spec/spec_helper.rb
|
metadata.gz.sig
ADDED
Binary file
|