• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 22035052639

15 Feb 2026 11:42AM UTC coverage: 91.702% (+1.7%) from 90.0%
22035052639

Pull #5339

github

web-flow
Merge 0d5d00d39 into 2993205af
Pull Request #5339: Improve block cipher tests

104230 of 113662 relevant lines covered (91.7%)

11133970.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.71
/src/tests/test_block.cpp
1
/*
2
* (C) 2014,2015 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8

9
#if defined(BOTAN_HAS_BLOCK_CIPHER)
10
   #include <botan/block_cipher.h>
11
   #include <botan/exceptn.h>
12
   #include <botan/mem_ops.h>
13
   #include <botan/rng.h>
14
   #include <botan/internal/fmt.h>
15
#endif
16

17
namespace Botan_Tests {
18

19
#if defined(BOTAN_HAS_BLOCK_CIPHER)
20

21
class Block_Cipher_Tests final : public Text_Based_Test {
×
22
   public:
23
      Block_Cipher_Tests() : Text_Based_Test("block", "Key,In,Out", "Tweak,Iterations") {}
2✔
24

25
      std::vector<std::string> possible_providers(const std::string& algo) override {
19,669✔
26
         return provider_filter(Botan::BlockCipher::providers(algo));
19,669✔
27
      }
28

29
      Test::Result run_one_test(const std::string& algo, const VarMap& vars) override {
19,669✔
30
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
19,669✔
31
         const std::vector<uint8_t> input = vars.get_req_bin("In");
19,669✔
32
         const std::vector<uint8_t> expected = vars.get_req_bin("Out");
19,669✔
33
         const std::vector<uint8_t> tweak = vars.get_opt_bin("Tweak");
19,669✔
34
         const size_t iterations = vars.get_opt_sz("Iterations", 1);
19,669✔
35

36
         Test::Result result(algo);
19,669✔
37

38
         if(iterations > 1 && run_long_tests() == false) {
19,669✔
39
            return result;
40
         }
41

42
         const std::vector<std::string> providers = possible_providers(algo);
19,669✔
43

44
         if(providers.empty()) {
19,669✔
45
            result.note_missing("block cipher " + algo);
×
46
            return result;
×
47
         }
48

49
         for(const auto& provider_ask : providers) {
39,338✔
50
            auto cipher = Botan::BlockCipher::create(algo, provider_ask);
19,669✔
51

52
            if(!cipher) {
19,669✔
53
               result.test_failure(Botan::fmt("Cipher {} supported by {} but not found", algo, provider_ask));
×
54
               continue;
×
55
            }
56

57
            const std::string provider(cipher->provider());
19,669✔
58
            result.test_is_nonempty("provider", provider);
19,669✔
59
            result.test_eq(provider, cipher->name(), algo);
19,669✔
60
            result.test_sz_gte(provider, cipher->parallelism(), 1);
19,669✔
61
            result.test_sz_gte(provider, cipher->block_size(), 8);
19,669✔
62
            result.test_sz_gte(provider, cipher->parallel_bytes(), cipher->block_size() * cipher->parallelism());
39,338✔
63

64
            result.test_is_false("no key set", cipher->has_keying_material());
19,669✔
65

66
            // Test that trying to encrypt or decrypt with no key set throws Botan::Invalid_State
67
            try {
19,669✔
68
               std::vector<uint8_t> block(cipher->block_size());
19,669✔
69
               cipher->encrypt(block);
19,669✔
70
               result.test_failure("Was able to encrypt without a key being set");
×
71
            } catch(Botan::Invalid_State&) {
39,338✔
72
               result.test_success("Trying to encrypt with no key set fails");
19,669✔
73
            }
19,669✔
74

75
            try {
19,669✔
76
               std::vector<uint8_t> block(cipher->block_size());
19,669✔
77
               cipher->decrypt(block);
19,669✔
78
               result.test_failure("Was able to decrypt without a key being set");
×
79
            } catch(Botan::Invalid_State&) {
39,338✔
80
               result.test_success("Trying to encrypt with no key set fails");
19,669✔
81
            }
19,669✔
82

83
            // Test to make sure clear() resets what we need it to
84
            cipher->set_key(this->rng().random_vec(cipher->key_spec().maximum_keylength()));
19,669✔
85
            Botan::secure_vector<uint8_t> garbage = this->rng().random_vec(cipher->block_size());
19,669✔
86
            cipher->encrypt(garbage);
19,669✔
87
            cipher->clear();
19,669✔
88

89
            /*
90
            * Different providers may have additional restrictions on key sizes.
91
            * Avoid testing the cipher with a key size that it does not natively support.
92
            */
93
            if(!cipher->valid_keylength(key.size())) {
19,669✔
94
               result.test_note("Skipping test with provider " + provider + " as it does not support key length " +
×
95
                                std::to_string(key.size()));
×
96
               continue;
×
97
            }
98

99
            cipher->set_key(key);
19,669✔
100
            result.test_is_true("key set", cipher->has_keying_material());
19,669✔
101

102
            if(!tweak.empty()) {
19,669✔
103
               Botan::Tweakable_Block_Cipher* tbc = dynamic_cast<Botan::Tweakable_Block_Cipher*>(cipher.get());
2✔
104
               if(tbc == nullptr) {
2✔
105
                  result.test_failure("Tweak set in test data but cipher is not a Tweakable_Block_Cipher");
×
106
               } else {
107
                  tbc->set_tweak(tweak.data(), tweak.size());
2✔
108
               }
109
            }
110

111
            // Test that clone works and does not affect parent object
112
            auto clone = cipher->new_object();
19,669✔
113
            result.test_is_true("Clone has different pointer", cipher.get() != clone.get());
19,669✔
114
            result.test_eq("Clone has same name", cipher->name(), clone->name());
19,669✔
115
            clone->set_key(this->rng().random_vec(cipher->maximum_keylength()));
19,669✔
116

117
            // have called set_key on clone: process input values
118
            std::vector<uint8_t> buf = input;
19,669✔
119

120
            for(size_t i = 0; i != iterations; ++i) {
3,039,335✔
121
               cipher->encrypt(buf);
6,039,332✔
122
            }
123

124
            result.test_eq(provider, "encrypt", buf, expected);
19,669✔
125

126
            // always decrypt expected ciphertext vs what we produced above
127
            buf = expected;
19,669✔
128

129
            for(size_t i = 0; i != iterations; ++i) {
3,039,335✔
130
               cipher->decrypt(buf);
6,039,332✔
131
            }
132

133
            result.test_eq(provider, "decrypt", buf, input);
19,669✔
134

135
            // Now test misaligned buffers
136
            const size_t blocks = input.size() / cipher->block_size();
19,669✔
137
            buf.resize(input.size() + 1);
19,669✔
138
            Botan::copy_mem(buf.data() + 1, input.data(), input.size());
19,669✔
139

140
            for(size_t i = 0; i != iterations; ++i) {
3,039,335✔
141
               cipher->encrypt_n(buf.data() + 1, buf.data() + 1, blocks);
3,019,666✔
142
            }
143

144
            result.test_eq(provider.c_str(),
19,669✔
145
                           "encrypt misaligned",
146
                           buf.data() + 1,
19,669✔
147
                           buf.size() - 1,
19,669✔
148
                           expected.data(),
149
                           expected.size());
150

151
            // always decrypt expected ciphertext vs what we produced above
152
            Botan::copy_mem(buf.data() + 1, expected.data(), expected.size());
19,669✔
153

154
            for(size_t i = 0; i != iterations; ++i) {
3,039,335✔
155
               cipher->decrypt_n(buf.data() + 1, buf.data() + 1, blocks);
3,019,666✔
156
            }
157

158
            result.test_eq(
19,669✔
159
               provider.c_str(), "decrypt misaligned", buf.data() + 1, buf.size() - 1, input.data(), input.size());
19,669✔
160

161
            result.test_is_true("key set", cipher->has_keying_material());
19,669✔
162
            cipher->clear();
19,669✔
163
            result.test_is_false("key set", cipher->has_keying_material());
19,669✔
164

165
            try {
19,669✔
166
               std::vector<uint8_t> block(cipher->block_size());
19,669✔
167
               cipher->encrypt(block);
19,669✔
168
               result.test_failure("Was able to encrypt without a key being set");
×
169
            } catch(Botan::Invalid_State&) {
39,338✔
170
               result.test_success("Trying to encrypt with no key set (after clear) fails");
19,669✔
171
            }
19,669✔
172

173
            try {
19,669✔
174
               std::vector<uint8_t> block(cipher->block_size());
19,669✔
175
               cipher->decrypt(block);
19,669✔
176
               result.test_failure("Was able to decrypt without a key being set");
×
177
            } catch(Botan::Invalid_State&) {
39,338✔
178
               result.test_success("Trying to decrypt with no key set (after clear) fails");
19,669✔
179
            }
19,669✔
180
         }
78,676✔
181

182
         return result;
183
      }
19,669✔
184
};
185

186
BOTAN_REGISTER_SERIALIZED_SMOKE_TEST("block", "block_ciphers", Block_Cipher_Tests);
187

188
class BlockCipher_ParallelOp_Test final : public Test {
1✔
189
   public:
190
      std::vector<Test::Result> run() override {
1✔
191
         /*
192
         * This is somewhat intentionally not a list of all ciphers
193
         * but rather those that are or are likely in the future to be
194
         * implemented using some kind of bitslicing or SIMD technique.
195
         */
196
         const std::vector<std::string> ciphers = {"AES-128",
1✔
197
                                                   "AES-192",
198
                                                   "AES-256",
199
                                                   "ARIA-128",
200
                                                   "ARIA-256",
201
                                                   "Camellia-128",
202
                                                   "Camellia-192",
203
                                                   "Camellia-256",
204
                                                   "DES",
205
                                                   "TripleDES",
206
                                                   "IDEA",
207
                                                   "Noekeon",
208
                                                   "SEED",
209
                                                   "Serpent",
210
                                                   "SHACAL2",
211
                                                   "SM4"};
1✔
212

213
         std::vector<Test::Result> results;
1✔
214
         results.reserve(ciphers.size());
1✔
215
         for(const auto& cipher : ciphers) {
17✔
216
            results.push_back(test_parallel_op(cipher));
32✔
217
         }
218
         return results;
1✔
219
      }
1✔
220

221
   private:
222
      Test::Result test_parallel_op(const std::string& cipher_name) const {
16✔
223
         Test::Result result(cipher_name + " parallel operation");
16✔
224

225
         auto cipher = Botan::BlockCipher::create(cipher_name);
16✔
226
         if(cipher == nullptr) {
16✔
227
            result.note_missing(cipher_name);
×
228
            return result;
229
         }
230

231
         result.test_sz_gte("Has non-zero parallelism", cipher->parallelism(), 1);
16✔
232

233
         const size_t block_size = cipher->block_size();
16✔
234

235
         // Chosen to maximize coverage of handling of tail blocks
236
         constexpr size_t test_blocks = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1;
16✔
237

238
         std::vector<uint8_t> input(block_size * test_blocks);
16✔
239
         rng().randomize(input);
16✔
240

241
         cipher->set_key(rng().random_vec(cipher->maximum_keylength()));
16✔
242

243
         // Encrypt the message one block at a time
244
         std::vector<uint8_t> enc_1by1(input);
16✔
245

246
         for(size_t i = 0; i != test_blocks; ++i) {
4,096✔
247
            cipher->encrypt(&enc_1by1[i * block_size], &enc_1by1[i * block_size]);
4,080✔
248
         }
249

250
         // Encrypt the message with all blocks potentially in parallel
251
         std::vector<uint8_t> enc_all(input);
16✔
252

253
         cipher->encrypt(enc_all);
16✔
254

255
         result.test_eq("Same output no matter how encrypted", enc_all, enc_1by1);
16✔
256

257
         // Decrypt the message one block at a time
258
         for(size_t i = 0; i != test_blocks; ++i) {
4,096✔
259
            cipher->decrypt(&enc_1by1[i * block_size], &enc_1by1[i * block_size]);
4,080✔
260
         }
261

262
         // Decrypt the message with all blocks potentially in parallel
263
         cipher->decrypt(enc_all);
16✔
264

265
         result.test_eq("Same output no matter how decrypted", enc_all, enc_1by1);
16✔
266
         result.test_eq("Original input recovered in 1-by-1", enc_1by1, input);
16✔
267
         result.test_eq("Original input recovered in parallel processing", enc_all, input);
16✔
268

269
         return result;
16✔
270
      }
32✔
271
};
272

273
BOTAN_REGISTER_TEST("block", "bc_parop", BlockCipher_ParallelOp_Test);
274

275
#endif
276

277
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc