hpke 0.3.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2428f5f16865e10e78a4e67bcf58081715899957789e841454b066f4a86d955
4
- data.tar.gz: 15b28f1c5bfdddb03a054fa65d6a0ccecc2d00af55bfdf49416f3db1459e51bf
3
+ metadata.gz: af90c4d6df46848b41461a37d6026d06984109712489219d17d27fddf4abf1f5
4
+ data.tar.gz: eaac6a6353c644133d964131d4f2ed3d09e901e9b7c12863522252c9c80ad190
5
5
  SHA512:
6
- metadata.gz: b443a11d1fa8913b29bfb2b8fb26dc30d37162acbb5528e5ccdd361f59992d34bc4660cd581cbca4c009804b65d76dfdf9fbb0de2ed5f9b993a141256a34cfde
7
- data.tar.gz: 0c40bccf4ba67f67f227ee4dab8b2adcc26ca1d08ef411e775684e0add0bc20395a635af80857378ea6854b26b59f819b077204df127772c496339f7f451504f
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.1.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.2)
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 still in very early development, so:
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
- - APIs are subject to change
12
- - Especially the instantation interface of KEM and HPKE suite
13
- - 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.
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(:x25519, :sha256, :sha256, :aes_128_gcm)
69
- hpke_r = HPKE.new(:x25519, :sha256, :sha256, :aes_128_gcm)
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/hpke.gemspec CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.require_paths = ["lib"]
30
30
 
31
31
  # Uncomment to register a new dependency of your gem
32
- spec.add_dependency "openssl", "~> 3.3.0"
32
+ spec.add_dependency "openssl", "~> 3.3.0", ">= 3.0"
33
33
 
34
34
  # For more information and examples about making a new gem, check out our
35
35
  # guide at: https://bundler.io/guides/creating_gem.html
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(hash_name)
10
- @hkdf = HPKE::HKDF.new(hash_name)
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(alg_name)
29
- if algorithm = ALGORITHMS[alg_name]
30
- @digest = OpenSSL::Digest.new(algorithm[:name])
31
- @kdf_id = algorithm[:kdf_id]
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class HPKE
4
- VERSION = "0.3.0"
4
+ VERSION = "1.0.0-rc1"
5
5
  end
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
- MODES = {
15
- base: 0x00,
16
- psk: 0x01,
17
- auth: 0x02,
18
- auth_psk: 0x03
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
- aes_128_gcm: {
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
- aes_256_gcm: {
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
- chacha20_poly1305: {
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
- export_only: {
43
- aead_id: 0xffff
63
+ EXPORT_ONLY => {
44
64
  }
45
65
  }
46
- HASHES = {
47
- sha256: {
48
- name: 'SHA256',
49
- kdf_id: 1
50
- },
51
- sha384: {
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(kem_curve_name, kem_hash, kdf_hash, aead_cipher)
69
- raise Exception.new('Unsupported KEM curve name') if KEM_CURVES[kem_curve_name].nil?
70
- raise Exception.new('Unsupported AEAD cipher name') if CIPHERS[aead_cipher].nil?
71
-
72
- @kem = KEM_CURVES[kem_curve_name].new(kem_hash)
73
- @hkdf = HKDF.new(kdf_hash)
74
- @aead_name = CIPHERS[aead_cipher][:name]
75
- @aead_id = CIPHERS[aead_cipher][:aead_id]
76
- @n_k = CIPHERS[aead_cipher][:n_k]
77
- @n_n = CIPHERS[aead_cipher][:n_n]
78
- @n_t = CIPHERS[aead_cipher][:n_t]
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 == CIPHERS[:export_only][: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.aead_name == :export_only
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.aead_name == :export_only
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.3.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: 2025-04-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: openssl
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: 3.3.0
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,6 +26,9 @@ dependencies:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
28
  version: 3.3.0
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: '3.0'
26
32
  description: Hybrid Public Key Encryption (HPKE; RFC 9180) on Ruby
27
33
  email:
28
34
  - sylph01@s01.ninja
@@ -63,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
69
  - !ruby/object:Gem::Version
64
70
  version: '0'
65
71
  requirements: []
66
- rubygems_version: 3.6.5
72
+ rubygems_version: 3.6.7
67
73
  specification_version: 4
68
74
  summary: Hybrid Public Key Encryption (HPKE; RFC 9180) on Ruby
69
75
  test_files: []