ecies 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -2
- data/.gitignore +1 -0
- data/CHANGELOG.md +25 -1
- data/README.md +12 -11
- data/certs/jamoes.pem +23 -18
- data/ecies.gemspec +2 -5
- data/lib/ecies/crypt.rb +44 -37
- data/lib/ecies/version.rb +1 -1
- data/lib/ecies.rb +3 -2
- data/spec/crypt_spec.rb +61 -65
- data.tar.gz.sig +0 -0
- metadata +32 -28
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccdc4376e7e505685dafd7456609bee87adb7909ee863dea0a02a46bc10ea9ec
|
4
|
+
data.tar.gz: c2c8c601e446e361ea28d9605108ba52b5df6d3444b20093f410ac6cee73621a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14dcb2ccec245e71d68eb63b520d320c6e39b330a2f01fd59848ce9ff0d6436d90e100eb5b5857bda40b780dedf365d2bfc4f14dbca9ca8b612560bb82a5c111
|
7
|
+
data.tar.gz: dd259230ce124924f5269b1fbb966ea17207ee41dcc206669d1eccc74e62ccd35803f9c96c869011333526cf5c8a03175d671abbf27356d117e5502d42e54d51
|
checksums.yaml.gz.sig
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
�������=xu8
|
2
|
+
x��;���Y�E�D]B��'�Z�����r���c$�{1�L���kWm�ѓ����2E��6z�p���
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,9 +4,33 @@ Change log
|
|
4
4
|
This gem follows [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html).
|
5
5
|
All classes and public methods are part of the public API.
|
6
6
|
|
7
|
+
0.4.0
|
8
|
+
---
|
9
|
+
Released on 2023-06-20
|
10
|
+
|
11
|
+
- Add support for OpenSSL 3.0.
|
12
|
+
- Remove `Crypt.private_key_from_hex` method.
|
13
|
+
- Users can still utilize `OpenSSL::PKey::EC.new` to construct a PKey.
|
14
|
+
- `Crypt.public_key_from_hex` now raises `ArgumentError` on an invalid key, rather than an `OpenSSL::PKey::EC::Point::Error`.
|
15
|
+
- Explicitly require 'stringio' (thanks thekuwayama!).
|
16
|
+
|
17
|
+
0.3.0
|
18
|
+
---
|
19
|
+
Released on 2018-04-22
|
20
|
+
|
21
|
+
- Prevent benign malleability, as suggested in sec1-v2 page 97.
|
22
|
+
- The ECIES process is modified to prevent benign malleability by including the ephemeral public key as an input to the KDF.
|
23
|
+
- All encrypted output generated with previous versions cannot be decrypted with this version, and older versions cannot decrypt output generated with this version.
|
24
|
+
- The choice was made to simply break compatibility early in this library's life, rather than add an extra configuration parameter that should almost always be unused.
|
25
|
+
- Add `Crypt.public_key_from_hex` method.
|
26
|
+
- Add `Crypt.private_key_from_hex` method.
|
27
|
+
- Remove support for hex-encoded keys in `Crypt#encrypt` and `Crypt#decrypt` methods. The above `*_from_hex` helper methods can be used instead.
|
28
|
+
- Remove `ec_group` option from `Crypt` constructor.
|
29
|
+
- Add `Crypt#to_s` method.
|
30
|
+
|
7
31
|
0.2.0
|
8
32
|
---
|
9
|
-
|
33
|
+
Released on 2018-04-19
|
10
34
|
|
11
35
|
- Add support for hex-encoded keys in `Crypt#encrypt` and `Crypt#decrypt` methods.
|
12
36
|
- Add new option `ec_group` in `Crypt` constructor.
|
data/README.md
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
# ECIES - Elliptical Curve Integrated Encryption System
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/jamoes/ecies.svg?branch=master)](https://travis-ci.org/jamoes/ecies)
|
4
|
-
|
5
3
|
## Description
|
6
4
|
|
7
5
|
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
6
|
|
9
7
|
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
8
|
|
11
|
-
ECIES can be used to encrypt messages to bitcoin addresses with keys published on the blockchain, and subsequently to decrypt messages by the holders of the address's private key.
|
9
|
+
ECIES can be used to encrypt messages to bitcoin addresses with public keys published on the blockchain, and subsequently to decrypt messages by the holders of the address's private key.
|
12
10
|
|
13
11
|
## Installation
|
14
12
|
|
@@ -27,7 +25,7 @@ require 'ecies'
|
|
27
25
|
Intitlialize a key and a `Crypt` object.
|
28
26
|
|
29
27
|
```ruby
|
30
|
-
key = OpenSSL::PKey::EC.
|
28
|
+
key = OpenSSL::PKey::EC.generate('secp256k1')
|
31
29
|
crypt = ECIES::Crypt.new
|
32
30
|
```
|
33
31
|
|
@@ -48,17 +46,17 @@ crypt.decrypt(key, encrypted) # => "secret message"
|
|
48
46
|
Bitcoin P2PKH addresses themselves contain only *hashes* of public keys (hence the name, pay-to-public-key-hash). However, any time a P2PKH output is spent, the public key associated with the address is published on the blockchain in the transaction's scriptSig. This allows you to encrypt a message to any bitcoin address that has sent a transaction (or published its public key in other ways). To demonstrate this, we'll encrypt a message to Satoshi's public key from Bitcoin's genesis block:
|
49
47
|
|
50
48
|
```ruby
|
51
|
-
|
49
|
+
public_key = ECIES::Crypt.public_key_from_hex(
|
52
50
|
"04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb"\
|
53
|
-
"649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"
|
54
|
-
encrypted = ECIES::Crypt.new.encrypt(
|
51
|
+
"649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")
|
52
|
+
encrypted = ECIES::Crypt.new.encrypt(public_key, 'secret message')
|
55
53
|
```
|
56
54
|
|
57
55
|
To decrypt this message, Satoshi would follow these steps:
|
58
56
|
|
59
57
|
```ruby
|
60
|
-
|
61
|
-
ECIES::Crypt.new.decrypt(
|
58
|
+
private_key = OpenSSL::PKey::EC.new("<PEM/DER encoded private key for genesis block>")
|
59
|
+
ECIES::Crypt.new.decrypt(private_key, encrypted) # => "secret message"
|
62
60
|
```
|
63
61
|
|
64
62
|
### Default parameters
|
@@ -74,8 +72,7 @@ These defaults work well for encrypting messages to bitcoin keys. This library a
|
|
74
72
|
|
75
73
|
## Compatibility
|
76
74
|
|
77
|
-
The sec1-v2 document allows for
|
78
|
-
|
75
|
+
The sec1-v2 document allows for many combinations of various algorithms for ECIES. This library only supports a subset of the allowable algorithms:
|
79
76
|
- Key Derivation Functions
|
80
77
|
- Supported:
|
81
78
|
- ANSI-X9.63-KDF
|
@@ -119,6 +116,10 @@ The sec1-v2 document allows for a many combinations of various algorithms for EC
|
|
119
116
|
- 3-key TDES in CBC mode
|
120
117
|
- XOR encryption scheme
|
121
118
|
|
119
|
+
In addition, the following options have been chosen:
|
120
|
+
- Elliptical curve points are represented in compressed form.
|
121
|
+
- Benign malleability is prevented by including the ephemeral public key as an input to the KDF (sec1-v2 p97).
|
122
|
+
|
122
123
|
## Supported platforms
|
123
124
|
|
124
125
|
Ruby 2.0 and above.
|
data/certs/jamoes.pem
CHANGED
@@ -1,21 +1,26 @@
|
|
1
1
|
-----BEGIN CERTIFICATE-----
|
2
|
-
|
2
|
+
MIIEeDCCAuCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMRMwEQYDVQQDDApzam1j
|
3
3
|
Y2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
4
|
-
|
4
|
+
b20wHhcNMjMwNjIwMjEwMTI2WhcNMjQwNjE5MjEwMTI2WjBBMRMwEQYDVQQDDApz
|
5
5
|
am1jY2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
FgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDOGmdomo9zQxUD
|
7
|
+
PMEtDYtzY7Fnb/T/TVXAhKMO06Fw84eEMdU4yOUOMapsv6DinBMm0kDjseFkuqm+
|
8
|
+
BFYcU7AaGfKOuoPJQ23LuQOBXg5STw+pDKGdoq1th26M1v05Q9hCH4HVlGIiNPzx
|
9
|
+
J+0jFoH9qmiNhHxCD03702etb/ppJsCbBGzpMzofbgKQEHIKZNAkVi2RU2QLqXDt
|
10
|
+
A44wknz8NkiWahgN24cpLn/euz5G+J4nFgJ32YUBgA/mVXdeEOhxgMCPIbHIxiez
|
11
|
+
3c/yymARogxf6e9b3Pcwo1k63zTZYf9YHIM+Q5lKiOWOGQ1lUG3nanB9NhFdUkAk
|
12
|
+
PcuEzQUp5/pQmnoM/j+VpeXyvwiNLTzsxNRGfePSZU+oDpkEvlN1qaJXmHRpedWp
|
13
|
+
zOcUlVUpwFSWM1WWDGm4FCxoYH8GdYdHVXmUWWm/iPcfLCkXwNUXL+i38OXbVSp2
|
14
|
+
aqaXrRtkwZsfmE4PCHVUJyHFeVXLqvgK3DLVPfCGk6ty4X7wuXcCAwEAAaN7MHkw
|
15
|
+
CQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFELj2NEPT2elfT1kefaM
|
16
|
+
tcBz+k6QMB8GA1UdEQQYMBaBFHNqbWNjYXJ0aHlAZ21haWwuY29tMB8GA1UdEgQY
|
17
|
+
MBaBFHNqbWNjYXJ0aHlAZ21haWwuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQBvbFzT
|
18
|
+
zHTVmJ2J3VvWf1J4HFTkLDtRi9lFLtPqvsNczd+FeO3s/pI9/gr56mBgpZ/vLypN
|
19
|
+
9CBkk4wR4GLifl8+xuDtVkYYh3Ru9S3ru8iu225aOIfg6MpKH7x+IsShxm7jdEva
|
20
|
+
AnqMQ47KEOdRetbBMIHeWtCSxyKhXSJsfoIh+fkQZlIM6+3l0ljTiRxo5Eb1JyEn
|
21
|
+
Wb+jyVwJugHuGPb2tB9dx+khKgrv0YQ0lxsIaqSYRT5pb3xZuJNoxj00IsZGOx0J
|
22
|
+
s5SzopHnmHWPS0h9CVcTa9QgEz5MbCk1X2rWhmUkGR0pzHdeq6vvWr0pxhpiWJdA
|
23
|
+
eaD1Uldg06Zi31RbAARghTuRKzg+SA9/DmglkO479GuEhZvGA0P2fBvKTaVAZR+4
|
24
|
+
Lb3ESftZmM+MReXZS5FfAeYJBG5+R/LDyMiuQIz6WLYcPpfHpSX873IXAcNYTCd4
|
25
|
+
RVHpiTyCFYjchqoOvSC7rHgf1HpEQbuXsl9RKWhSQOyTGlUGbCCGFDx10Dg=
|
26
|
+
-----END CERTIFICATE-----
|
data/ecies.gemspec
CHANGED
@@ -14,17 +14,14 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.homepage = 'https://github.com/jamoes/ecies'
|
15
15
|
s.license = 'MIT'
|
16
16
|
|
17
|
-
s.cert_chain = ['certs/jamoes.pem']
|
18
|
-
s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
|
19
|
-
|
20
17
|
s.files = `git ls-files`.split("\n")
|
21
18
|
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
22
19
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
23
20
|
|
24
21
|
s.required_ruby_version = '>= 2.0'
|
25
22
|
|
26
|
-
s.add_development_dependency 'bundler', '~>
|
27
|
-
s.add_development_dependency 'rake', '~>
|
23
|
+
s.add_development_dependency 'bundler', '~> 2'
|
24
|
+
s.add_development_dependency 'rake', '~> 13'
|
28
25
|
s.add_development_dependency 'rspec', '~> 3.7'
|
29
26
|
s.add_development_dependency 'simplecov', '~> 0'
|
30
27
|
s.add_development_dependency 'yard', '~> 0.9.12'
|
data/lib/ecies/crypt.rb
CHANGED
@@ -27,9 +27,6 @@ module ECIES
|
|
27
27
|
# length will be equal to half the mac_digest's digest_legnth. If
|
28
28
|
# :full, the mac length will be equal to the mac_digest's
|
29
29
|
# digest_length.
|
30
|
-
# @param ec_group [OpenSSL::PKey::EC::Group,String] The elliptical curve
|
31
|
-
# group to use when the key is passed in hex form to `encrypt` or
|
32
|
-
# `decrypt`.
|
33
30
|
# @param kdf_digest [String,OpenSSL::Digest,nil] The digest algorithm to
|
34
31
|
# use for KDF. If not specified, the `digest` argument will be used.
|
35
32
|
# @param mac_digest [String,OpenSSL::Digest,nil] The digest algorithm to
|
@@ -38,9 +35,8 @@ module ECIES
|
|
38
35
|
# info used for KDF, also known as SharedInfo1.
|
39
36
|
# @param mac_shared_info [String] Optional. A string containing the shared
|
40
37
|
# info used for MAC, also known as SharedInfo2.
|
41
|
-
def initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_length: :half,
|
38
|
+
def initialize(cipher: 'AES-256-CTR', digest: 'SHA256', mac_length: :half, kdf_digest: nil, mac_digest: nil, kdf_shared_info: '', mac_shared_info: '')
|
42
39
|
@cipher = OpenSSL::Cipher.new(cipher)
|
43
|
-
@ec_group = OpenSSL::PKey::EC::Group.new(ec_group)
|
44
40
|
@mac_digest = OpenSSL::Digest.new(mac_digest || digest)
|
45
41
|
@kdf_digest = OpenSSL::Digest.new(kdf_digest || digest)
|
46
42
|
@kdf_shared_info = kdf_shared_info
|
@@ -57,27 +53,19 @@ module ECIES
|
|
57
53
|
|
58
54
|
# Encrypts a message to a public key using ECIES.
|
59
55
|
#
|
60
|
-
# @param key [OpenSSL::EC:PKey
|
61
|
-
# containing the public key, or a hex-encoded string representing the
|
62
|
-
# public key on this Crypt's `ec_group`.
|
56
|
+
# @param key [OpenSSL::EC:PKey] The public key.
|
63
57
|
# @param message [String] The plain-text message.
|
64
58
|
# @return [String] The octet string of the encrypted message.
|
65
59
|
def encrypt(key, message)
|
66
|
-
if key.is_a?(String)
|
67
|
-
new_key = OpenSSL::PKey::EC.new(@ec_group)
|
68
|
-
new_key.public_key = OpenSSL::PKey::EC::Point.new(@ec_group, OpenSSL::BN.new(key, 16))
|
69
|
-
key = new_key
|
70
|
-
end
|
71
60
|
key.public_key? or raise "Must have public key to encrypt"
|
72
61
|
@cipher.reset
|
73
62
|
|
74
|
-
|
75
|
-
|
76
|
-
ephemeral_key = OpenSSL::PKey::EC.new(group_copy).generate_key
|
63
|
+
ephemeral_key = OpenSSL::PKey::EC.generate(key.group)
|
64
|
+
ephemeral_public_key_octet = ephemeral_key.public_key.to_octet_string(:compressed)
|
77
65
|
|
78
66
|
shared_secret = ephemeral_key.dh_compute_key(key.public_key)
|
79
67
|
|
80
|
-
key_pair = kdf(shared_secret, @cipher.key_len + @mac_length)
|
68
|
+
key_pair = kdf(shared_secret, @cipher.key_len + @mac_length, ephemeral_public_key_octet)
|
81
69
|
cipher_key = key_pair.byteslice(0, @cipher.key_len)
|
82
70
|
hmac_key = key_pair.byteslice(-@mac_length, @mac_length)
|
83
71
|
|
@@ -88,42 +76,31 @@ module ECIES
|
|
88
76
|
|
89
77
|
mac = OpenSSL::HMAC.digest(@mac_digest, hmac_key, ciphertext + @mac_shared_info).byteslice(0, @mac_length)
|
90
78
|
|
91
|
-
|
79
|
+
ephemeral_public_key_octet + ciphertext + mac
|
92
80
|
end
|
93
81
|
|
94
82
|
# Decrypts a message with a private key using ECIES.
|
95
83
|
#
|
96
84
|
# @param key [OpenSSL::EC:PKey] The private key.
|
97
|
-
# @param key [OpenSSL::EC:PKey,String] The private key. An OpenSSL::EC:PKey
|
98
|
-
# containing the private key, or a hex-encoded string representing the
|
99
|
-
# private key on this Crypt's `ec_group`.
|
100
85
|
# @param encrypted_message [String] Octet string of the encrypted message.
|
101
86
|
# @return [String] The plain-text message.
|
102
87
|
def decrypt(key, encrypted_message)
|
103
|
-
if key.is_a?(String)
|
104
|
-
new_key = OpenSSL::PKey::EC.new(@ec_group)
|
105
|
-
new_key.private_key = OpenSSL::BN.new(key, 16)
|
106
|
-
key = new_key
|
107
|
-
end
|
108
88
|
key.private_key? or raise "Must have private key to decrypt"
|
109
89
|
@cipher.reset
|
110
90
|
|
111
|
-
|
112
|
-
group_copy.point_conversion_form = :compressed
|
113
|
-
|
114
|
-
ephemeral_public_key_length = group_copy.generator.to_bn.to_s(2).bytesize
|
91
|
+
ephemeral_public_key_length = key.group.generator.to_octet_string(:compressed).bytesize
|
115
92
|
ciphertext_length = encrypted_message.bytesize - ephemeral_public_key_length - @mac_length
|
116
93
|
ciphertext_length > 0 or raise OpenSSL::PKey::ECError, "Encrypted message too short"
|
117
94
|
|
118
|
-
|
95
|
+
ephemeral_public_key_octet = encrypted_message.byteslice(0, ephemeral_public_key_length)
|
119
96
|
ciphertext = encrypted_message.byteslice(ephemeral_public_key_length, ciphertext_length)
|
120
97
|
mac = encrypted_message.byteslice(-@mac_length, @mac_length)
|
121
98
|
|
122
|
-
ephemeral_public_key = OpenSSL::PKey::EC::Point.new(
|
99
|
+
ephemeral_public_key = OpenSSL::PKey::EC::Point.new(key.group, OpenSSL::BN.new(ephemeral_public_key_octet, 2))
|
123
100
|
|
124
101
|
shared_secret = key.dh_compute_key(ephemeral_public_key)
|
125
102
|
|
126
|
-
key_pair = kdf(shared_secret, @cipher.key_len + @mac_length)
|
103
|
+
key_pair = kdf(shared_secret, @cipher.key_len + @mac_length, ephemeral_public_key_octet)
|
127
104
|
cipher_key = key_pair.byteslice(0, @cipher.key_len)
|
128
105
|
hmac_key = key_pair.byteslice(-@mac_length, @mac_length)
|
129
106
|
|
@@ -139,11 +116,13 @@ module ECIES
|
|
139
116
|
|
140
117
|
# Key-derivation function, compatible with ANSI-X9.63-KDF
|
141
118
|
#
|
142
|
-
# @param shared_secret [String] The shared secret from which the key will
|
143
|
-
# derived.
|
119
|
+
# @param shared_secret [String] The shared secret from which the key will
|
120
|
+
# be derived.
|
144
121
|
# @param length [Integer] The length of the key to generate.
|
122
|
+
# @param shared_info_suffix [String] The suffix to append to the
|
123
|
+
# shared_info.
|
145
124
|
# @return [String] Octet string of the derived key.
|
146
|
-
def kdf(shared_secret, length)
|
125
|
+
def kdf(shared_secret, length, shared_info_suffix)
|
147
126
|
length >=0 or raise "length cannot be negative"
|
148
127
|
return "" if length == 0
|
149
128
|
|
@@ -158,11 +137,39 @@ module ECIES
|
|
158
137
|
counter += 1
|
159
138
|
counter_bytes = [counter].pack('N')
|
160
139
|
|
161
|
-
io << @kdf_digest.digest(shared_secret + counter_bytes + @kdf_shared_info)
|
140
|
+
io << @kdf_digest.digest(shared_secret + counter_bytes + @kdf_shared_info + shared_info_suffix)
|
162
141
|
if io.pos >= length
|
163
142
|
return io.string.byteslice(0, length)
|
164
143
|
end
|
165
144
|
end
|
166
145
|
end
|
146
|
+
|
147
|
+
# @return [String] A string representing this Crypt's parameters.
|
148
|
+
def to_s
|
149
|
+
"KDF-#{@kdf_digest.name}_" +
|
150
|
+
"HMAC-SHA-#{@mac_digest.digest_length * 8}-#{@mac_length * 8}_" +
|
151
|
+
@cipher.name
|
152
|
+
end
|
153
|
+
|
154
|
+
# Converts a hex-encoded public key to an `OpenSSL::PKey::EC`.
|
155
|
+
#
|
156
|
+
# @param hex_string [String] The hex-encoded public key.
|
157
|
+
# @param ec_group [OpenSSL::PKey::EC::Group,String] The elliptical curve
|
158
|
+
# group for this public key.
|
159
|
+
# @return [OpenSSL::PKey::EC] The public key.
|
160
|
+
# @raise [ArgumentError] If the public key is invalid.
|
161
|
+
def self.public_key_from_hex(hex_string, ec_group = 'secp256k1')
|
162
|
+
ec_group = OpenSSL::PKey::EC::Group.new(ec_group) if ec_group.is_a?(String)
|
163
|
+
|
164
|
+
sequence = OpenSSL::ASN1.Sequence([
|
165
|
+
OpenSSL::ASN1.Sequence([
|
166
|
+
OpenSSL::ASN1.ObjectId("id-ecPublicKey"),
|
167
|
+
OpenSSL::ASN1::ObjectId(ec_group.curve_name),
|
168
|
+
]),
|
169
|
+
OpenSSL::ASN1.BitString([hex_string].pack('H*')),
|
170
|
+
])
|
171
|
+
|
172
|
+
OpenSSL::PKey::EC.new(sequence.to_der)
|
173
|
+
end
|
167
174
|
end
|
168
175
|
end
|
data/lib/ecies/version.rb
CHANGED
data/lib/ecies.rb
CHANGED
data/spec/crypt_spec.rb
CHANGED
@@ -5,89 +5,79 @@ describe ECIES::Crypt do
|
|
5
5
|
describe 'Encryption and decryption' do
|
6
6
|
|
7
7
|
it 'Encrypts and decrypts' do
|
8
|
-
key = OpenSSL::PKey::EC.
|
8
|
+
key = OpenSSL::PKey::EC.generate('secp256k1')
|
9
9
|
crypt = ECIES::Crypt.new
|
10
10
|
|
11
11
|
encrypted = crypt.encrypt(key, 'secret')
|
12
12
|
expect(crypt.decrypt(key, encrypted)).to eq 'secret'
|
13
|
+
|
14
|
+
expect{ ECIES::Crypt.new(mac_length: :full).decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
15
|
+
expect{ ECIES::Crypt.new(mac_digest: 'sha512').decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
13
16
|
end
|
14
17
|
|
15
18
|
it 'Supports hex-encoded keys' do
|
16
|
-
key = OpenSSL::PKey::EC.
|
19
|
+
key = OpenSSL::PKey::EC.generate('secp256k1')
|
17
20
|
public_key_hex = key.public_key.to_bn.to_s(16)
|
18
|
-
private_key_hex = key.private_key.to_s(16)
|
19
21
|
|
20
|
-
|
22
|
+
public_key = ECIES::Crypt.public_key_from_hex(public_key_hex)
|
23
|
+
|
24
|
+
expect(public_key.public_key).to eq key.public_key
|
21
25
|
|
22
|
-
|
23
|
-
expect(crypt.decrypt(private_key_hex, encrypted)).to eq 'secret'
|
26
|
+
expect{ ECIES::Crypt.public_key_from_hex(public_key_hex, 'secp224k1') }.to raise_error(ArgumentError)
|
24
27
|
end
|
25
28
|
|
26
29
|
it 'Supports other EC curves' do
|
27
|
-
key = OpenSSL::PKey::EC.
|
28
|
-
crypt = ECIES::Crypt.new
|
30
|
+
key = OpenSSL::PKey::EC.generate('secp224k1')
|
31
|
+
crypt = ECIES::Crypt.new
|
29
32
|
|
30
33
|
encrypted = crypt.encrypt(key, 'secret')
|
31
34
|
expect(crypt.decrypt(key, encrypted)).to eq 'secret'
|
32
|
-
|
33
|
-
encrypted = crypt.encrypt(key.public_key.to_bn.to_s(16), 'secret')
|
34
|
-
expect(crypt.decrypt(key.private_key.to_s(16), encrypted)).to eq 'secret'
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
context 'known value' do
|
38
|
+
before(:all) do
|
39
|
+
OpenSSL::PKey::EC.instance_eval do
|
40
|
+
# Overwrites `generate` for both the key generated below, and the
|
41
|
+
# ephemeral_key generated in the `encrypt` method.
|
42
|
+
# The private_key is equal to '2', and the group is 'secp256k1'.
|
43
|
+
def generate(group)
|
44
|
+
OpenSSL::PKey::EC.new("0t\x02\x01\x01\x04 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xA0\a\x06\x05+\x81\x04\x00\n\xA1D\x03B\x00\x04\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5\x1A\xE1h\xFE\xA6=\xC39\xA3\xC5\x84\x19Fl\xEA\xEE\xF7\xF62e2f\xD0\xE1#d1\xA9P\xCF\xE5*")
|
45
|
+
end
|
46
|
+
end
|
40
47
|
|
41
|
-
|
42
|
-
|
48
|
+
@key = OpenSSL::PKey::EC.generate('secp256k1')
|
49
|
+
end
|
43
50
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
51
|
+
[
|
52
|
+
[ECIES::Crypt.new, "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5C\x9E\xE0\x0FYBZ\xBB\xC8\x95\x93\xC1@\xC6+\xE2/yb\x065\xFF".b],
|
53
|
+
[ECIES::Crypt.new(mac_length: :full), "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5C\x9E\xE0\x0FYB\x03.\x1E\x92,[\rI\xBC\xCC\xFD%\xCD)9\v!]]A\xE0\xADc\xBA[\xA4\xF2\xB1\xB5\xC5)\xA4".b],
|
54
|
+
[ECIES::Crypt.new(digest: 'sha512', mac_length: :full), "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5\xA2Y\x1A\x7F\xB3\xB2\xA7\xDE\x03\xF4\xA6\e\xD1\x9F\xF9\xD5P\x06\x91\x8EiW\xC82\xD9\xBB\xD2\xC92\xE2\x9F\x15F.\x8C]\xE3Y2\xD3L\xE8\xC4\x9F\xBF\xA5S\x98\x9AYy_Y\xF8\x05\xE7\x19\x9E\xDA\vn;Bvm\xA2`i5:".b],
|
55
|
+
[ECIES::Crypt.new(cipher: 'aes-256-cbc', mac_length: :full), "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5\xDF\xCD\x95\xAD!m\xAA/Xv\"\x97\x04\xEE\x9F\xEB^\x1F\xA7\xC9n\xE3\x94l;\xBA\xF2\xBE\xCD\x83\x02+\x02\x9D\x18\x11\x9A\xBEz_\x8A\xDB\xA3\x00\xF7\x8A\x94G".b],
|
56
|
+
[ECIES::Crypt.new(mac_digest: 'sha256', kdf_digest: 'sha512'), "\x02\xC6\x04\x7F\x94A\xED}m0E@n\x95\xC0|\xD8\\w\x8EK\x8C\xEF<\xA7\xAB\xAC\t\xB9\\p\x9E\xE5\xA2Y\x1A\x7F\xB3\xB2l\x9E|\xC4\xBCE r\xA6\xB1k\x93W\xE5d\xE4".b],
|
57
|
+
].each do |crypt, expected_value|
|
58
|
+
it "matches for #{crypt.to_s}" do
|
59
|
+
encrypted = crypt.encrypt(@key, 'secret')
|
60
|
+
expect(encrypted).to eq expected_value
|
61
|
+
expect(crypt.decrypt(@key, encrypted)).to eq 'secret'
|
52
62
|
end
|
53
63
|
end
|
64
|
+
end
|
54
65
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
expect{ crypt_full.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
67
|
-
|
68
|
-
encrypted = crypt_full.encrypt(key, 'secret')
|
69
|
-
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)
|
70
|
-
expect(crypt_full.decrypt(key, encrypted)).to eq 'secret'
|
71
|
-
expect{ crypt_sha512.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
72
|
-
|
73
|
-
encrypted = crypt_sha512.encrypt(key, 'secret')
|
74
|
-
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)
|
75
|
-
expect(crypt_sha512.decrypt(key, encrypted)).to eq 'secret'
|
76
|
-
expect{ crypt_full.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
77
|
-
|
78
|
-
encrypted = crypt_cbc.encrypt(key, 'secret')
|
79
|
-
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)
|
80
|
-
expect(crypt_cbc.decrypt(key, encrypted)).to eq 'secret'
|
81
|
-
expect{ crypt_mixed.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
82
|
-
|
83
|
-
encrypted = crypt_mixed.encrypt(key, 'secret')
|
84
|
-
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)
|
85
|
-
expect(crypt_mixed.decrypt(key, encrypted)).to eq 'secret'
|
86
|
-
expect{ crypt_full.decrypt(key, encrypted) }.to raise_error(OpenSSL::PKey::ECError)
|
66
|
+
it '#to_s' do
|
67
|
+
[
|
68
|
+
[ECIES::Crypt.new, "KDF-SHA256_HMAC-SHA-256-128_AES-256-CTR"],
|
69
|
+
[ECIES::Crypt.new(mac_length: :full), "KDF-SHA256_HMAC-SHA-256-256_AES-256-CTR"],
|
70
|
+
[ECIES::Crypt.new(digest: 'sha512'), "KDF-SHA512_HMAC-SHA-512-256_AES-256-CTR"],
|
71
|
+
[ECIES::Crypt.new(mac_digest: 'sha512'), "KDF-SHA256_HMAC-SHA-512-256_AES-256-CTR"],
|
72
|
+
[ECIES::Crypt.new(mac_digest: 'sha512', kdf_digest: 'sha224'), "KDF-SHA224_HMAC-SHA-512-256_AES-256-CTR"],
|
73
|
+
[ECIES::Crypt.new(cipher: 'aes-128-cbc'), "KDF-SHA256_HMAC-SHA-256-128_AES-128-CBC"],
|
74
|
+
].each do |crypt, expected_value|
|
75
|
+
expect(crypt.to_s).to eq expected_value
|
76
|
+
end
|
87
77
|
end
|
88
78
|
|
89
79
|
it 'Raises on unknown cipher or digest' do
|
90
|
-
key = OpenSSL::PKey::EC.
|
80
|
+
key = OpenSSL::PKey::EC.generate('secp256k1')
|
91
81
|
|
92
82
|
expect{ ECIES::Crypt.new(digest: 'foo') }.to raise_error(RuntimeError)
|
93
83
|
expect{ ECIES::Crypt.new(digest: 'md5') }.to raise_error(RuntimeError)
|
@@ -103,10 +93,8 @@ describe ECIES::Crypt do
|
|
103
93
|
end
|
104
94
|
end
|
105
95
|
|
106
|
-
|
107
|
-
|
108
96
|
describe '#kdf' do
|
109
|
-
it '
|
97
|
+
it 'derives keys correctly' do
|
110
98
|
sha256_test_vectors = [
|
111
99
|
# [shared_secret, shared_info, expected_key]
|
112
100
|
['96c05619d56c328ab95fe84b18264b08725b85e33fd34f08', '', '443024c3dae66b95e6f5670601558f71'],
|
@@ -120,16 +108,24 @@ describe ECIES::Crypt do
|
|
120
108
|
shared_info = [shared_info].pack('H*')
|
121
109
|
expected_key = [expected_key].pack('H*')
|
122
110
|
|
123
|
-
computed_key = ECIES::Crypt.new(kdf_shared_info: shared_info).kdf(shared_secret, expected_key.size)
|
124
|
-
|
111
|
+
computed_key = ECIES::Crypt.new(kdf_shared_info: shared_info).kdf(shared_secret, expected_key.size, '')
|
125
112
|
expect(computed_key).to eq expected_key
|
126
113
|
end
|
127
114
|
end
|
128
115
|
|
116
|
+
it 'concats kdf_shared_info with shared_info_suffix' do
|
117
|
+
shared_secret = ['22518b10e70f2a3f243810ae3254139efbee04aa57c7af7d'].pack('H*')
|
118
|
+
shared_info = ['75eef81aa3041e33'].pack('H*')
|
119
|
+
shared_info_suffix = ['b80971203d2c0c52'].pack('H*')
|
120
|
+
expected_key = ['c498af77161cc59f2962b9a713e2b215152d139766ce34a776df11866a69bf2e52a13d9c7c6fc878c50c5ea0bc7b00e0da2447cfd874f6cf92f30d0097111485500c90c3af8b487872d04685d14c8d1dc8d7fa08beb0ce0ababc11f0bd496269142d43525a78e5bc79a17f59676a5706dc54d54d4d1f0bd7e386128ec26afc21'].pack('H*')
|
121
|
+
|
122
|
+
computed_key = ECIES::Crypt.new(kdf_shared_info: shared_info).kdf(shared_secret, expected_key.size, shared_info_suffix)
|
123
|
+
expect(computed_key).to eq expected_key
|
124
|
+
end
|
125
|
+
|
129
126
|
it 'raises when size is invalid' do
|
130
|
-
expect{ ECIES::Crypt.new.kdf('a', -1) }.to raise_error(RuntimeError)
|
131
|
-
expect{ ECIES::Crypt.new.kdf('a', 32 * 2**32) }.to raise_error(RuntimeError)
|
127
|
+
expect{ ECIES::Crypt.new.kdf('a', -1, '') }.to raise_error(RuntimeError)
|
128
|
+
expect{ ECIES::Crypt.new.kdf('a', 32 * 2**32, '') }.to raise_error(RuntimeError)
|
132
129
|
end
|
133
130
|
end
|
134
|
-
|
135
131
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,36 +1,41 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ecies
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen McCarthy
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain:
|
11
11
|
- |
|
12
12
|
-----BEGIN CERTIFICATE-----
|
13
|
-
|
13
|
+
MIIEeDCCAuCgAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMRMwEQYDVQQDDApzam1j
|
14
14
|
Y2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZFgNj
|
15
|
-
|
15
|
+
b20wHhcNMjMwNjIwMjEwMTI2WhcNMjQwNjE5MjEwMTI2WjBBMRMwEQYDVQQDDApz
|
16
16
|
am1jY2FydGh5MRUwEwYKCZImiZPyLGQBGRYFZ21haWwxEzARBgoJkiaJk/IsZAEZ
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
17
|
+
FgNjb20wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDOGmdomo9zQxUD
|
18
|
+
PMEtDYtzY7Fnb/T/TVXAhKMO06Fw84eEMdU4yOUOMapsv6DinBMm0kDjseFkuqm+
|
19
|
+
BFYcU7AaGfKOuoPJQ23LuQOBXg5STw+pDKGdoq1th26M1v05Q9hCH4HVlGIiNPzx
|
20
|
+
J+0jFoH9qmiNhHxCD03702etb/ppJsCbBGzpMzofbgKQEHIKZNAkVi2RU2QLqXDt
|
21
|
+
A44wknz8NkiWahgN24cpLn/euz5G+J4nFgJ32YUBgA/mVXdeEOhxgMCPIbHIxiez
|
22
|
+
3c/yymARogxf6e9b3Pcwo1k63zTZYf9YHIM+Q5lKiOWOGQ1lUG3nanB9NhFdUkAk
|
23
|
+
PcuEzQUp5/pQmnoM/j+VpeXyvwiNLTzsxNRGfePSZU+oDpkEvlN1qaJXmHRpedWp
|
24
|
+
zOcUlVUpwFSWM1WWDGm4FCxoYH8GdYdHVXmUWWm/iPcfLCkXwNUXL+i38OXbVSp2
|
25
|
+
aqaXrRtkwZsfmE4PCHVUJyHFeVXLqvgK3DLVPfCGk6ty4X7wuXcCAwEAAaN7MHkw
|
26
|
+
CQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFELj2NEPT2elfT1kefaM
|
27
|
+
tcBz+k6QMB8GA1UdEQQYMBaBFHNqbWNjYXJ0aHlAZ21haWwuY29tMB8GA1UdEgQY
|
28
|
+
MBaBFHNqbWNjYXJ0aHlAZ21haWwuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQBvbFzT
|
29
|
+
zHTVmJ2J3VvWf1J4HFTkLDtRi9lFLtPqvsNczd+FeO3s/pI9/gr56mBgpZ/vLypN
|
30
|
+
9CBkk4wR4GLifl8+xuDtVkYYh3Ru9S3ru8iu225aOIfg6MpKH7x+IsShxm7jdEva
|
31
|
+
AnqMQ47KEOdRetbBMIHeWtCSxyKhXSJsfoIh+fkQZlIM6+3l0ljTiRxo5Eb1JyEn
|
32
|
+
Wb+jyVwJugHuGPb2tB9dx+khKgrv0YQ0lxsIaqSYRT5pb3xZuJNoxj00IsZGOx0J
|
33
|
+
s5SzopHnmHWPS0h9CVcTa9QgEz5MbCk1X2rWhmUkGR0pzHdeq6vvWr0pxhpiWJdA
|
34
|
+
eaD1Uldg06Zi31RbAARghTuRKzg+SA9/DmglkO479GuEhZvGA0P2fBvKTaVAZR+4
|
35
|
+
Lb3ESftZmM+MReXZS5FfAeYJBG5+R/LDyMiuQIz6WLYcPpfHpSX873IXAcNYTCd4
|
36
|
+
RVHpiTyCFYjchqoOvSC7rHgf1HpEQbuXsl9RKWhSQOyTGlUGbCCGFDx10Dg=
|
32
37
|
-----END CERTIFICATE-----
|
33
|
-
date:
|
38
|
+
date: 2023-06-20 00:00:00.000000000 Z
|
34
39
|
dependencies:
|
35
40
|
- !ruby/object:Gem::Dependency
|
36
41
|
name: bundler
|
@@ -38,28 +43,28 @@ dependencies:
|
|
38
43
|
requirements:
|
39
44
|
- - "~>"
|
40
45
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
46
|
+
version: '2'
|
42
47
|
type: :development
|
43
48
|
prerelease: false
|
44
49
|
version_requirements: !ruby/object:Gem::Requirement
|
45
50
|
requirements:
|
46
51
|
- - "~>"
|
47
52
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
53
|
+
version: '2'
|
49
54
|
- !ruby/object:Gem::Dependency
|
50
55
|
name: rake
|
51
56
|
requirement: !ruby/object:Gem::Requirement
|
52
57
|
requirements:
|
53
58
|
- - "~>"
|
54
59
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
60
|
+
version: '13'
|
56
61
|
type: :development
|
57
62
|
prerelease: false
|
58
63
|
version_requirements: !ruby/object:Gem::Requirement
|
59
64
|
requirements:
|
60
65
|
- - "~>"
|
61
66
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
67
|
+
version: '13'
|
63
68
|
- !ruby/object:Gem::Dependency
|
64
69
|
name: rspec
|
65
70
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,7 +163,7 @@ homepage: https://github.com/jamoes/ecies
|
|
158
163
|
licenses:
|
159
164
|
- MIT
|
160
165
|
metadata: {}
|
161
|
-
post_install_message:
|
166
|
+
post_install_message:
|
162
167
|
rdoc_options: []
|
163
168
|
require_paths:
|
164
169
|
- lib
|
@@ -173,9 +178,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
178
|
- !ruby/object:Gem::Version
|
174
179
|
version: '0'
|
175
180
|
requirements: []
|
176
|
-
|
177
|
-
|
178
|
-
signing_key:
|
181
|
+
rubygems_version: 3.3.25
|
182
|
+
signing_key:
|
179
183
|
specification_version: 4
|
180
184
|
summary: Elliptical Curve Integrated Encryption System (ECIES), as specified by SEC
|
181
185
|
1 - Ver. 2.0
|
metadata.gz.sig
CHANGED
Binary file
|