ecies 0.1.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
- 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
|
+
[](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
|