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 +4 -4
- data/README.md +5 -4
- data/lib/xaes_gcm/version.rb +1 -1
- data/lib/xaes_gcm/xaes256gcm/key.rb +83 -0
- data/lib/xaes_gcm/xaes256gcm.rb +18 -0
- data/lib/xaes_gcm.rb +4 -10
- data/sig/xaes_gcm.rbs +21 -16
- metadata +3 -2
- data/lib/xaes_gcm/key.rb +0 -81
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 455b27ec1f586df020e3abfdaad8b77cf6d0813ecdd026d22b771c582190e90a
|
|
4
|
+
data.tar.gz: 36d8ef0d413ecac690aa35ee2803e95776e65bd5a537ad9c691c163b1fa35877
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
33
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
data/lib/xaes_gcm/version.rb
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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/
|
|
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
|
-
|
|
10
|
-
attr_reader key: String
|
|
11
|
-
attr_reader nonce: String
|
|
7
|
+
def self.key: (Integer key_length, String key) -> Xaes256gcm::Key
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
+
private
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
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.
|
|
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
|