xaes_gcm 0.1.1 → 0.2.0

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: 8c94c5ea1a1fb9d1ee9045139a86f53f5c8b7ebf89edc7521136a4065e4e32b5
4
- data.tar.gz: 4fd2f8e6b2eccb6de066608818177084ad4f5a9c289b9448842224eaa39ad9d7
3
+ metadata.gz: 455b27ec1f586df020e3abfdaad8b77cf6d0813ecdd026d22b771c582190e90a
4
+ data.tar.gz: 36d8ef0d413ecac690aa35ee2803e95776e65bd5a537ad9c691c163b1fa35877
5
5
  SHA512:
6
- metadata.gz: c8a2dc2bc6fb43f227b9fb606cc05ff8caa0f90a160b5e704a19e65bcd959bc2496fa49ff1959b5903fe22087087cc816710ad914a53b498e9c57346e48ac109
7
- data.tar.gz: c881c39b0475962a1ee080dfb23c3db5fbb64713ac6fb74225c816fdcac89623789c97ff6d5fa2f8bc0064f468fc6b9a1d2975355cbe9fc9d600069e604b5c38
6
+ metadata.gz: c9da599d720066ed4a880fa318f5bf4993163eab861b9d83de96425d2c91ca7f64681b7bfc10f342308122b141cb8cf5a1ea401efc87ef737c3d609ad9a7de57
7
+ data.tar.gz: 6aae306dc14a251b25ae1542ce17238c18ea2a96a72aded1916de9b178c419eefb5eb8c2b9343958c5f7e682ff903d859639dc7aefcb80e4a14436f91d9dd64f
data/README.md CHANGED
@@ -29,13 +29,13 @@ gem install xaes_gcm
29
29
  require "xaes_gcm"
30
30
 
31
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)
32
+ raw_key = OpenSSL::Random.random_bytes(XaesGcm::Xaes256gcm::KEY_SIZE) # 32 bytes
33
+ key = XaesGcm.key(256, raw_key)
34
34
 
35
35
  # Encrypt (generates a random 192-bit nonce by default)
36
36
  cipher = OpenSSL::Cipher.new("aes-256-gcm")
37
37
  cipher.encrypt
38
- nonce = xkey.apply(cipher)
38
+ nonce = key.apply(cipher)
39
39
  cipher.auth_data = "optional authenticated data"
40
40
  ciphertext = cipher.update(plaintext) + cipher.final
41
41
  tag = cipher.auth_tag
@@ -43,7 +43,7 @@ tag = cipher.auth_tag
43
43
  # Decrypt (pass the same nonce used for encryption)
44
44
  decipher = OpenSSL::Cipher.new("aes-256-gcm")
45
45
  decipher.decrypt
46
- xkey.apply(decipher, nonce:)
46
+ key.apply(decipher, nonce:)
47
47
  decipher.auth_tag = tag
48
48
  decipher.auth_data = "optional authenticated data"
49
49
  plaintext = decipher.update(ciphertext) + decipher.final
@@ -60,6 +60,7 @@ Key differences:
60
60
  - Smaller code footprint
61
61
  - Leaving OpenSSL::Cipher setup to the user
62
62
  - Accumulated randomized test vectors are included in the test suite
63
+ - rbs signature
63
64
 
64
65
  ## License
65
66
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module XaesGcm
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XaesGcm
4
+ module Xaes256gcm
5
+ class Key
6
+ def initialize(key)
7
+ raise ArgumentError, "key must be #{KEY_SIZE} bytes" unless key.bytesize == KEY_SIZE
8
+
9
+ @cipher = OpenSSL::Cipher.new('aes-256-ecb')
10
+ @cipher.encrypt
11
+ @cipher.padding = 0
12
+ @cipher.key = key
13
+
14
+ raise "unexpected cipher block size" unless @cipher.block_size == 16
15
+ l = @cipher.update("\x00" * @cipher.block_size)
16
+ @cipher.freeze
17
+
18
+ # K1: shift L left by 1 bit, XOR last byte with 0x87 if MSB was set
19
+ msb = l.getbyte(0) >> 7
20
+ k1_bytes = Array.new(16) do |i|
21
+ next_bit = (i < 15) ? (l.getbyte(i + 1) >> 7) : 0
22
+ ((l.getbyte(i) << 1) | next_bit) & 0b11111111
23
+ end
24
+ k1_bytes[-1] ^= 0b10000111 & -(msb & 1)
25
+ @k1 = k1_bytes.pack('C*')
26
+ end
27
+
28
+ if HAVE_INSTANCE_VARIABLES_TO_INSPECT
29
+ def instance_variables_to_inspect = []
30
+ else
31
+ def inspect
32
+ "#<#{self.class}>"
33
+ end
34
+ alias to_s inspect
35
+ end
36
+
37
+ def enable_hazmat!
38
+ @enable_hazmat = true
39
+ self
40
+ end
41
+
42
+ def apply(cipher, nonce: OpenSSL::Random.random_bytes(NONCE_SIZE))
43
+ raise ArgumentError, "cipher must be AES-256-GCM" unless cipher.name == "AES-256-GCM"
44
+
45
+ dk = derive_key_raw(nonce:)
46
+ cipher.key = dk.key
47
+ cipher.iv = dk.nonce
48
+ nonce
49
+ end
50
+
51
+ def derive_key(nonce:)
52
+ 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
53
+ derive_key_raw(nonce:)
54
+ end
55
+
56
+ private
57
+
58
+ def derive_key_raw(nonce:)
59
+ raise ArgumentError, "nonce must be #{NONCE_SIZE} bytes" unless nonce.bytesize == NONCE_SIZE
60
+
61
+ n12 = nonce.byteslice(0, 12)
62
+
63
+ m1 = "\x00\x01\x58\x00".b + n12
64
+ m2 = "\x00\x02\x58\x00".b + n12
65
+
66
+ m1_xored = xor_blocks(m1, @k1)
67
+ m2_xored = xor_blocks(m2, @k1)
68
+
69
+ cipher = @cipher.dup
70
+ derived = cipher.update(m1_xored + m2_xored)
71
+
72
+ DerivedKey.new(key: derived, nonce: nonce.byteslice(12, 12))
73
+ end
74
+
75
+ def xor_blocks(a, b)
76
+ a_bytes = a.unpack('C*')
77
+ b_bytes = b.unpack('C*')
78
+ a_bytes.length.times { |i| a_bytes[i] ^= b_bytes[i] }
79
+ a_bytes.pack('C*')
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module XaesGcm
4
+ module Xaes256gcm
5
+ KEY_SIZE = 32
6
+ NONCE_SIZE = 24
7
+
8
+ DerivedKey = Data.define(:key, :nonce) do
9
+ # Data.define#inspect doesn't use instance_variables_to_inspect
10
+ def inspect
11
+ "#<#{self.class}>"
12
+ end
13
+ alias to_s inspect
14
+ end
15
+ end
16
+ end
17
+
18
+ require_relative "xaes256gcm/key"
data/lib/xaes_gcm.rb CHANGED
@@ -6,9 +6,6 @@ require_relative "xaes_gcm/version"
6
6
  module XaesGcm
7
7
  class Error < StandardError; end
8
8
 
9
- KEY_SIZE = 32
10
- NONCE_SIZE = 24
11
-
12
9
  # Detect instance_variables_to_inspect support (Ruby feature #13555)
13
10
  HAVE_INSTANCE_VARIABLES_TO_INSPECT = begin
14
11
  klass = Class.new do
@@ -18,13 +15,10 @@ module XaesGcm
18
15
  !klass.new.inspect.include?("@secret")
19
16
  end
20
17
 
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
18
+ def self.key(key_length, key)
19
+ raise ArgumentError, "unsupported key length: #{key_length}" unless key_length == 256
20
+ Xaes256gcm::Key.new(key)
27
21
  end
28
22
  end
29
23
 
30
- require_relative "xaes_gcm/key"
24
+ require_relative "xaes_gcm/xaes256gcm"
data/sig/xaes_gcm.rbs CHANGED
@@ -1,28 +1,33 @@
1
1
  module XaesGcm
2
2
  VERSION: String
3
- KEY_SIZE: Integer
4
- NONCE_SIZE: Integer
5
3
 
6
4
  class Error < StandardError
7
5
  end
8
6
 
9
- class DerivedKey
10
- attr_reader key: String
11
- attr_reader nonce: String
7
+ def self.key: (Integer key_length, String key) -> Xaes256gcm::Key
12
8
 
13
- def self.new: (key: String, nonce: String) -> DerivedKey
14
- def self.[]: (key: String, nonce: String) -> DerivedKey
15
- end
9
+ module Xaes256gcm
10
+ KEY_SIZE: Integer
11
+ NONCE_SIZE: Integer
12
+
13
+ class DerivedKey
14
+ attr_reader key: String
15
+ attr_reader nonce: String
16
+
17
+ def self.new: (key: String, nonce: String) -> DerivedKey
18
+ def self.[]: (key: String, nonce: String) -> DerivedKey
19
+ end
16
20
 
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
21
+ class Key
22
+ def initialize: (String key) -> void
23
+ def enable_hazmat!: () -> self
24
+ def apply: (OpenSSL::Cipher cipher, ?nonce: String) -> String
25
+ def derive_key: (nonce: String) -> DerivedKey
22
26
 
23
- private
27
+ private
24
28
 
25
- def derive_key_raw: (nonce: String) -> DerivedKey
26
- def xor_blocks: (String a, String b) -> String
29
+ def derive_key_raw: (nonce: String) -> DerivedKey
30
+ def xor_blocks: (String a, String b) -> String
31
+ end
27
32
  end
28
33
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xaes_gcm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sorah Fukumori
@@ -22,8 +22,9 @@ files:
22
22
  - README.md
23
23
  - Rakefile
24
24
  - lib/xaes_gcm.rb
25
- - lib/xaes_gcm/key.rb
26
25
  - lib/xaes_gcm/version.rb
26
+ - lib/xaes_gcm/xaes256gcm.rb
27
+ - lib/xaes_gcm/xaes256gcm/key.rb
27
28
  - sig/xaes_gcm.rbs
28
29
  homepage: https://github.com/sorah/xaes_gcm
29
30
  licenses:
data/lib/xaes_gcm/key.rb DELETED
@@ -1,81 +0,0 @@
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