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.
@@ -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