aead 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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