aead 1.3.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.
- data/.gitignore +8 -0
- data/.travis.yml +3 -0
- data/.yardopts +2 -0
- data/Gemfile +7 -0
- data/Guardfile +10 -0
- data/LICENSE.md +22 -0
- data/README.md +57 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/aead.gemspec +34 -0
- data/ext/openssl/cipher/aead/.gitignore +4 -0
- data/ext/openssl/cipher/aead/aead.c +156 -0
- data/ext/openssl/cipher/aead/extconf.rb +6 -0
- data/lib/aead.rb +4 -0
- data/lib/aead/cipher.rb +221 -0
- data/lib/aead/cipher/aes_256_cbc_hmac_sha_256.rb +30 -0
- data/lib/aead/cipher/aes_256_ctr_hmac_sha_256.rb +30 -0
- data/lib/aead/cipher/aes_256_gcm.rb +45 -0
- data/lib/aead/cipher/aes_hmac.rb +69 -0
- data/lib/aead/nonce.rb +270 -0
- data/lib/openssl/cipher/.gitignore +0 -0
- data/spec/aead/cipher/aes_256_cbc_hmac_sha_256_spec.rb +142 -0
- data/spec/aead/cipher/aes_256_ctr_hmac_sha_256_spec.rb +142 -0
- data/spec/aead/cipher/aes_256_gcm_spec.rb +133 -0
- data/spec/aead/cipher_spec.rb +61 -0
- data/spec/aead/nonce_spec.rb +124 -0
- data/spec/spec_helper.rb +26 -0
- metadata +287 -0
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'aead/cipher/aes_256_ctr_hmac_sha_256'
|
3
|
+
|
4
|
+
describe AEAD::Cipher::AES_256_CBC_HMAC_SHA_256 do
|
5
|
+
subject { self.cipher.new(self.key) }
|
6
|
+
|
7
|
+
let(:algo) { 'aes-256-cbc-hmac-sha-256' }
|
8
|
+
let(:cipher) { AEAD::Cipher.new(algo) }
|
9
|
+
let(:key) { self.cipher.generate_key }
|
10
|
+
let(:nonce) { self.cipher.generate_nonce }
|
11
|
+
let(:aad) { SecureRandom.random_bytes }
|
12
|
+
let(:plaintext) { SecureRandom.random_bytes }
|
13
|
+
|
14
|
+
it 'must decrypt its own ciphertexts' do
|
15
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
16
|
+
plaintext = subject.decrypt(self.nonce, self.aad, ciphertext)
|
17
|
+
|
18
|
+
plaintext.must_equal self.plaintext
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'must require a 512-bit or larger key' do
|
22
|
+
bad_keys = [ 0, 1, 33, 63 ].map {|size| SecureRandom.random_bytes(size) }
|
23
|
+
good_keys = [ 64, 65, 10_000 ].map {|size| SecureRandom.random_bytes(size) }
|
24
|
+
|
25
|
+
bad_keys.each do |key|
|
26
|
+
-> { self.cipher.new(key) }.must_raise ArgumentError
|
27
|
+
end
|
28
|
+
|
29
|
+
good_keys.each do |key|
|
30
|
+
self.cipher.new(key).must_be_kind_of AEAD::Cipher
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'must require a 16-byte nonce' do
|
35
|
+
bad_nonces = [ 0, 1, 15, 17 ].map {|size| SecureRandom.random_bytes(size) }
|
36
|
+
good_nonces = [ 16 ] .map {|size| SecureRandom.random_bytes(size) }
|
37
|
+
|
38
|
+
bad_nonces.each do |nonce|
|
39
|
+
-> { self.subject.encrypt(nonce, self.plaintext, self.aad) }.
|
40
|
+
must_raise ArgumentError
|
41
|
+
end
|
42
|
+
|
43
|
+
good_nonces.each do |nonce|
|
44
|
+
self.subject.encrypt(nonce, self.plaintext, self.aad).
|
45
|
+
must_be_kind_of String
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'must require a non-empty plaintext' do
|
50
|
+
-> { self.subject.encrypt(nonce, self.aad, nil) }.must_raise ArgumentError
|
51
|
+
-> { self.subject.encrypt(nonce, self.aad, '') }.must_raise ArgumentError
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'must encrypt plaintexts correctly' do
|
55
|
+
subject.encrypt(self.nonce, self.aad, self.plaintext).
|
56
|
+
must_equal openssl_encrypt(self.key, self.nonce, self.aad, self.plaintext)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'must decrypt ciphertexts correctly' do
|
60
|
+
ciphertext = openssl_encrypt(self.key, self.nonce, self.aad, self.plaintext)
|
61
|
+
|
62
|
+
subject.decrypt(self.nonce, self.aad, ciphertext).
|
63
|
+
must_equal openssl_decrypt(self.key, self.nonce, self.aad, ciphertext)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'must resist manipulation of the signing key' do
|
67
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
68
|
+
cipher = self.cipher.new key[0, 32] << twiddle(key[32, 32])
|
69
|
+
|
70
|
+
-> { cipher.decrypt(self.nonce, self.aad, ciphertext) }.
|
71
|
+
must_raise ArgumentError
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'must resist manipulation of the nonce' do
|
75
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
76
|
+
nonce = twiddle(self.nonce)
|
77
|
+
|
78
|
+
-> { self.subject.decrypt(nonce, self.aad, ciphertext) }.
|
79
|
+
must_raise ArgumentError
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'must resist manipulation of the ciphertext' do
|
83
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
84
|
+
ciphertext = twiddle(ciphertext)
|
85
|
+
|
86
|
+
-> { self.subject.decrypt(self.nonce, self.aad, ciphertext) }.
|
87
|
+
must_raise ArgumentError
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'must resist manipulation of the aad' do
|
91
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
92
|
+
aad = twiddle(self.aad)
|
93
|
+
|
94
|
+
-> { self.subject.decrypt(self.nonce, aad, ciphertext) }.
|
95
|
+
must_raise ArgumentError
|
96
|
+
end
|
97
|
+
|
98
|
+
def twiddle(bytes)
|
99
|
+
# pick a random byte to change
|
100
|
+
index = SecureRandom.random_number(bytes.bytesize)
|
101
|
+
|
102
|
+
# change it by a random offset that won't loop back around to its
|
103
|
+
# original value
|
104
|
+
offset = SecureRandom.random_number(254) + 1
|
105
|
+
ord = bytes[index].ord
|
106
|
+
byte = (ord + offset).modulo(256).chr
|
107
|
+
|
108
|
+
# reconstruct the bytes with the twiddled bit inserted in place
|
109
|
+
bytes[0, index] << byte << bytes[index.succ..-1]
|
110
|
+
end
|
111
|
+
|
112
|
+
def openssl_encrypt(key, nonce, aad, plaintext)
|
113
|
+
encryption_key = key[ 0, 32]
|
114
|
+
signing_key = key[32, 32]
|
115
|
+
cipher = OpenSSL::Cipher.new('aes-256-cbc').encrypt
|
116
|
+
nonce = nonce.rjust(16, "\0")
|
117
|
+
cipher.key = encryption_key
|
118
|
+
cipher.iv = nonce
|
119
|
+
|
120
|
+
ciphertext = cipher.update(plaintext) + cipher.final
|
121
|
+
tag = OpenSSL::HMAC.digest 'SHA256', signing_key,
|
122
|
+
[ 'aes-256-cbc' .length ].pack('Q>') << 'aes-256-cbc' <<
|
123
|
+
[ ciphertext .length ].pack('Q>') << ciphertext <<
|
124
|
+
[ nonce .length ].pack('Q>') << nonce <<
|
125
|
+
[ aad .length ].pack('Q>') << aad
|
126
|
+
|
127
|
+
ciphertext + tag
|
128
|
+
end
|
129
|
+
|
130
|
+
def openssl_decrypt(key, nonce, aad, ciphertext)
|
131
|
+
encryption_key = key[ 0, 32]
|
132
|
+
signing_key = key[32, 32]
|
133
|
+
tag = ciphertext[ -32 .. -1 ]
|
134
|
+
ciphertext = ciphertext[ 0 .. -33 ]
|
135
|
+
|
136
|
+
cipher = OpenSSL::Cipher.new('aes-256-cbc').decrypt
|
137
|
+
cipher.key = encryption_key
|
138
|
+
cipher.iv = nonce.rjust(16, "\0")
|
139
|
+
|
140
|
+
cipher.update(ciphertext) + cipher.final
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'aead/cipher/aes_256_ctr_hmac_sha_256'
|
3
|
+
|
4
|
+
describe AEAD::Cipher::AES_256_CTR_HMAC_SHA_256 do
|
5
|
+
subject { self.cipher.new(self.key) }
|
6
|
+
|
7
|
+
let(:algo) { 'aes-256-ctr-hmac-sha-256' }
|
8
|
+
let(:cipher) { AEAD::Cipher.new(algo) }
|
9
|
+
let(:key) { self.cipher.generate_key }
|
10
|
+
let(:nonce) { self.cipher.generate_nonce }
|
11
|
+
let(:aad) { SecureRandom.random_bytes }
|
12
|
+
let(:plaintext) { SecureRandom.random_bytes }
|
13
|
+
|
14
|
+
it 'must decrypt its own ciphertexts' do
|
15
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
16
|
+
plaintext = subject.decrypt(self.nonce, self.aad, ciphertext)
|
17
|
+
|
18
|
+
plaintext.must_equal self.plaintext
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'must require a 512-bit or larger key' do
|
22
|
+
bad_keys = [ 0, 1, 33, 63 ].map {|size| SecureRandom.random_bytes(size) }
|
23
|
+
good_keys = [ 64, 65, 10_000 ].map {|size| SecureRandom.random_bytes(size) }
|
24
|
+
|
25
|
+
bad_keys.each do |key|
|
26
|
+
-> { self.cipher.new(key) }.must_raise ArgumentError
|
27
|
+
end
|
28
|
+
|
29
|
+
good_keys.each do |key|
|
30
|
+
self.cipher.new(key).must_be_kind_of AEAD::Cipher
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'must require a 16-byte nonce' do
|
35
|
+
bad_nonces = [0, 1, 15, 17 ].map {|size| SecureRandom.random_bytes(size) }
|
36
|
+
good_nonces = [ 16 ] .map {|size| SecureRandom.random_bytes(size) }
|
37
|
+
|
38
|
+
bad_nonces.each do |nonce|
|
39
|
+
-> { self.subject.encrypt(nonce, self.plaintext, self.aad) }.
|
40
|
+
must_raise ArgumentError
|
41
|
+
end
|
42
|
+
|
43
|
+
good_nonces.each do |nonce|
|
44
|
+
self.subject.encrypt(nonce, self.plaintext, self.aad).
|
45
|
+
must_be_kind_of String
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'must require a non-empty plaintext' do
|
50
|
+
-> { self.subject.encrypt(nonce, self.aad, nil) }.must_raise ArgumentError
|
51
|
+
-> { self.subject.encrypt(nonce, self.aad, '') }.must_raise ArgumentError
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'must encrypt plaintexts correctly' do
|
55
|
+
subject.encrypt(self.nonce, self.aad, self.plaintext).
|
56
|
+
must_equal openssl_encrypt(self.key, self.nonce, self.aad, self.plaintext)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'must decrypt ciphertexts correctly' do
|
60
|
+
ciphertext = openssl_encrypt(self.key, self.nonce, self.aad, self.plaintext)
|
61
|
+
|
62
|
+
subject.decrypt(self.nonce, self.aad, ciphertext).
|
63
|
+
must_equal openssl_decrypt(self.key, self.nonce, self.aad, ciphertext)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'must resist manipulation of the signing key' do
|
67
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
68
|
+
cipher = self.cipher.new key[0, 32] << twiddle(key[32, 32])
|
69
|
+
|
70
|
+
-> { cipher.decrypt(self.nonce, self.aad, ciphertext) }.
|
71
|
+
must_raise ArgumentError
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'must resist manipulation of the nonce' do
|
75
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
76
|
+
nonce = twiddle(self.nonce)
|
77
|
+
|
78
|
+
-> { self.subject.decrypt(nonce, self.aad, ciphertext) }.
|
79
|
+
must_raise ArgumentError
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'must resist manipulation of the ciphertext' do
|
83
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
84
|
+
ciphertext = twiddle(ciphertext)
|
85
|
+
|
86
|
+
-> { self.subject.decrypt(self.nonce, self.aad, ciphertext) }.
|
87
|
+
must_raise ArgumentError
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'must resist manipulation of the aad' do
|
91
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
92
|
+
aad = twiddle(self.aad)
|
93
|
+
|
94
|
+
-> { self.subject.decrypt(self.nonce, aad, ciphertext) }.
|
95
|
+
must_raise ArgumentError
|
96
|
+
end
|
97
|
+
|
98
|
+
def twiddle(bytes)
|
99
|
+
# pick a random byte to change
|
100
|
+
index = SecureRandom.random_number(bytes.bytesize)
|
101
|
+
|
102
|
+
# change it by a random offset that won't loop back around to its
|
103
|
+
# original value
|
104
|
+
offset = SecureRandom.random_number(254) + 1
|
105
|
+
ord = bytes[index].ord
|
106
|
+
byte = (ord + offset).modulo(256).chr
|
107
|
+
|
108
|
+
# reconstruct the bytes with the twiddled bit inserted in place
|
109
|
+
bytes[0, index] << byte << bytes[index.succ..-1]
|
110
|
+
end
|
111
|
+
|
112
|
+
def openssl_encrypt(key, nonce, aad, plaintext)
|
113
|
+
encryption_key = key[ 0, 32]
|
114
|
+
signing_key = key[32, 32]
|
115
|
+
cipher = OpenSSL::Cipher.new('aes-256-ctr').encrypt
|
116
|
+
nonce = nonce.rjust(16, "\0")
|
117
|
+
cipher.key = encryption_key
|
118
|
+
cipher.iv = nonce
|
119
|
+
|
120
|
+
ciphertext = cipher.update(plaintext) + cipher.final
|
121
|
+
tag = OpenSSL::HMAC.digest 'SHA256', signing_key,
|
122
|
+
[ 'aes-256-cbc' .length ].pack('Q>') << 'aes-256-ctr' <<
|
123
|
+
[ ciphertext .length ].pack('Q>') << ciphertext <<
|
124
|
+
[ nonce .length ].pack('Q>') << nonce <<
|
125
|
+
[ aad .length ].pack('Q>') << aad
|
126
|
+
|
127
|
+
ciphertext + tag
|
128
|
+
end
|
129
|
+
|
130
|
+
def openssl_decrypt(key, nonce, aad, ciphertext)
|
131
|
+
encryption_key = key[ 0, 32]
|
132
|
+
signing_key = key[32, 32]
|
133
|
+
tag = ciphertext[ -32 .. -1 ]
|
134
|
+
ciphertext = ciphertext[ 0 .. -33 ]
|
135
|
+
|
136
|
+
cipher = OpenSSL::Cipher.new('aes-256-ctr').decrypt
|
137
|
+
cipher.key = encryption_key
|
138
|
+
cipher.iv = nonce.rjust(16, "\0")
|
139
|
+
|
140
|
+
cipher.update(ciphertext) + cipher.final
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'aead/cipher/aes_256_gcm'
|
3
|
+
|
4
|
+
describe AEAD::Cipher::AES_256_GCM do
|
5
|
+
subject { self.cipher.new(self.key) }
|
6
|
+
|
7
|
+
let(:algo) { 'aes-256-gcm' }
|
8
|
+
let(:cipher) { AEAD::Cipher.new(algo) }
|
9
|
+
let(:key) { self.cipher.generate_key }
|
10
|
+
let(:nonce) { self.cipher.generate_nonce }
|
11
|
+
let(:aad) { SecureRandom.random_bytes }
|
12
|
+
let(:plaintext) { SecureRandom.random_bytes }
|
13
|
+
|
14
|
+
it 'must decrypt its own ciphertexts' do
|
15
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
16
|
+
plaintext = subject.decrypt(self.nonce, self.aad, ciphertext)
|
17
|
+
|
18
|
+
plaintext.must_equal self.plaintext
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'must require a 256-bit or larger key' do
|
22
|
+
bad_keys = [ 0, 1, 31 ].map {|size| SecureRandom.random_bytes(size) }
|
23
|
+
good_keys = [ 32, 33, 256 ].map {|size| SecureRandom.random_bytes(size) }
|
24
|
+
|
25
|
+
bad_keys.each do |key|
|
26
|
+
-> { self.cipher.new(key) }.must_raise ArgumentError
|
27
|
+
end
|
28
|
+
|
29
|
+
good_keys.each do |key|
|
30
|
+
self.cipher.new(key).must_be_kind_of AEAD::Cipher
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'must require a 12-byte nonce' do
|
35
|
+
bad_nonces = [0, 1, 11, 13 ].map {|size| SecureRandom.random_bytes(size) }
|
36
|
+
good_nonces = [ 12 ] .map {|size| SecureRandom.random_bytes(size) }
|
37
|
+
|
38
|
+
bad_nonces.each do |nonce|
|
39
|
+
-> { self.subject.encrypt(nonce, self.plaintext, self.aad) }.
|
40
|
+
must_raise ArgumentError
|
41
|
+
end
|
42
|
+
|
43
|
+
good_nonces.each do |nonce|
|
44
|
+
self.subject.encrypt(nonce, self.plaintext, self.aad).
|
45
|
+
must_be_kind_of String
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'must require a non-empty plaintext' do
|
50
|
+
-> { self.subject.encrypt(nonce, self.aad, nil) }.must_raise ArgumentError
|
51
|
+
-> { self.subject.encrypt(nonce, self.aad, '') }.must_raise ArgumentError
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'must encrypt plaintexts correctly' do
|
55
|
+
subject.encrypt(self.nonce, self.aad, self.plaintext).
|
56
|
+
must_equal openssl_encrypt(self.key, self.nonce, self.aad, self.plaintext)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'must decrypt ciphertexts correctly' do
|
60
|
+
ciphertext = openssl_encrypt(self.key, self.nonce, self.aad, self.plaintext)
|
61
|
+
|
62
|
+
subject.decrypt(self.nonce, self.aad, ciphertext).
|
63
|
+
must_equal openssl_decrypt(self.key, self.nonce, self.aad, ciphertext)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'must resist manipulation of the key' do
|
67
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
68
|
+
cipher = self.cipher.new twiddle(key)
|
69
|
+
|
70
|
+
-> { cipher.decrypt(self.nonce, self.aad, ciphertext) }.
|
71
|
+
must_raise ArgumentError
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'must resist manipulation of the nonce' do
|
75
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
76
|
+
nonce = twiddle(self.nonce)
|
77
|
+
|
78
|
+
-> { self.subject.decrypt(nonce, self.aad, ciphertext) }.
|
79
|
+
must_raise ArgumentError
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'must resist manipulation of the ciphertext' do
|
83
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
84
|
+
ciphertext = twiddle(ciphertext)
|
85
|
+
|
86
|
+
-> { self.subject.decrypt(self.nonce, self.aad, ciphertext) }.
|
87
|
+
must_raise ArgumentError
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'must resist manipulation of the aad' do
|
91
|
+
ciphertext = subject.encrypt(self.nonce, self.aad, self.plaintext)
|
92
|
+
aad = twiddle(self.aad)
|
93
|
+
|
94
|
+
-> { self.subject.decrypt(self.nonce, aad, ciphertext) }.
|
95
|
+
must_raise ArgumentError
|
96
|
+
end
|
97
|
+
|
98
|
+
def twiddle(bytes)
|
99
|
+
# pick a random byte to change
|
100
|
+
index = SecureRandom.random_number(bytes.bytesize)
|
101
|
+
|
102
|
+
# change it by a random offset that won't loop back around to its
|
103
|
+
# original value
|
104
|
+
offset = SecureRandom.random_number(254) + 1
|
105
|
+
ord = bytes[index].ord
|
106
|
+
byte = (ord + offset).modulo(256).chr
|
107
|
+
|
108
|
+
# reconstruct the bytes with the twiddled bit inserted in place
|
109
|
+
bytes[0, index] << byte << bytes[index.succ..-1]
|
110
|
+
end
|
111
|
+
|
112
|
+
def openssl_encrypt(key, nonce, aad, plaintext)
|
113
|
+
cipher = OpenSSL::Cipher.new(self.algo).encrypt
|
114
|
+
cipher.key = key
|
115
|
+
cipher.iv = nonce
|
116
|
+
cipher.aad = aad if aad
|
117
|
+
|
118
|
+
cipher.update(plaintext) + cipher.final + cipher.gcm_tag
|
119
|
+
end
|
120
|
+
|
121
|
+
def openssl_decrypt(key, nonce, aad, ciphertext)
|
122
|
+
tag = ciphertext[ -16 .. -1 ]
|
123
|
+
ciphertext = ciphertext[ 0 .. -17 ]
|
124
|
+
|
125
|
+
cipher = OpenSSL::Cipher.new(self.algo).decrypt
|
126
|
+
cipher.key = key
|
127
|
+
cipher.iv = nonce
|
128
|
+
cipher.gcm_tag = tag
|
129
|
+
cipher.aad = aad if aad
|
130
|
+
|
131
|
+
cipher.update(ciphertext).tap { cipher.verify }
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'aead/cipher'
|
3
|
+
|
4
|
+
describe AEAD::Cipher do
|
5
|
+
subject { AEAD::Cipher }
|
6
|
+
|
7
|
+
let(:aes_256_gcm) { subject.new('aes-256-gcm') }
|
8
|
+
let(:aes_256_ctr_hmac_sha_256) { subject.new('aes-256-ctr-hmac-sha-256') }
|
9
|
+
|
10
|
+
it 'must instantiate subclasses' do
|
11
|
+
subject.new('aes-256-gcm').
|
12
|
+
must_equal AEAD::Cipher::AES_256_GCM
|
13
|
+
|
14
|
+
subject.new('aes-256-ctr-hmac-sha-256').
|
15
|
+
must_equal AEAD::Cipher::AES_256_CTR_HMAC_SHA_256
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'must generate nonces' do
|
19
|
+
self.aes_256_ctr_hmac_sha_256.generate_nonce.bytesize.
|
20
|
+
must_equal self.aes_256_ctr_hmac_sha_256.nonce_len
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'must generate appropriately-sized keys' do
|
24
|
+
self.aes_256_gcm.generate_key.bytesize.
|
25
|
+
must_equal self.aes_256_gcm.key_len
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'must compare signatures' do
|
29
|
+
left = SecureRandom.random_bytes(64)
|
30
|
+
right = SecureRandom.random_bytes(64)
|
31
|
+
|
32
|
+
subject.signature_compare(left, right).must_equal false
|
33
|
+
subject.signature_compare(left, left) .must_equal true
|
34
|
+
end
|
35
|
+
|
36
|
+
bench 'signature_compare on increasingly similar strings' do
|
37
|
+
assert_performance_constant 0.99999 do |n|
|
38
|
+
left = SecureRandom.random_bytes(10_000)
|
39
|
+
right = left.chars.take(n).join + SecureRandom.random_bytes(10_000 - n)
|
40
|
+
|
41
|
+
10.times { subject.signature_compare(left.downcase, right.downcase) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
bench 'signature_compare on different-sized strings' do
|
46
|
+
assert_performance_constant 0.99999 do |n|
|
47
|
+
left = SecureRandom.random_bytes(n)
|
48
|
+
right = SecureRandom.random_bytes(n + 1)
|
49
|
+
|
50
|
+
1_000.times { subject.signature_compare(left, right) }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
bench 'signature_compare on increasingly large strings' do
|
55
|
+
assert_performance_linear 0.999 do |n|
|
56
|
+
string = SecureRandom.random_bytes(n * 500)
|
57
|
+
|
58
|
+
subject.signature_compare(string, string)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|