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.
@@ -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
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ .ruby-version
2
+ .ruby-gemset
3
+ Gemfile.lock
4
+ *.gem
5
+ coverage/
6
+ doc/
7
+ .yardoc/
@@ -0,0 +1,10 @@
1
+ script: "rspec"
2
+ language: "ruby"
3
+ sudo: false
4
+ rvm:
5
+ - 2.0
6
+ - 2.1
7
+ - 2.2
8
+ - 2.3
9
+ - 2.4
10
+ - 2.5
@@ -0,0 +1,7 @@
1
+ --readme README.md
2
+ --markup markdown
3
+ lib/*.rb
4
+ lib/*/*.rb
5
+ -
6
+ CHANGELOG.md
7
+ LICENSE
@@ -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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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.
@@ -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).
@@ -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
@@ -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-----
@@ -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
@@ -0,0 +1,8 @@
1
+ require 'openssl'
2
+
3
+ require 'ecies/crypt.rb'
4
+ require 'ecies/version.rb'
5
+
6
+ # The top-level module for the ecies gem.
7
+ module ECIES
8
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module ECIES
2
+ # This gem's version.
3
+ VERSION = '0.1.0'
4
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ if ENV['COVERAGE']
2
+ require 'simplecov'
3
+ SimpleCov.start
4
+ end
5
+
6
+ require 'ecies'
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
Binary file