hpke 0.3.1 → 1.0.0.pre.rc1
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 +4 -4
- data/Gemfile.lock +3 -3
- data/README.md +33 -26
- data/lib/hpke/dhkem.rb +5 -2
- data/lib/hpke/hkdf.rb +9 -55
- data/lib/hpke/version.rb +1 -1
- data/lib/hpke.rb +67 -50
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af90c4d6df46848b41461a37d6026d06984109712489219d17d27fddf4abf1f5
|
4
|
+
data.tar.gz: eaac6a6353c644133d964131d4f2ed3d09e901e9b7c12863522252c9c80ad190
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a16070a91990a059c8bdcde394b7cd8b2644a3dcc6675833dfda59a7298380d37a24838026f65f25a3ac7cbb25012ec2d33986da58f8e564fd2b2c242113a0d
|
7
|
+
data.tar.gz: 279298ae5d3dd40645f9bc5254b9c88d961493fdf5c2f64280b69858a7202d21e52a3e63c0e68cf7611edea3f625bd1e2be9453f1cbb7a015df50104d8001b06
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
hpke (0.
|
5
|
-
openssl (~> 3.0.0)
|
4
|
+
hpke (1.0.0.pre.rc1)
|
5
|
+
openssl (~> 3.3.0, >= 3.0)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
diff-lcs (1.5.0)
|
11
|
-
openssl (3.0
|
11
|
+
openssl (3.3.0)
|
12
12
|
rake (13.0.6)
|
13
13
|
rspec (3.12.0)
|
14
14
|
rspec-core (~> 3.12.0)
|
data/README.md
CHANGED
@@ -6,11 +6,26 @@ Hybrid Public Key Encryption (HPKE; [RFC 9180](https://datatracker.ietf.org/doc/
|
|
6
6
|
|
7
7
|
## Note
|
8
8
|
|
9
|
-
This is
|
9
|
+
This is tested against test vectors supplied by the authors of the RFC, but is not formally audited for security. Please be aware of this when using in production.
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
### Breaking Changes in Version 1.0.0(-rc1)
|
12
|
+
|
13
|
+
Previously, the instantiation of HPKE instance took four arguments, the curve for DHKEM, the hash function for DHKEM's HKDF, the hash function for the HKDF of HPKE, and the AEAD algorithm:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
hpke = HPKE.new(:x25519, :sha256, :sha256, :aes_128_gcm)
|
17
|
+
```
|
18
|
+
|
19
|
+
From 1.0.0, the instantiation of the HPKE instance will be done by passing algorithm identifiers (specified in the RFC, in Section 7.1, 7.2, and 7.3):
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
hpke = HPKE.new(HPKE::DHKEM_X25519_HKDF_SHA256, HPKE::HKDF_SHA256, HPKE::AES_128_GCM)
|
23
|
+
|
24
|
+
# also equivalent to:
|
25
|
+
hpke = HPKE.new(0x0020, 0x0001, 0x0001)
|
26
|
+
```
|
27
|
+
|
28
|
+
The name of constants (and its values) are listed in the next section.
|
14
29
|
|
15
30
|
## Supported Features
|
16
31
|
|
@@ -22,20 +37,20 @@ Supports all modes, KEMs, AEAD functions in RFC 9180.
|
|
22
37
|
- Auth
|
23
38
|
- AuthPSK
|
24
39
|
- Key Encapsulation Mechanisms (KEMs)
|
25
|
-
- DHKEM(P-256, HKDF-SHA256)
|
26
|
-
- DHKEM(P-384, HKDF-SHA384)
|
27
|
-
- DHKEM(P-521, HKDF-SHA512)
|
28
|
-
- DHKEM(X25519, HKDF-SHA256)
|
29
|
-
- DHKEM(X448, HKDF-SHA512)
|
40
|
+
- DHKEM(P-256, HKDF-SHA256) `DHKEM_P256_HKDF_SHA256` (= `0x0010`)
|
41
|
+
- DHKEM(P-384, HKDF-SHA384) `DHKEM_P384_HKDF_SHA384` (= `0x0011`)
|
42
|
+
- DHKEM(P-521, HKDF-SHA512) `DHKEM_P521_HKDF_SHA512` (= `0x0012`)
|
43
|
+
- DHKEM(X25519, HKDF-SHA256) `DHKEM_X25519_HKDF_SHA256` (= `0x0020`)
|
44
|
+
- DHKEM(X448, HKDF-SHA512) `DHKEM_X448_HKDF_SHA512` (= `0x0021`)
|
30
45
|
- Key Derivation Functions (KDFs)
|
31
|
-
- HKDF-SHA256
|
32
|
-
- HKDF-SHA384
|
33
|
-
- HKDF-SHA512
|
46
|
+
- HKDF-SHA256 `HKDF_SHA256` (= `0x0001`)
|
47
|
+
- HKDF-SHA384 `HKDF_SHA384` (= `0x0002`)
|
48
|
+
- HKDF-SHA512 `HKDF_SHA512` (= `0x0003`)
|
34
49
|
- AEAD Functions
|
35
|
-
- AES-128-GCM
|
36
|
-
- AES-256-GCM
|
37
|
-
- ChaCha20-Poly1305
|
38
|
-
- Export Only
|
50
|
+
- AES-128-GCM `AES_128_GCM` (= `0x0001`)
|
51
|
+
- AES-256-GCM `AES_256_GCM` (= `0x0002`)
|
52
|
+
- ChaCha20-Poly1305 `CHACHA20_POLY1305` (= `0x0003`)
|
53
|
+
- Export Only `EXPORT_ONLY`(= `0xffff`)
|
39
54
|
|
40
55
|
## Supported Environments
|
41
56
|
|
@@ -65,8 +80,8 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
65
80
|
# fourth parameter specifies the AEAD function
|
66
81
|
|
67
82
|
# we will generate a different instance just for demonstration to show that nothing secret is stored in the HPKE suite instance
|
68
|
-
hpke_s = HPKE.new(
|
69
|
-
hpke_r = HPKE.new(
|
83
|
+
hpke_s = HPKE.new(HPKE::DHKEM_X25519_HKDF_SHA256, HPKE::HKDF_SHA256, HPKE::AES_128_GCM)
|
84
|
+
hpke_r = HPKE.new(HPKE::DHKEM_X25519_HKDF_SHA256, HPKE::HKDF_SHA256, HPKE::AES_128_GCM)
|
70
85
|
|
71
86
|
# get a OpenSSL::PKey::PKey instance by either generating a key or loading a key from a PEM
|
72
87
|
# see https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/PKey/PKey.html
|
@@ -99,14 +114,6 @@ ciphertext = context_s.seal('authentication_associated_data', 'plaintext')
|
|
99
114
|
context_r.open('authentication_associated_data', ciphertext)
|
100
115
|
```
|
101
116
|
|
102
|
-
- Curve names (parameter 1)
|
103
|
-
- `:p_256`, `:p_384`, `:p_521`, `:x25519`, `:x448`
|
104
|
-
- Note: `:p_256` corresponds to `prime256v1`, `:p_384` corresponds to `secp384r1`, and `:p_521` corresponds to `secp521r1` in OpenSSL
|
105
|
-
- Hash names (parameter 2 and 3)
|
106
|
-
- `:sha256`, `:sha384`, `:sha512`
|
107
|
-
- AEAD function names (parameter 4)
|
108
|
-
- `:aes_128_gcm`, `:aes_256_gcm`, `:chacha20_poly1305`, `:export_only`
|
109
|
-
|
110
117
|
## Development
|
111
118
|
|
112
119
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/hpke/dhkem.rb
CHANGED
@@ -6,8 +6,11 @@ require_relative 'util'
|
|
6
6
|
class HPKE::DHKEM
|
7
7
|
include HPKE::Util
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
|
9
|
+
def initialize(kdf_id)
|
10
|
+
# Currently all KDFs are HKDF so this works fine,
|
11
|
+
# but when other KDFs are added, this should be fixed
|
12
|
+
@hkdf = HPKE::HKDF.new(kdf_id)
|
13
|
+
raise Exception.new('KDF not compatible with DHKEM curve') unless @hkdf.n_h == self.n_secret
|
11
14
|
end
|
12
15
|
|
13
16
|
def encap(pk_r)
|
data/lib/hpke/hkdf.rb
CHANGED
@@ -6,32 +6,22 @@ class HPKE::HKDF
|
|
6
6
|
|
7
7
|
attr_reader :kdf_id
|
8
8
|
|
9
|
-
ALGORITHMS = {
|
10
|
-
sha256: {
|
11
|
-
name: 'SHA256',
|
12
|
-
kdf_id: 1
|
13
|
-
},
|
14
|
-
sha384: {
|
15
|
-
name: 'SHA384',
|
16
|
-
kdf_id: 2
|
17
|
-
},
|
18
|
-
sha512: {
|
19
|
-
name: 'SHA512',
|
20
|
-
kdf_id: 3
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
9
|
def n_h
|
25
10
|
@digest.digest_length
|
26
11
|
end
|
27
12
|
|
28
|
-
def initialize(
|
29
|
-
|
30
|
-
|
31
|
-
@
|
13
|
+
def initialize(kdf_id)
|
14
|
+
case kdf_id
|
15
|
+
when HPKE::HKDF_SHA256
|
16
|
+
@digest = OpenSSL::Digest.new('SHA256')
|
17
|
+
when HPKE::HKDF_SHA384
|
18
|
+
@digest = OpenSSL::Digest.new('SHA384')
|
19
|
+
when HPKE::HKDF_SHA512
|
20
|
+
@digest = OpenSSL::Digest.new('SHA512')
|
32
21
|
else
|
33
22
|
raise Exception.new('Unknown hash algorithm')
|
34
23
|
end
|
24
|
+
@kdf_id = kdf_id
|
35
25
|
end
|
36
26
|
|
37
27
|
def hmac(key, data)
|
@@ -62,39 +52,3 @@ class HPKE::HKDF
|
|
62
52
|
expand(prk, labeled_info, l)
|
63
53
|
end
|
64
54
|
end
|
65
|
-
|
66
|
-
class HPKE::HKDF::HMAC_SHA256 < HPKE::HKDF
|
67
|
-
private
|
68
|
-
|
69
|
-
def digest_algorithm
|
70
|
-
'SHA256'
|
71
|
-
end
|
72
|
-
|
73
|
-
def kdf_id
|
74
|
-
1
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
class HPKE::HKDF::HMAC_SHA384 < HPKE::HKDF
|
79
|
-
private
|
80
|
-
|
81
|
-
def digest_algorithm
|
82
|
-
'SHA384'
|
83
|
-
end
|
84
|
-
|
85
|
-
def kdf_id
|
86
|
-
2
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
class HPKE::HKDF::HMAC_SHA512 < HPKE::HKDF
|
91
|
-
private
|
92
|
-
|
93
|
-
def digest_algorithm
|
94
|
-
'SHA512'
|
95
|
-
end
|
96
|
-
|
97
|
-
def kdf_id
|
98
|
-
3
|
99
|
-
end
|
100
|
-
end
|
data/lib/hpke/version.rb
CHANGED
data/lib/hpke.rb
CHANGED
@@ -9,73 +9,90 @@ require_relative './hpke/util'
|
|
9
9
|
class HPKE
|
10
10
|
include HPKE::Util
|
11
11
|
|
12
|
-
attr_reader :kem, :hkdf, :aead_name, :n_k, :n_n, :n_t
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
attr_reader :kem, :hkdf, :aead_id, :aead_name, :n_k, :n_n, :n_t
|
13
|
+
|
14
|
+
# Algorithm Identifiers
|
15
|
+
# DHKEM
|
16
|
+
# RFC 9180, Section 7.1 Table 2
|
17
|
+
DHKEM_P256_HKDF_SHA256 = 0x0010
|
18
|
+
DHKEM_P384_HKDF_SHA384 = 0x0011
|
19
|
+
DHKEM_P521_HKDF_SHA512 = 0x0012
|
20
|
+
DHKEM_X25519_HKDF_SHA256 = 0x0020
|
21
|
+
DHKEM_X448_HKDF_SHA512 = 0x0021
|
22
|
+
AVAILABLE_KEM = [
|
23
|
+
DHKEM_P256_HKDF_SHA256, DHKEM_P384_HKDF_SHA384, DHKEM_P521_HKDF_SHA512, DHKEM_X25519_HKDF_SHA256, DHKEM_X448_HKDF_SHA512
|
24
|
+
]
|
25
|
+
|
26
|
+
# HKDF
|
27
|
+
# RFC 9180, Section 7.2, Table 3
|
28
|
+
HKDF_SHA256 = 0x0001
|
29
|
+
HKDF_SHA384 = 0x0002
|
30
|
+
HKDF_SHA512 = 0x0003
|
31
|
+
AVAILABLE_KDF = [
|
32
|
+
HKDF_SHA256, HKDF_SHA384, HKDF_SHA512
|
33
|
+
]
|
34
|
+
|
35
|
+
# AEAD
|
36
|
+
# RFC 9180, Section 7.3, Table 5
|
37
|
+
AES_128_GCM = 0x0001
|
38
|
+
AES_256_GCM = 0x0002
|
39
|
+
CHACHA20_POLY1305 = 0x0003
|
40
|
+
EXPORT_ONLY = 0xffff
|
41
|
+
AVAILABLE_AEAD = [
|
42
|
+
AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305, EXPORT_ONLY
|
43
|
+
]
|
20
44
|
CIPHERS = {
|
21
|
-
|
45
|
+
AES_128_GCM => {
|
22
46
|
name: 'aes-128-gcm',
|
23
|
-
aead_id: 0x0001,
|
24
47
|
n_k: 16,
|
25
48
|
n_n: 12,
|
26
49
|
n_t: 16
|
27
50
|
},
|
28
|
-
|
51
|
+
AES_256_GCM => {
|
29
52
|
name: 'aes-256-gcm',
|
30
|
-
aead_id: 0x0002,
|
31
53
|
n_k: 32,
|
32
54
|
n_n: 12,
|
33
55
|
n_t: 16
|
34
56
|
},
|
35
|
-
|
57
|
+
CHACHA20_POLY1305 => {
|
36
58
|
name: 'chacha20-poly1305',
|
37
|
-
aead_id: 0x0003,
|
38
59
|
n_k: 32,
|
39
60
|
n_n: 12,
|
40
61
|
n_t: 16
|
41
62
|
},
|
42
|
-
|
43
|
-
aead_id: 0xffff
|
63
|
+
EXPORT_ONLY => {
|
44
64
|
}
|
45
65
|
}
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
name: 'SHA384',
|
53
|
-
kdf_id: 2
|
54
|
-
},
|
55
|
-
sha512: {
|
56
|
-
name: 'SHA512',
|
57
|
-
kdf_id: 3
|
58
|
-
}
|
59
|
-
}
|
60
|
-
KEM_CURVES = {
|
61
|
-
p_256: DHKEM::EC::P_256,
|
62
|
-
p_384: DHKEM::EC::P_384,
|
63
|
-
p_521: DHKEM::EC::P_521,
|
64
|
-
x25519: DHKEM::X25519,
|
65
|
-
x448: DHKEM::X448
|
66
|
+
|
67
|
+
MODES = {
|
68
|
+
base: 0x00,
|
69
|
+
psk: 0x01,
|
70
|
+
auth: 0x02,
|
71
|
+
auth_psk: 0x03
|
66
72
|
}
|
67
73
|
|
68
|
-
def initialize(
|
69
|
-
raise Exception.new('Unsupported
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
74
|
+
def initialize(kem_id, kdf_id, aead_id)
|
75
|
+
raise Exception.new('Unsupported AEAD') unless AVAILABLE_AEAD.include?(aead_id)
|
76
|
+
|
77
|
+
@kem = case kem_id
|
78
|
+
when DHKEM_P256_HKDF_SHA256 then DHKEM::EC::P_256.new(HKDF_SHA256)
|
79
|
+
when DHKEM_P384_HKDF_SHA384 then DHKEM::EC::P_384.new(HKDF_SHA384)
|
80
|
+
when DHKEM_P521_HKDF_SHA512 then DHKEM::EC::P_521.new(HKDF_SHA512)
|
81
|
+
when DHKEM_X25519_HKDF_SHA256 then DHKEM::X25519.new(HKDF_SHA256)
|
82
|
+
when DHKEM_X448_HKDF_SHA512 then DHKEM::X448.new(HKDF_SHA512)
|
83
|
+
else raise Exception.new('Unsupported KEM')
|
84
|
+
end
|
85
|
+
@hkdf = case kdf_id
|
86
|
+
when HKDF_SHA256 then HKDF.new(HKDF_SHA256)
|
87
|
+
when HKDF_SHA384 then HKDF.new(HKDF_SHA384)
|
88
|
+
when HKDF_SHA512 then HKDF.new(HKDF_SHA512)
|
89
|
+
else raise Exception.new('Unsupported KDF')
|
90
|
+
end
|
91
|
+
@aead_id = aead_id
|
92
|
+
@aead_name = CIPHERS[aead_id][:name]
|
93
|
+
@n_k = CIPHERS[aead_id][:n_k]
|
94
|
+
@n_n = CIPHERS[aead_id][:n_n]
|
95
|
+
@n_t = CIPHERS[aead_id][:n_t]
|
79
96
|
end
|
80
97
|
|
81
98
|
# public facing APIs
|
@@ -221,7 +238,7 @@ class HPKE
|
|
221
238
|
|
222
239
|
secret = @hkdf.labeled_extract(shared_secret, 'secret', psk, suite_id)
|
223
240
|
|
224
|
-
unless @aead_id ==
|
241
|
+
unless @aead_id == EXPORT_ONLY
|
225
242
|
key = @hkdf.labeled_expand(secret, 'key', key_schedule_context, @n_k, suite_id)
|
226
243
|
base_nonce = @hkdf.labeled_expand(secret, 'base_nonce', key_schedule_context, @n_n, suite_id)
|
227
244
|
end
|
@@ -276,7 +293,7 @@ end
|
|
276
293
|
|
277
294
|
class HPKE::ContextS < HPKE::Context
|
278
295
|
def seal(aad, pt)
|
279
|
-
raise Exception.new('AEAD is export only') if @hpke.
|
296
|
+
raise Exception.new('AEAD is export only') if @hpke.aead_id == HPKE::EXPORT_ONLY
|
280
297
|
|
281
298
|
ct = @hpke.aead_encrypt(@key, compute_nonce(@sequence_number), aad, pt)
|
282
299
|
increment_seq
|
@@ -286,7 +303,7 @@ end
|
|
286
303
|
|
287
304
|
class HPKE::ContextR < HPKE::Context
|
288
305
|
def open(aad, ct)
|
289
|
-
raise Exception.new('AEAD is export only') if @hpke.
|
306
|
+
raise Exception.new('AEAD is export only') if @hpke.aead_id == HPKE::EXPORT_ONLY
|
290
307
|
|
291
308
|
pt = @hpke.aead_decrypt(@key, compute_nonce(@sequence_number), aad, ct)
|
292
309
|
# TODO: catch openerror then send out own openerror
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hpke
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryo Kajiwara
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: openssl
|
@@ -69,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '0'
|
71
71
|
requirements: []
|
72
|
-
rubygems_version: 3.6.
|
72
|
+
rubygems_version: 3.6.7
|
73
73
|
specification_version: 4
|
74
74
|
summary: Hybrid Public Key Encryption (HPKE; RFC 9180) on Ruby
|
75
75
|
test_files: []
|