Skip to content

Commit 8c0c9ee

Browse files
committed
- Add support for AES-GCM key wrap algorithms (A128GCMKW, A192GCMKW, A256GCMKW)
- Add new internal architecture with `Base`, `Validator`, `Header`, and `NameResolver` classes - Improve code organization with refactored module structure - RuboCop compliance improvements - Deprecated `JWE.check_params`, `JWE.check_alg`, `JWE.check_enc`, `JWE.check_zip`, `JWE.check_key` (use `JWE::Validator` instead) - Deprecated `JWE.param_to_class_name` (use `JWE::NameResolver` instead) - Deprecated internal methods `JWE.apply_zip`, `JWE.generate_header`, `JWE.generate_serialization`
1 parent 6a2e506 commit 8c0c9ee

35 files changed

+771
-41
lines changed

.rubocop.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,23 @@ Style/PercentLiteralDelimiters:
1818

1919
Naming/MethodParameterName:
2020
AllowedNames: ["iv", "b", "j", "a", "r", "t"]
21-
21+
22+
Metrics/AbcSize:
23+
Exclude:
24+
- 'lib/jwe.rb'
25+
26+
Metrics/ParameterLists:
27+
Exclude:
28+
- 'lib/jwe.rb'
29+
30+
Metrics/MethodLength:
31+
Exclude:
32+
- 'lib/jwe.rb'
33+
34+
Lint/MissingSuper:
35+
Exclude:
36+
- 'lib/jwe/alg/dir.rb'
37+
- 'lib/jwe/alg/rsa15.rb'
38+
- 'lib/jwe/alg/rsa_oaep.rb'
39+
- 'lib/jwe/alg/rsa_oaep_256.rb'
40+

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# Changelog
22

3+
## Add support for AES-GCM key wrap algorithms(https://github.com/jwt/ruby-jwe/pull/38) (2025-10-08)
4+
5+
**Features:**
6+
- Add support for AES-GCM key wrap algorithms (A128GCMKW, A192GCMKW, A256GCMKW)
7+
- Add new internal architecture with `Base`, `Validator`, `Header`, and `NameResolver` classes
8+
- Improve code organization with refactored module structure
9+
- RuboCop compliance improvements
10+
11+
**Deprecations:**
12+
13+
- Deprecated `JWE.check_params`, `JWE.check_alg`, `JWE.check_enc`, `JWE.check_zip`, `JWE.check_key`
14+
(use `JWE::Validator` instead)
15+
- Deprecated `JWE.param_to_class_name` (use `JWE::NameResolver` instead)
16+
- Deprecated internal methods `JWE.apply_zip`, `JWE.generate_header`, `JWE.generate_serialization`
17+
18+
**Notes:**
19+
20+
All deprecated methods remain functional with deprecation warnings for backward compatibility.
21+
322
## [v1.1.1](https://github.com/jwt/ruby-jwe/tree/v1.1.1) (2025-08-07)
423

524
[Full Changelog](https://github.com/jwt/ruby-jwe/compare/v1.1.0...v1.1.1)

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ source 'https://rubygems.org'
44

55
gemspec
66

7+
gem 'pry'
78
gem 'rake'
89
gem 'rspec'
910
gem 'rubocop'

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,21 @@ encrypted = JWE.encrypt(payload, key, copyright: 'This is my stuff! All rights r
8686
puts encrypted
8787
```
8888

89+
This example uses AES-GCM key wrap algorithm (A128GCMKW, A192GCMKW, or A256GCMKW).
90+
91+
```ruby
92+
require 'jwe'
93+
94+
key = SecureRandom.random_bytes(16) # 16 bytes for A128GCMKW, 24 for A192GCMKW, 32 for A256GCMKW
95+
payload = "The quick brown fox jumps over the lazy dog."
96+
97+
encrypted = JWE.encrypt(payload, key, alg: 'A128GCMKW')
98+
puts encrypted
99+
100+
plaintext = JWE.decrypt(encrypted, key)
101+
puts plaintext #"The quick brown fox jumps over the lazy dog."
102+
```
103+
89104
## Available Algorithms
90105

91106
The RFC 7518 JSON Web Algorithms (JWA) spec defines the algorithms for [encryption](https://tools.ietf.org/html/rfc7518#section-5.1)
@@ -105,9 +120,9 @@ Key management:
105120
* ~~ECDH-ES+A128KW~~
106121
* ~~ECDH-ES+A192KW~~
107122
* ~~ECDH-ES+A256KW~~
108-
* ~~A128GCMKW~~
109-
* ~~A192GCMKW~~
110-
* ~~A256GCMKW~~
123+
* A128GCMKW
124+
* A192GCMKW
125+
* A256GCMKW
111126
* ~~PBES2-HS256+A128KW~~
112127
* ~~PBES2-HS384+A192KW~~
113128
* ~~PBES2-HS512+A256KW~~

lib/jwe.rb

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77

88
require 'jwe/base64'
99
require 'jwe/serialization/compact'
10+
require 'jwe/name_resolver'
1011
require 'jwe/alg'
1112
require 'jwe/enc'
1213
require 'jwe/zip'
14+
require 'jwe/validator'
15+
require 'jwe/header'
1316

1417
# A ruby implementation of the RFC 7516 JSON Web Encryption (JWE) standard.
1518
module JWE
@@ -18,68 +21,90 @@ class NotImplementedError < RuntimeError; end
1821
class BadCEK < RuntimeError; end
1922
class InvalidData < RuntimeError; end
2023

21-
VALID_ALG = ['RSA1_5', 'RSA-OAEP', 'RSA-OAEP-256', 'A128KW', 'A192KW', 'A256KW', 'dir', 'ECDH-ES', 'ECDH-ES+A128KW', 'ECDH-ES+A192KW', 'ECDH-ES+A256KW', 'A128GCMKW', 'A192GCMKW', 'A256GCMKW', 'PBES2-HS256+A128KW', 'PBES2-HS384+A192KW', 'PBES2-HS512+A256KW'].freeze
22-
VALID_ENC = %w[A128CBC-HS256 A192CBC-HS384 A256CBC-HS512 A128GCM A192GCM A256GCM].freeze
23-
VALID_ZIP = ['DEF'].freeze
24-
2524
class << self
26-
def encrypt(payload, key, alg: 'RSA-OAEP', enc: 'A128GCM', **more_headers)
27-
header = generate_header(alg, enc, more_headers)
28-
check_params(header, key)
25+
def encrypt(payload, key, alg: 'RSA-OAEP', enc: 'A128GCM', zip: nil, **more_headers)
26+
Validator.new.check_params(alg, enc, zip, key)
27+
payload = Zip.for(zip).new.compress(payload) if zip
28+
29+
enc_cipher = Enc.for(enc)
30+
enc_cipher.cek = key if alg == 'dir'
2931

30-
payload = apply_zip(header, payload, :compress)
32+
alg_cipher = Alg.for(alg).new(key)
33+
encrypted_cek = alg_cipher.encrypt(enc_cipher.cek)
3134

32-
cipher = Enc.for(enc)
33-
cipher.cek = key if alg == 'dir'
35+
header = Header.new.generate_header(alg_cipher, enc_cipher, zip, more_headers)
3436

35-
json_hdr = header.to_json
36-
ciphertext = cipher.encrypt(payload, Base64.jwe_encode(json_hdr))
37+
ciphertext = enc_cipher.encrypt(payload, Base64.jwe_encode(header))
3738

38-
generate_serialization(json_hdr, Alg.encrypt_cek(alg, key, cipher.cek), ciphertext, cipher)
39+
Serialization::Compact.encode(header, encrypted_cek, enc_cipher.iv, ciphertext, enc_cipher.tag)
3940
end
4041

4142
def decrypt(payload, key)
4243
header, enc_key, iv, ciphertext, tag = Serialization::Compact.decode(payload)
4344
header = JSON.parse(header)
44-
check_params(header, key)
45+
alg, enc, zip = header.values_at('alg', 'enc', 'zip')
46+
47+
Validator.new.check_params(alg, enc, zip, key)
48+
49+
alg_cipher = Alg.for(alg).new(key)
50+
if alg_cipher.need_additional_header_parameters?
51+
alg_cipher.iv = Base64.jwe_decode(header['iv'])
52+
alg_cipher.tag = Base64.jwe_decode(header['tag'])
53+
end
54+
55+
cek = alg_cipher.decrypt(enc_key)
56+
enc_cipher = Enc.for(enc, cek, iv, tag)
4557

46-
cek = Alg.decrypt_cek(header['alg'], key, enc_key)
47-
cipher = Enc.for(header['enc'], cek, iv, tag)
58+
plaintext = enc_cipher.decrypt(ciphertext, payload.split('.').first)
4859

49-
plaintext = cipher.decrypt(ciphertext, payload.split('.').first)
60+
return plaintext unless zip
5061

51-
apply_zip(header, plaintext, :decompress)
62+
Zip.for(zip).new.decompress(plaintext)
5263
end
5364

65+
# @deprecated Use Validator.new.check_params instead
5466
def check_params(header, key)
67+
warn '[DEPRECATION] `JWE.check_params` is deprecated. Use `JWE::Validator.new.check_params` instead.'
5568
check_alg(header[:alg] || header['alg'])
5669
check_enc(header[:enc] || header['enc'])
5770
check_zip(header[:zip] || header['zip'])
5871
check_key(key)
5972
end
6073

74+
# @deprecated Use Validator.new.check_params instead
6175
def check_alg(alg)
62-
raise ArgumentError.new("\"#{alg}\" is not a valid alg method") unless VALID_ALG.include?(alg)
76+
warn '[DEPRECATION] `JWE.check_alg` is deprecated. Please validate parameters manually.'
77+
raise ArgumentError.new("\"#{alg}\" is not a valid alg method") unless Validator::VALID_ALG.include?(alg)
6378
end
6479

80+
# @deprecated Use Validator.new.check_params instead
6581
def check_enc(enc)
66-
raise ArgumentError.new("\"#{enc}\" is not a valid enc method") unless VALID_ENC.include?(enc)
82+
warn '[DEPRECATION] `JWE.check_enc` is deprecated. Please validate parameters manually.'
83+
raise ArgumentError.new("\"#{enc}\" is not a valid enc method") unless Validator::VALID_ENC.include?(enc)
6784
end
6885

86+
# @deprecated Use Validator.new.check_params instead
6987
def check_zip(zip)
70-
raise ArgumentError.new("\"#{zip}\" is not a valid zip method") unless zip.nil? || zip == '' || VALID_ZIP.include?(zip)
88+
warn '[DEPRECATION] `JWE.check_zip` is deprecated. Please validate parameters manually.'
89+
raise ArgumentError.new("\"#{zip}\" is not a valid zip method") unless zip.nil? || zip == '' || Validator::VALID_ZIP.include?(zip)
7190
end
7291

92+
# @deprecated Use Validator.new.check_params instead
7393
def check_key(key)
94+
warn '[DEPRECATION] `JWE.check_key` is deprecated. Please validate parameters manually.'
7495
raise ArgumentError.new('The key must not be nil or blank') if key.nil? || (key.is_a?(String) && key.strip == '')
7596
end
7697

98+
# @deprecated Use NameResolver#param_to_class_name instead
7799
def param_to_class_name(param)
100+
warn '[DEPRECATION] `JWE.param_to_class_name` is deprecated. Use `JWE::NameResolver#param_to_class_name` instead.'
78101
klass = param.gsub(/[-+]/, '_').downcase.sub(/^[a-z\d]*/) { ::Regexp.last_match(0).capitalize }
79102
klass.gsub(/_([a-z\d]*)/i) { Regexp.last_match(1).capitalize }
80103
end
81104

105+
# @deprecated Internal method, do not use
82106
def apply_zip(header, data, direction)
107+
warn '[DEPRECATION] `JWE.apply_zip` is deprecated. This is an internal method and should not be used externally.'
83108
zip = header[:zip] || header['zip']
84109
if zip
85110
Zip.for(zip).new.send(direction, data)
@@ -88,13 +113,17 @@ def apply_zip(header, data, direction)
88113
end
89114
end
90115

116+
# @deprecated Use Header.new.generate_header instead
91117
def generate_header(alg, enc, more)
118+
warn '[DEPRECATION] `JWE.generate_header` is deprecated. This is an internal method and should not be used externally.'
92119
header = { alg: alg, enc: enc }.merge(more)
93120
header.delete(:zip) if header[:zip] == ''
94121
header
95122
end
96123

124+
# @deprecated Use Serialization::Compact.encode instead
97125
def generate_serialization(hdr, cek, content, cipher)
126+
warn '[DEPRECATION] `JWE.generate_serialization` is deprecated. Use `JWE::Serialization::Compact.encode` instead.'
98127
Serialization::Compact.encode(hdr, cek, cipher.iv, content, cipher.tag)
99128
end
100129
end

lib/jwe/alg.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# frozen_string_literal: true
22

3+
require 'jwe/alg/base'
34
require 'jwe/alg/a128_kw'
45
require 'jwe/alg/a192_kw'
56
require 'jwe/alg/a256_kw'
7+
require 'jwe/alg/a128gcmkw'
8+
require 'jwe/alg/a192gcmkw'
9+
require 'jwe/alg/a256gcmkw'
610
require 'jwe/alg/dir'
711
require 'jwe/alg/rsa_oaep'
812
require 'jwe/alg/rsa_oaep_256' if OpenSSL::VERSION >= '3.0'
@@ -11,8 +15,10 @@
1115
module JWE
1216
# Key encryption algorithms namespace
1317
module Alg
18+
extend JWE::NameResolver
19+
1420
def self.for(alg)
15-
const_get(JWE.param_to_class_name(alg))
21+
const_get(param_to_class_name(alg))
1622
rescue NameError
1723
raise NotImplementedError.new("Unsupported alg type: #{alg}")
1824
end

lib/jwe/alg/a128_kw.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
module JWE
66
module Alg
77
# AES-128 Key Wrapping algorithm
8-
class A128kw
8+
class A128kw < Base
99
include AesKw
1010

1111
def cipher_name

lib/jwe/alg/a128gcmkw.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
require 'jwe/alg/aes_gcm'
4+
5+
module JWE
6+
module Alg
7+
# AES-128 key wrap with GCM algorithm
8+
class A128gcmkw < Base
9+
include AesGcm
10+
11+
def initialize(key, iv = nil)
12+
super
13+
end
14+
15+
private
16+
17+
def key_length
18+
16
19+
end
20+
21+
def cipher_name
22+
'aes-128-gcm'
23+
end
24+
25+
def required_additional_header_parameters?
26+
true
27+
end
28+
end
29+
end
30+
end

lib/jwe/alg/a192_kw.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
module JWE
66
module Alg
77
# AES-192 Key Wrapping algorithm
8-
class A192kw
8+
class A192kw < Base
99
include AesKw
1010

1111
def cipher_name

lib/jwe/alg/a192gcmkw.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
require 'jwe/alg/aes_gcm'
4+
5+
module JWE
6+
module Alg
7+
# AES-192 key wrap with GCM algorithm
8+
class A192gcmkw < Base
9+
include AesGcm
10+
11+
def initialize(key, iv = nil)
12+
super
13+
end
14+
15+
private
16+
17+
def key_length
18+
24
19+
end
20+
21+
def cipher_name
22+
'aes-192-gcm'
23+
end
24+
25+
def required_additional_header_parameters?
26+
true
27+
end
28+
end
29+
end
30+
end

0 commit comments

Comments
 (0)