xaes_gcm 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8c94c5ea1a1fb9d1ee9045139a86f53f5c8b7ebf89edc7521136a4065e4e32b5
4
+ data.tar.gz: 4fd2f8e6b2eccb6de066608818177084ad4f5a9c289b9448842224eaa39ad9d7
5
+ SHA512:
6
+ metadata.gz: c8a2dc2bc6fb43f227b9fb606cc05ff8caa0f90a160b5e704a19e65bcd959bc2496fa49ff1959b5903fe22087087cc816710ad914a53b498e9c57346e48ac109
7
+ data.tar.gz: c881c39b0475962a1ee080dfb23c3db5fbb64713ac6fb74225c816fdcac89623789c97ff6d5fa2f8bc0064f468fc6b9a1d2975355cbe9fc9d600069e604b5c38
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2020
2
+ The C2SP Authors. All rights reserved.
3
+ Copyright (c) 2026 Sorah Fukumori.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions
7
+ are met:
8
+ 1. Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY The C2SP Authors ``AS IS'' AND
12
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
13
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
14
+ ARE DISCLAIMED. IN NO EVENT SHALL The C2SP Authors BE LIABLE
15
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
17
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
19
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
20
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
21
+ SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # xaes_gcm
2
+
3
+ Ruby implementation of [XAES-256-GCM](https://c2sp.org/XAES-256-GCM), an extended-nonce AEAD built on AES-256-GCM.
4
+
5
+ XAES-256-GCM uses 192-bit (24-byte) nonces instead of AES-256-GCM's 96-bit nonces. The longer nonce makes it safe to generate nonces randomly for a practically unlimited number of messages, without risking nonce reuse.
6
+
7
+ This gem implements the key and nonce derivation step of XAES-256-GCM. It derives a standard AES-256-GCM key and nonce from the extended inputs, which you then use with Ruby's built-in `OpenSSL::Cipher` for encryption and decryption.
8
+
9
+ ## Security Warning
10
+
11
+ > [!CAUTION]
12
+ > No security audits of this gem have ever been performed. USE AT YOUR OWN RISK!
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ bundle add xaes_gcm
18
+ ```
19
+
20
+ Or install directly:
21
+
22
+ ```bash
23
+ gem install xaes_gcm
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```ruby
29
+ require "xaes_gcm"
30
+
31
+ # Create a reusable key (precomputes the AES key schedule and subkey)
32
+ key = OpenSSL::Random.random_bytes(XaesGcm::KEY_SIZE) # 32 bytes
33
+ xkey = XaesGcm::Key.new(key)
34
+
35
+ # Encrypt (generates a random 192-bit nonce by default)
36
+ cipher = OpenSSL::Cipher.new("aes-256-gcm")
37
+ cipher.encrypt
38
+ nonce = xkey.apply(cipher)
39
+ cipher.auth_data = "optional authenticated data"
40
+ ciphertext = cipher.update(plaintext) + cipher.final
41
+ tag = cipher.auth_tag
42
+
43
+ # Decrypt (pass the same nonce used for encryption)
44
+ decipher = OpenSSL::Cipher.new("aes-256-gcm")
45
+ decipher.decrypt
46
+ xkey.apply(decipher, nonce:)
47
+ decipher.auth_tag = tag
48
+ decipher.auth_data = "optional authenticated data"
49
+ plaintext = decipher.update(ciphertext) + decipher.final
50
+ ```
51
+
52
+ `Key#apply` generates a random nonce, derives the AES-256-GCM key and nonce, sets them on the cipher, and returns the 24-byte nonce. Pass the same nonce back for decryption. `Key` precomputes the AES key schedule and subkey, so reuse the same instance when encrypting multiple messages under the same key.
53
+
54
+ ## Alternative gems
55
+
56
+ There's alternative gem `xaes_256_gcm`: https://github.com/vcsjones/xaes-256-gcm-ruby
57
+
58
+ Key differences:
59
+
60
+ - Smaller code footprint
61
+ - Leaving OpenSSL::Cipher setup to the user
62
+ - Accumulated randomized test vectors are included in the test suite
63
+
64
+ ## License
65
+
66
+ The gem is available as open source under the terms of the [BSD 1-Clause License](https://opensource.org/licenses/BSD-1-Clause).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XaesGcm
4
+ class Key
5
+ def initialize(key)
6
+ raise ArgumentError, "key must be #{KEY_SIZE} bytes" unless key.bytesize == KEY_SIZE
7
+
8
+ @cipher = OpenSSL::Cipher.new('aes-256-ecb')
9
+ @cipher.encrypt
10
+ @cipher.padding = 0
11
+ @cipher.key = key
12
+
13
+ # L = AES-256-ECB_K(0^128)
14
+ l = @cipher.update("\x00" * 16) + @cipher.final
15
+
16
+ # K1: shift L left by 1 bit, XOR last byte with 0x87 if MSB was set
17
+ msb = l.getbyte(0) >> 7
18
+ k1_bytes = Array.new(16) do |i|
19
+ next_bit = (i < 15) ? (l.getbyte(i + 1) >> 7) : 0
20
+ ((l.getbyte(i) << 1) | next_bit) & 0b11111111
21
+ end
22
+ k1_bytes[-1] ^= 0b10000111 & -(msb & 1)
23
+ @k1 = k1_bytes.pack('C*')
24
+ @cipher.freeze
25
+ end
26
+
27
+ if HAVE_INSTANCE_VARIABLES_TO_INSPECT
28
+ def instance_variables_to_inspect = []
29
+ else
30
+ def inspect
31
+ "#<#{self.class}>"
32
+ end
33
+ alias to_s inspect
34
+ end
35
+
36
+ def enable_hazmat!
37
+ @enable_hazmat = true
38
+ self
39
+ end
40
+
41
+ def apply(cipher, nonce: OpenSSL::Random.random_bytes(NONCE_SIZE))
42
+ raise ArgumentError, "cipher must be AES-256-GCM" unless cipher.name == "AES-256-GCM"
43
+
44
+ dk = derive_key_raw(nonce:)
45
+ cipher.key = dk.key
46
+ cipher.iv = dk.nonce
47
+ nonce
48
+ end
49
+
50
+ def derive_key(nonce:)
51
+ raise RuntimeError, "derive_key is a hazmat API that exposes raw key material; call enable_hazmat! on the Key instance to use it directly" unless @enable_hazmat
52
+ derive_key_raw(nonce:)
53
+ end
54
+
55
+ private
56
+
57
+ def derive_key_raw(nonce:)
58
+ raise ArgumentError, "nonce must be #{NONCE_SIZE} bytes" unless nonce.bytesize == NONCE_SIZE
59
+
60
+ n12 = nonce.byteslice(0, 12)
61
+
62
+ m1 = "\x00\x01X\x00".b + n12
63
+ m2 = "\x00\x02X\x00".b + n12
64
+
65
+ m1_xored = xor_blocks(m1, @k1)
66
+ m2_xored = xor_blocks(m2, @k1)
67
+
68
+ cipher = @cipher.dup
69
+ derived = cipher.update(m1_xored + m2_xored)
70
+
71
+ DerivedKey.new(key: derived, nonce: nonce.byteslice(12, 12))
72
+ end
73
+
74
+ def xor_blocks(a, b)
75
+ a_bytes = a.unpack('C*')
76
+ b_bytes = b.unpack('C*')
77
+ a_bytes.length.times { |i| a_bytes[i] ^= b_bytes[i] }
78
+ a_bytes.pack('C*')
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XaesGcm
4
+ VERSION = "0.1.1"
5
+ end
data/lib/xaes_gcm.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require_relative "xaes_gcm/version"
5
+
6
+ module XaesGcm
7
+ class Error < StandardError; end
8
+
9
+ KEY_SIZE = 32
10
+ NONCE_SIZE = 24
11
+
12
+ # Detect instance_variables_to_inspect support (Ruby feature #13555)
13
+ HAVE_INSTANCE_VARIABLES_TO_INSPECT = begin
14
+ klass = Class.new do
15
+ def initialize = @secret = true
16
+ def instance_variables_to_inspect = []
17
+ end
18
+ !klass.new.inspect.include?("@secret")
19
+ end
20
+
21
+ DerivedKey = Data.define(:key, :nonce) do
22
+ # Data.define#inspect doesn't use instance_variables_to_inspect
23
+ def inspect
24
+ "#<#{self.class}>"
25
+ end
26
+ alias to_s inspect
27
+ end
28
+ end
29
+
30
+ require_relative "xaes_gcm/key"
data/sig/xaes_gcm.rbs ADDED
@@ -0,0 +1,28 @@
1
+ module XaesGcm
2
+ VERSION: String
3
+ KEY_SIZE: Integer
4
+ NONCE_SIZE: Integer
5
+
6
+ class Error < StandardError
7
+ end
8
+
9
+ class DerivedKey
10
+ attr_reader key: String
11
+ attr_reader nonce: String
12
+
13
+ def self.new: (key: String, nonce: String) -> DerivedKey
14
+ def self.[]: (key: String, nonce: String) -> DerivedKey
15
+ end
16
+
17
+ class Key
18
+ def initialize: (String key) -> void
19
+ def enable_hazmat!: () -> self
20
+ def apply: (OpenSSL::Cipher cipher, ?nonce: String) -> String
21
+ def derive_key: (nonce: String) -> DerivedKey
22
+
23
+ private
24
+
25
+ def derive_key_raw: (nonce: String) -> DerivedKey
26
+ def xor_blocks: (String a, String b) -> String
27
+ end
28
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xaes_gcm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Sorah Fukumori
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Ruby implementation of XAES-256-GCM (c2sp.org/XAES-256-GCM), an extended-nonce
13
+ AEAD built on AES-256-GCM. Derives standard AES-256-GCM keys and nonces from 256-bit
14
+ keys and 192-bit nonces.
15
+ email:
16
+ - sorah@ivry.jp
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE.txt
22
+ - README.md
23
+ - Rakefile
24
+ - lib/xaes_gcm.rb
25
+ - lib/xaes_gcm/key.rb
26
+ - lib/xaes_gcm/version.rb
27
+ - sig/xaes_gcm.rbs
28
+ homepage: https://github.com/sorah/xaes_gcm
29
+ licenses:
30
+ - BSD-1-Clause
31
+ metadata:
32
+ allowed_push_host: https://rubygems.org
33
+ homepage_uri: https://github.com/sorah/xaes_gcm
34
+ source_code_uri: https://github.com/sorah/xaes_gcm
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 3.2.0
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubygems_version: 3.6.9
50
+ specification_version: 4
51
+ summary: XAES-256-GCM extended-nonce AEAD key derivation
52
+ test_files: []