ecies 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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