diff options
author | Jack Sangdahl <[email protected]> | 2024-06-15 22:27:25 -0600 |
---|---|---|
committer | Jack Sangdahl <[email protected]> | 2024-06-15 22:27:25 -0600 |
commit | a59a5d144cc31cc8386e60dcc97be45a0dfd5256 (patch) | |
tree | 076d0adc9bc53538e7283e0248adef57ce2a9a51 | |
download | cryptopals-master.tar.gz cryptopals-master.zip |
60 files changed, 3366 insertions, 0 deletions
diff --git a/aes_in_ecb_s1.c b/aes_in_ecb_s1.c new file mode 100644 index 0000000..0544920 --- /dev/null +++ b/aes_in_ecb_s1.c @@ -0,0 +1,25 @@ +#include "util/aes.h" +#include "util/base64.h" +#include "util/io.h" + +int main() { + char *b64txt = read_non_space(stdin); + if (b64txt == NULL) { + printf("read_non_space() failed\n"); + exit(1); + } + size_t cyphertext_len; + uint8_t *cyphertext = base64_decode(b64txt, &cyphertext_len); + unsigned char *key = + (unsigned char *)"YELLOW SUBMARINE"; // 128bit best number :^) + unsigned char *decrypted = malloc(cyphertext_len + 16); // inl + block_size + EVP_CIPHER_CTX *ctx = evp_init(); + int decrypted_len = + evp_ecb_decrypt(ctx, cyphertext, cyphertext_len, key, decrypted); + if (decrypted_len == -1) + handle_evp_errors(); + evp_cleanup(ctx); + decrypted[decrypted_len] = '\0'; // NULL-terminate + printf("decrypted text is: %s\n", decrypted); + return 0; +} diff --git a/break_fixed_nonce_s3.c b/break_fixed_nonce_s3.c new file mode 100644 index 0000000..eb37500 --- /dev/null +++ b/break_fixed_nonce_s3.c @@ -0,0 +1,95 @@ +// https://cryptopals.com/sets/3/challenges/19 +// https://cryptopals.com/sets/3/challenges/20 +#include "util/aes.h" +#include "util/base64.h" +#include "util/random.h" +#include "util/score.h" + +/* [challenge 20] + * data/c20_s3.txt contains base64'd plaintext. solve by using repeating-key + * XOR'd algorithm. CTR encryption appears different from repeated-key XOR, but + * with a fixed nonce, they are effectively the same thing. to make use of this + * "exploit", take collection of ciphertexts and trucate them to a common + * length. solve resulting concatenation of ciphertexts as if for repeating-key + * XOR, with a key size of the legnth of ciphertext. + */ + +unsigned char key[16]; +char *strings[] = {"SSBoYXZlIG1ldCB0aGVtIGF0IGNsb3NlIG9mIGRheQ==", + "Q29taW5nIHdpdGggdml2aWQgZmFjZXM=", + "RnJvbSBjb3VudGVyIG9yIGRlc2sgYW1vbmcgZ3JleQ==", + "RWlnaHRlZW50aC1jZW50dXJ5IGhvdXNlcy4=", + "SSBoYXZlIHBhc3NlZCB3aXRoIGEgbm9kIG9mIHRoZSBoZWFk", + "T3IgcG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==", + "T3IgaGF2ZSBsaW5nZXJlZCBhd2hpbGUgYW5kIHNhaWQ=", + "UG9saXRlIG1lYW5pbmdsZXNzIHdvcmRzLA==", + "QW5kIHRob3VnaHQgYmVmb3JlIEkgaGFkIGRvbmU=", + "T2YgYSBtb2NraW5nIHRhbGUgb3IgYSBnaWJl", + "VG8gcGxlYXNlIGEgY29tcGFuaW9u", + "QXJvdW5kIHRoZSBmaXJlIGF0IHRoZSBjbHViLA==", + "QmVpbmcgY2VydGFpbiB0aGF0IHRoZXkgYW5kIEk=", + "QnV0IGxpdmVkIHdoZXJlIG1vdGxleSBpcyB3b3JuOg==", + "QWxsIGNoYW5nZWQsIGNoYW5nZWQgdXR0ZXJseTo=", + "QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4=", + "VGhhdCB3b21hbidzIGRheXMgd2VyZSBzcGVudA==", + "SW4gaWdub3JhbnQgZ29vZCB3aWxsLA==", + "SGVyIG5pZ2h0cyBpbiBhcmd1bWVudA==", + "VW50aWwgaGVyIHZvaWNlIGdyZXcgc2hyaWxsLg==", + "V2hhdCB2b2ljZSBtb3JlIHN3ZWV0IHRoYW4gaGVycw==", + "V2hlbiB5b3VuZyBhbmQgYmVhdXRpZnVsLA==", + "U2hlIHJvZGUgdG8gaGFycmllcnM/", + "VGhpcyBtYW4gaGFkIGtlcHQgYSBzY2hvb2w=", + "QW5kIHJvZGUgb3VyIHdpbmdlZCBob3JzZS4=", + "VGhpcyBvdGhlciBoaXMgaGVscGVyIGFuZCBmcmllbmQ=", + "V2FzIGNvbWluZyBpbnRvIGhpcyBmb3JjZTs=", + "SGUgbWlnaHQgaGF2ZSB3b24gZmFtZSBpbiB0aGUgZW5kLA==", + "U28gc2Vuc2l0aXZlIGhpcyBuYXR1cmUgc2VlbWVkLA==", + "U28gZGFyaW5nIGFuZCBzd2VldCBoaXMgdGhvdWdodC4=", + "VGhpcyBvdGhlciBtYW4gSSBoYWQgZHJlYW1lZA==", + "QSBkcnVua2VuLCB2YWluLWdsb3Jpb3VzIGxvdXQu", + "SGUgaGFkIGRvbmUgbW9zdCBiaXR0ZXIgd3Jvbmc=", + "VG8gc29tZSB3aG8gYXJlIG5lYXIgbXkgaGVhcnQs", + "WWV0IEkgbnVtYmVyIGhpbSBpbiB0aGUgc29uZzs=", + "SGUsIHRvbywgaGFzIHJlc2lnbmVkIGhpcyBwYXJ0", + "SW4gdGhlIGNhc3VhbCBjb21lZHk7", + "SGUsIHRvbywgaGFzIGJlZW4gY2hhbmdlZCBpbiBoaXMgdHVybiw=", + "VHJhbnNmb3JtZWQgdXR0ZXJseTo=", + "QSB0ZXJyaWJsZSBiZWF1dHkgaXMgYm9ybi4="}; + +int main() { + init_with_random_bytes(key, 16); + size_t arr_len = sizeof(strings) / sizeof(strings[0]); + unsigned char *outs[arr_len]; + size_t lens[arr_len], max_len = 0; + // encrypt with random key + for (uint i = 0; i < arr_len; i++) { + size_t in_len; + uint8_t *in = base64_decode(strings[i], &in_len); + outs[i] = malloc(in_len); + lens[i] = in_len; + if (in_len > max_len) + max_len = in_len; + if (!ctr_encrypt(in, (int)in_len, key, 0, outs[i])) { + fprintf(stderr, "ctr_encrypt failed\n"); + exit(1); + } + } + uint8_t buf[arr_len], key_stream[max_len]; + for (uint j = 0; j < max_len; j++) { + size_t cnt = 0; + for (uint i = 0; i < arr_len; i++) + if (j < lens[i]) + buf[cnt++] = outs[i][j]; + key_stream[j] = best_key_step(buf, cnt, 0, 1); + } + // print decrypted result (last characters + // of lines are too difficult to break) + for (uint i = 0; i < arr_len; i++) + for (uint j = 0; j < max_len; j++) { + if (j < lens[i]) + printf("%c\n", outs[i][j] ^ key_stream[j]); + else + break; + } + return 0; +} diff --git a/break_repeating_key_xor_s1.c b/break_repeating_key_xor_s1.c new file mode 100644 index 0000000..322829b --- /dev/null +++ b/break_repeating_key_xor_s1.c @@ -0,0 +1,87 @@ +#include "util/base64.h" +#include "util/hamming.h" +#include "util/io.h" +#include "util/score.h" +#include "util/xor.h" +#include <limits.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#define DEBUG + +static double average(double *xs, size_t len) { + size_t count = 0; + double total = 0; + for (size_t i = 0; i < len; i++) { + count++; + total += xs[i]; + } + return total / count; +} + +static void print_key(uint8_t *key, size_t len) { + printf("key=\""); + for (size_t i = 0; i < len; i++) + printf("%c", key[i]); + printf("\"\n"); +} + +int main() { + FILE *stream = stdin; + char *b64text = read_non_space(stream); + if (b64text == NULL) { + printf("read_non_space() failed\n"); + exit(1); + } + size_t byte_len; + uint8_t *cyphertext = base64_decode(b64text, &byte_len); + if (cyphertext == NULL) { + printf("base64_decode() failed\n"); + exit(1); + } + size_t key_size, best_size = 0; + double best_dist = 1e9; + int n = 15; + for (key_size = 2; key_size <= 40; key_size++) { + double dist[n]; + uint8_t *curr = cyphertext; + for (int i = 0; i < n; i++) { + dist[i] = + ((double)hamming_distance_bytes(curr, curr + key_size, key_size)) / + key_size; + curr += 2 * key_size; + } + double norm_dist = average(dist, n); +#ifdef DEBUG + printf("key size: %zu, norm dist: %f\n", key_size, norm_dist); +#endif + if (norm_dist < best_dist) { + best_dist = norm_dist; + best_size = key_size; + } + } +#ifdef DEBUG + printf("best size: %zu, best dist: %f\n", best_size, best_dist); +#endif + uint8_t *key = malloc(best_size); + for (size_t i = 0; i < best_size; i++) { + uint8_t k = best_key_step(cyphertext, byte_len, i, best_size); + key[i] = k; + } + print_key(key, best_size); + printf("\ndecoded message:\n"); + uint8_t *decoded = + xor_with_repeating_key(cyphertext, byte_len, key, best_size); + if (fwrite(decoded, 1, byte_len, stdout) != byte_len) { + printf("frwrite() failed\n"); + exit(1); + } + free(key); + free(decoded); + free(b64text); + free(cyphertext); + fclose(stream); + return 0; +} diff --git a/cbc_bitflipping_attacks_s2.c b/cbc_bitflipping_attacks_s2.c new file mode 100644 index 0000000..cc9a247 --- /dev/null +++ b/cbc_bitflipping_attacks_s2.c @@ -0,0 +1,127 @@ +// https://cryptopals.com/sets/2/challenges/16 +#include "util/aes.h" +#include "util/random.h" +#include <assert.h> +#include <string.h> + +/* + * in CBC mode, a 1-bit error in ciphertext block: + * completeley scrambles the error block & produces + * identical 1-bit error/edit in the next ciphertext. + * + * bit flipping attack is possible since there is no + * integrity and authentication on the data. + * see + * https://crypto.stackexchange.com/questions/66085/bit-flipping-attack-on-cbc-mode + */ + +static unsigned char key[16], iv[16]; + +static int oracle(const char *in, size_t in_len, unsigned char **out) { + assert(*out && in && in_len); + const char *prefix = "comment1=cooking%20MCs;userdata="; + const char *suffix = ";comment2=%20like%20a%20pound%20of%20bacon"; + const size_t prefix_len = strlen(prefix); + const size_t suffix_len = strlen(suffix); + const size_t total_len = prefix_len + in_len + suffix_len; + unsigned char *buf = malloc(total_len); + if (buf == NULL) { + perror("malloc"); + exit(1); + } + // setup encoding buffer + memcpy(buf, (unsigned char *)prefix, prefix_len); + memcpy(buf + prefix_len, (unsigned char *)in, in_len); + memcpy(buf + prefix_len + in_len, (unsigned char *)suffix, suffix_len); + *out = malloc(total_len + 16); + int encr_len = cbc_encrypt(buf, (int)total_len, key, iv, *out); + free(buf); + return encr_len; +} + +static int check_admin(unsigned char *in, size_t in_len) { + unsigned char *out = malloc(in_len + 16); + int decr_len = cbc_decrypt(in, in_len, key, iv, out); + if (decr_len == -1) { + printf("decryption failed\n"); + exit(1); + } + out[decr_len] = '\0'; // NULL-terminate to use 'strstr' + const char *needle = ";admin=true;"; + return (strstr((char *)out, needle) != NULL) ? 1 : 0; +} + +static void flip_msb(unsigned char *data) { + //(flips most significant bit) + *data ^= 0x80; +} + +static size_t get_padding_size(unsigned char *in, size_t in_len) { + assert(in && in_len); + const size_t block_size = 16; + size_t res = block_size; + unsigned char *out = malloc(in_len + block_size); + size_t num_blocks = in_len / block_size + (in_len % block_size == 0 ? 0 : 1); + assert(num_blocks > 1); + unsigned char *second_last_block = in + (num_blocks - 2) * block_size; + for (uint i = 1; i < block_size; i++) { + flip_msb(&second_last_block[block_size - i - 1]); + int decr = cbc_decrypt(in, in_len, key, iv, out); + flip_msb(&second_last_block[block_size - i - 1]); + if (decr > -1) { + res = i; + break; + } + } + free(out); + return res; +} + +static void tamper_ciphertext(unsigned char *in, size_t in_len) { + assert(in_len > 16); + size_t num_blocks = in_len / 16 + (in_len % 16 == 0 ? 0 : 1); + unsigned char *second_last_block = in + (num_blocks - 2) * 16; + unsigned char mask[16], want[16] = ";admin=true;\x04\x04\x04\x04"; + for (uint i = 0; i < 16; i++) { + mask[i] = want[i] ^ 16; + second_last_block[i] ^= mask[i]; + } +} + +int main() { + unsigned char *encrypted; + init_with_random_bytes(key, 16); + memset(iv, 0, 16); + const char *test_str = "x"; + int encr_len = oracle(test_str, strlen(test_str), &encrypted); + if (encr_len == -1) { + printf("encryption failed\n"); + exit(1); + } + if (check_admin(encrypted, encr_len)) + printf("admin access granted :^)\n"); + else + printf("admin access denied D:\n"); + size_t pad_size = get_padding_size(encrypted, encr_len); + size_t total_data_size = encr_len - pad_size; + size_t oracle_data_size = total_data_size - strlen(test_str); + size_t required_data_size = encr_len - oracle_data_size; + printf("padding size: %zu\ntotal data size: %zu\n" + "oracle data size: %zu\nrequired data size: %zu\n", + pad_size, total_data_size, oracle_data_size, required_data_size); + free(encrypted); + char *buf = malloc(required_data_size); + memset(buf, 'a', required_data_size); + encr_len = oracle(buf, required_data_size, &encrypted); + if (encr_len == -1) { + printf("encryption failed\n"); + exit(1); + } + tamper_ciphertext(encrypted, encr_len); + if (check_admin(encrypted, encr_len)) + printf("admin access granted :^)\n"); + else + printf("admin access denied D:\n"); + free(buf); + return 0; +} diff --git a/cbc_padding_oracle_s3.c b/cbc_padding_oracle_s3.c new file mode 100644 index 0000000..bd41c38 --- /dev/null +++ b/cbc_padding_oracle_s3.c @@ -0,0 +1,115 @@ +// https://cryptopals.com/sets/3/challenges/17 +#include "util/aes.h" +#include "util/random.h" +#include <assert.h> +#include <string.h> + +static unsigned char key[16], iv[16]; + +static int oracle(const char *in, size_t in_len, unsigned char **out) { + assert(*out && in && in_len); + const char *prefix = "comment1=cooking%20MCs;userdata="; + const char *suffix = ";comment2=%20like%20a%20pound%20of%20bacon"; + const size_t prefix_len = strlen(prefix); + const size_t suffix_len = strlen(suffix); + const size_t total_len = prefix_len + in_len + suffix_len; + unsigned char *buf = malloc(total_len); + if (buf == NULL) { + perror("malloc"); + exit(1); + } + // set up encoding buffer + memcpy(buf, (unsigned char *)prefix, prefix_len); + memcpy(buf + prefix_len, (unsigned char *)in, in_len); + memcpy(buf + prefix_len + in_len, (unsigned char *)suffix, suffix_len); + *out = malloc(total_len + 16); + int encr_len = cbc_encrypt(buf, (int)total_len, key, iv, *out); + free(buf); + return encr_len; +} + +static int check_admin(unsigned char *in, size_t in_len) { + unsigned char *out = malloc(in_len + 16); + int decr_len = cbc_decrypt(in, in_len, key, iv, out); + if (decr_len == -1) { + printf("decryption failed\n"); + exit(1); + } + out[decr_len] = '\0'; // NULL-terminate to use 'strstr' + const char *needle = ";admin=true;"; + return (strstr((char *)out, needle) != NULL) ? 1 : 0; +} + +static void flip_msb(unsigned char *data) { + //(flips most significant bit) + *data ^= 0x80; +} + +static size_t get_padding_size(unsigned char *in, size_t in_len) { + assert(in && in_len); + const size_t block_size = 16; + size_t res = block_size; + unsigned char *out = malloc(in_len + block_size); + size_t num_blocks = in_len / block_size + (in_len % block_size == 0 ? 0 : 1); + assert(num_blocks > 1); + unsigned char *second_last_block = in + (num_blocks - 2) * block_size; + for (uint i = 1; i < block_size; i++) { + flip_msb(&second_last_block[block_size - i - 1]); + int decr = cbc_decrypt(in, in_len, key, iv, out); + flip_msb(&second_last_block[block_size - i - 1]); + if (decr > -1) + res = i; + break; + } + free(out); + return res; +} + +static void tamper_ciphertext(unsigned char *in, size_t in_len) { + assert(in_len > 16); + size_t num_blocks = in_len / 16 + (in_len % 16 == 0 ? 0 : 1); + unsigned char *second_last_block = in + (num_blocks - 2) * 16; + unsigned char mask[16], want[16] = ";admin=true;\x04\x04\x04\x04"; + for (uint i = 0; i < 16; i++) { + mask[i] ^= 16; + second_last_block[i] ^= mask[i]; + } +} + +int main() { + unsigned char *encrypted; + init_with_random_bytes(key, 16); + memset(iv, 0, 16); + const char *test_str = "x"; + int encr_len = oracle(test_str, strlen(test_str), &encrypted); + if (encr_len == -1) { + printf("encryption failed\n"); + exit(1); + } + if (check_admin(encrypted, encr_len)) + printf("admin access granted :^)\n"); + else + printf("admin access denied D:\n"); + size_t pad_size = get_padding_size(encrypted, encr_len); + size_t total_data_size = encr_len - pad_size; + size_t oracle_data_size = total_data_size - strlen(test_str); + size_t required_data_size = encr_len - oracle_data_size; + printf("padding size: %zu\ntotal data size: %zu\n" + "oracle data size: %zu\nrequired data size: %zu\n", + pad_size, total_data_size, oracle_data_size, required_data_size); + free(encrypted); + char *buf = malloc(required_data_size); + memset(buf, 'a', required_data_size); + encr_len = oracle(buf, required_data_size, &encrypted); + if (encr_len == -1) { + printf("encryption failed\n"); + exit(1); + } + tamper_ciphertext(encrypted, encr_len); + if (check_admin(encrypted, encr_len)) + printf("admin access granted :^)\n"); + else + printf("admin access denied D:\n"); + free(buf); + return 0; +} diff --git a/clone_mt19937_output_s3.c b/clone_mt19937_output_s3.c new file mode 100644 index 0000000..82105eb --- /dev/null +++ b/clone_mt19937_output_s3.c @@ -0,0 +1,79 @@ +// https://cryptopals.com/sets/3/challenges/23 +#include "util/mersenne_twister.h" +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * the internal state of MT19937 consists of 624 32-bit integers + * for each batch of 624 outputs, the algorithm permutes that internal + * state. by permuting state regularly, MT19937 achieves a period of + * 2^19937 (which is quite large!!) + * each time MT19937 is tapped, an element of its internal state is subjected to + * a tempering function that diffuses bits through the result, which can be + * inverted. to invert the temper transform, apply the inverse of each operation + * in reverse order; one XOR against right-shifted value & one XOR against + * left-shifted value AND'd with a magic number. so tldr we need left & right + * invert functions. + */ + +#define STATE_SIZE 624 +#define TEST_SIZE 10 +#define U 11UL +#define S 7UL +#define B 0x9D2C5680UL +#define T 15UL +#define C 0xEFC60000UL +#define L 18UL + +static uint32_t untemper(uint32_t y) { + y ^= y >> L; + y ^= (y << T) & C; + // ugly bit-by-bit solution for 7-bits shift + // i don't know how to improve it + y ^= (((((y << 7) ^ (y << 14)) & (1UL << 31)) | + (((y << 7) ^ (y << 14) ^ (y << 21) ^ (y << 28)) & (1UL << 28)) | + ((y << 7) & (1UL << 27)) | + (((y << 7) ^ (y << 14) ^ (y << 21)) & (1UL << 26)) | + ((y << 7) & (1UL << 24)) | + (((y << 7) ^ (y << 14) ^ (y << 21)) & (1UL << 21)) | + (((y << 7) ^ (y << 14)) & (1UL << 19)) | ((y << 7) & (1UL << 18)) | + (((y << 7) ^ (y << 14)) & (1UL << 14)) | ((y << 7) & (1UL << 12)) | + ((y << 7) & (1UL << 10)) | ((y << 7) & (1UL << 9)) | + ((y << 7) & (1UL << 7))) & + B); + y ^= (y >> U) ^ (y >> (2 * U)); + return y; +} + +int main() { + uint32_t outs[STATE_SIZE], state[STATE_SIZE]; + uint32_t new[TEST_SIZE], old[TEST_SIZE]; + seed(arc4random()); + for (int i = 0; i < STATE_SIZE; i++) { + outs[i] = extract(); + state[i] = untemper(outs[i]); + } + // at this point we have reconstructed MT state, + // so we can predict further values + + // random numbers from original RNG: + for (int i = 0; i < TEST_SIZE; i++) + old[i] = extract(); + // setup new RNG: + set_state(state); + // get the random number from this copied RNG: + for (int i = 0; i < TEST_SIZE; i++) + new[i] = extract(); + for (int i = 0; i < TEST_SIZE; i++) + printf("old: %10" PRIu32 ", new: %10" PRIu32 "\n", old[i], new[i]); + if (memcmp(old, new, sizeof(old)) == 0) { + printf("passed: RNG state reconstructed :^)\n"); + exit(EXIT_SUCCESS); + } else { + printf("failed D:\n"); + exit(EXIT_FAILURE); + } + return 0; +} diff --git a/crack_mt19937_seed_s3.c b/crack_mt19937_seed_s3.c new file mode 100644 index 0000000..4c4c4fb --- /dev/null +++ b/crack_mt19937_seed_s3.c @@ -0,0 +1,49 @@ +// https://cryptopals.com/sets/3/challenges/22 +#include "util/mersenne_twister.h" +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +/* + * wait a random number of seconds between 40-1000 + * seeds RNG with current unix timestamp + * wait a random number of seconds again + * returns first 32-bit output of RNG + * profit + */ + +#define LO 40 +#define HI 1000 + +static uint32_t real_seed; + +static uint32_t simulation() { + uint32_t wait_time = arc4random_uniform(HI - LO + 1) + LO; + sleep((int)wait_time); + real_seed = (uint32_t)time(NULL); + seed(real_seed); + wait_time = arc4random_uniform(HI - LO + 1) + LO; + sleep((int)wait_time); + return extract(); +} + +static uint32_t detect_seed(uint32_t first_val) { + uint32_t now = (uint32_t)time(NULL); + for (uint32_t s = now; s > now - 3 * HI; s--) { + seed(s); + if (extract() == first_val) + return s; + } + printf("could not find seed D:\n"); + return 0; +} + +int main() { + uint32_t rand_val = simulation(); + uint32_t detected_seed = detect_seed(rand_val); + printf("real_seed = %" PRIu32 ", detected seed = %" PRIu32 "\n", real_seed, + detected_seed); + return 0; +} diff --git a/create_mt19937_stream_cipher_s3.c b/create_mt19937_stream_cipher_s3.c new file mode 100644 index 0000000..a5e65a7 --- /dev/null +++ b/create_mt19937_stream_cipher_s3.c @@ -0,0 +1,53 @@ +// https://cryptopals.com/sets/3/challenges/24 +#include "util/mersenne_twister.h" +#include "util/random.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * a trivial stream cipher can be created out of any PRNG; use it to generate + * a sequence of 8-bit outputs and call those outputs 'keystream'. XOR each byte + * of plaintext with each successive byte of keystream. + */ + +void mt_encrypt(unsigned char *in, int in_len, uint16_t key, + unsigned char *out) { + unsigned char keystream[4]; + seed((uint32_t)key); + // extract 4 bytes at a time and use it as + // a keystream to XOR with plaintext + for (int i = 0; i < in_len; i++) { + if (i % 4 == 0) { + // prepare next 4 bytes of keystream + uint32_t k = extract(); + keystream[0] = (unsigned char)(k & 0x000000FF); + keystream[1] = (unsigned char)((k & 0x0000FFFF) >> 8); + keystream[2] = (unsigned char)((k & 0x00FFFFFF) >> 16); + keystream[3] = (unsigned char)(k >> 24); + } + out[i] = in[i] ^ keystream[i % 4]; + } +} + +void mt_decrypt(unsigned char *in, int in_len, uint16_t key, + unsigned char *out) { + mt_encrypt(in, in_len, key, out); +} + +int main() { + char *test = "a long test string, longer than 4 characters!"; + size_t len = strlen(test) + 1; + unsigned char *encr = malloc(len), *decr = malloc(len); + if (encr == NULL || decr == NULL) { + printf("out of memory D:\n"); + exit(1); + } + uint16_t key = (uint16_t)arc4random(); + mt_encrypt((unsigned char *)test, len, key, encr); + mt_decrypt(encr, len, key, decr); + printf("after turnaround: %s\n", decr); + free(encr); + free(decr); + return 0; +} diff --git a/ctr_stream_cipher_mode_s3.c b/ctr_stream_cipher_mode_s3.c new file mode 100644 index 0000000..f6fe932 --- /dev/null +++ b/ctr_stream_cipher_mode_s3.c @@ -0,0 +1,31 @@ +// https://cryptopals.com/sets/3/challenges/18 +#include "util/aes.h" +#include "util/base64.h" + +/* + * CTR mode: instead of encrypting plaintext, CTR mode encrypts + * a running counter, producing a 16-byte block of keystream, + * which is XOR'd against plaintext. + * this mode does not require padding; when there is no more + * plaintext, XOR'ing & keystream generation just stops. + * decryption is identical. + */ + +int main() { + char *str = "L77na/nrFsKvynd6HzOoG7GHTLXsTVu9qvY/" + "2syLXzhPweyyMTJULu/6/kXX0KSvoOLSFQ=="; + size_t decoded_len; + uint8_t *decoded = base64_decode(str, &decoded_len); + unsigned char key[16] = "YELLOW SUBMARINE"; + uint64_t nonce = 0; + unsigned char *decrypted = malloc(decoded_len); + if (!ctr_decrypt(decoded, decoded_len, key, nonce, decrypted)) { + fprintf(stderr, "CTR decryption error\n"); + exit(1); + } + if (fwrite(decrypted, 1, decoded_len, stdout) != decoded_len) { + perror("fwrite"); + exit(1); + } + return 0; +} diff --git a/data/c10_s2.txt b/data/c10_s2.txt new file mode 100644 index 0000000..f0802a5 --- /dev/null +++ b/data/c10_s2.txt @@ -0,0 +1,64 @@ +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 diff --git a/data/c20_s3.txt b/data/c20_s3.txt new file mode 100644 index 0000000..d601f0b --- /dev/null +++ b/data/c20_s3.txt @@ -0,0 +1,60 @@ +SSdtIHJhdGVkICJSIi4uLnRoaXMgaXMgYSB3YXJuaW5nLCB5YSBiZXR0ZXIgdm9pZCAvIFBvZXRzIGFyZSBwYXJhbm9pZCwgREoncyBELXN0cm95ZWQ= +Q3V6IEkgY2FtZSBiYWNrIHRvIGF0dGFjayBvdGhlcnMgaW4gc3BpdGUtIC8gU3RyaWtlIGxpa2UgbGlnaHRuaW4nLCBJdCdzIHF1aXRlIGZyaWdodGVuaW4nIQ== +QnV0IGRvbid0IGJlIGFmcmFpZCBpbiB0aGUgZGFyaywgaW4gYSBwYXJrIC8gTm90IGEgc2NyZWFtIG9yIGEgY3J5LCBvciBhIGJhcmssIG1vcmUgbGlrZSBhIHNwYXJrOw== +WWEgdHJlbWJsZSBsaWtlIGEgYWxjb2hvbGljLCBtdXNjbGVzIHRpZ2h0ZW4gdXAgLyBXaGF0J3MgdGhhdCwgbGlnaHRlbiB1cCEgWW91IHNlZSBhIHNpZ2h0IGJ1dA== +U3VkZGVubHkgeW91IGZlZWwgbGlrZSB5b3VyIGluIGEgaG9ycm9yIGZsaWNrIC8gWW91IGdyYWIgeW91ciBoZWFydCB0aGVuIHdpc2ggZm9yIHRvbW9ycm93IHF1aWNrIQ== +TXVzaWMncyB0aGUgY2x1ZSwgd2hlbiBJIGNvbWUgeW91ciB3YXJuZWQgLyBBcG9jYWx5cHNlIE5vdywgd2hlbiBJJ20gZG9uZSwgeWEgZ29uZSE= +SGF2ZW4ndCB5b3UgZXZlciBoZWFyZCBvZiBhIE1DLW11cmRlcmVyPyAvIFRoaXMgaXMgdGhlIGRlYXRoIHBlbmFsdHksYW5kIEknbSBzZXJ2aW4nIGE= +RGVhdGggd2lzaCwgc28gY29tZSBvbiwgc3RlcCB0byB0aGlzIC8gSHlzdGVyaWNhbCBpZGVhIGZvciBhIGx5cmljYWwgcHJvZmVzc2lvbmlzdCE= +RnJpZGF5IHRoZSB0aGlydGVlbnRoLCB3YWxraW5nIGRvd24gRWxtIFN0cmVldCAvIFlvdSBjb21lIGluIG15IHJlYWxtIHlhIGdldCBiZWF0IQ== +VGhpcyBpcyBvZmYgbGltaXRzLCBzbyB5b3VyIHZpc2lvbnMgYXJlIGJsdXJyeSAvIEFsbCB5YSBzZWUgaXMgdGhlIG1ldGVycyBhdCBhIHZvbHVtZQ== +VGVycm9yIGluIHRoZSBzdHlsZXMsIG5ldmVyIGVycm9yLWZpbGVzIC8gSW5kZWVkIEknbSBrbm93bi15b3VyIGV4aWxlZCE= +Rm9yIHRob3NlIHRoYXQgb3Bwb3NlIHRvIGJlIGxldmVsIG9yIG5leHQgdG8gdGhpcyAvIEkgYWluJ3QgYSBkZXZpbCBhbmQgdGhpcyBhaW4ndCB0aGUgRXhvcmNpc3Qh +V29yc2UgdGhhbiBhIG5pZ2h0bWFyZSwgeW91IGRvbid0IGhhdmUgdG8gc2xlZXAgYSB3aW5rIC8gVGhlIHBhaW4ncyBhIG1pZ3JhaW5lIGV2ZXJ5IHRpbWUgeWEgdGhpbms= +Rmxhc2hiYWNrcyBpbnRlcmZlcmUsIHlhIHN0YXJ0IHRvIGhlYXI6IC8gVGhlIFItQS1LLUktTSBpbiB5b3VyIGVhcjs= +VGhlbiB0aGUgYmVhdCBpcyBoeXN0ZXJpY2FsIC8gVGhhdCBtYWtlcyBFcmljIGdvIGdldCBhIGF4IGFuZCBjaG9wcyB0aGUgd2Fjaw== +U29vbiB0aGUgbHlyaWNhbCBmb3JtYXQgaXMgc3VwZXJpb3IgLyBGYWNlcyBvZiBkZWF0aCByZW1haW4= +TUMncyBkZWNheWluZywgY3V6IHRoZXkgbmV2ZXIgc3RheWVkIC8gVGhlIHNjZW5lIG9mIGEgY3JpbWUgZXZlcnkgbmlnaHQgYXQgdGhlIHNob3c= +VGhlIGZpZW5kIG9mIGEgcmh5bWUgb24gdGhlIG1pYyB0aGF0IHlvdSBrbm93IC8gSXQncyBvbmx5IG9uZSBjYXBhYmxlLCBicmVha3MtdGhlIHVuYnJlYWthYmxl +TWVsb2RpZXMtdW5tYWthYmxlLCBwYXR0ZXJuLXVuZXNjYXBhYmxlIC8gQSBob3JuIGlmIHdhbnQgdGhlIHN0eWxlIEkgcG9zc2Vz +SSBibGVzcyB0aGUgY2hpbGQsIHRoZSBlYXJ0aCwgdGhlIGdvZHMgYW5kIGJvbWIgdGhlIHJlc3QgLyBGb3IgdGhvc2UgdGhhdCBlbnZ5IGEgTUMgaXQgY2FuIGJl +SGF6YXJkb3VzIHRvIHlvdXIgaGVhbHRoIHNvIGJlIGZyaWVuZGx5IC8gQSBtYXR0ZXIgb2YgbGlmZSBhbmQgZGVhdGgsIGp1c3QgbGlrZSBhIGV0Y2gtYS1za2V0Y2g= +U2hha2UgJ3RpbGwgeW91ciBjbGVhciwgbWFrZSBpdCBkaXNhcHBlYXIsIG1ha2UgdGhlIG5leHQgLyBBZnRlciB0aGUgY2VyZW1vbnksIGxldCB0aGUgcmh5bWUgcmVzdCBpbiBwZWFjZQ== +SWYgbm90LCBteSBzb3VsJ2xsIHJlbGVhc2UhIC8gVGhlIHNjZW5lIGlzIHJlY3JlYXRlZCwgcmVpbmNhcm5hdGVkLCB1cGRhdGVkLCBJJ20gZ2xhZCB5b3UgbWFkZSBpdA== +Q3V6IHlvdXIgYWJvdXQgdG8gc2VlIGEgZGlzYXN0cm91cyBzaWdodCAvIEEgcGVyZm9ybWFuY2UgbmV2ZXIgYWdhaW4gcGVyZm9ybWVkIG9uIGEgbWljOg== +THlyaWNzIG9mIGZ1cnkhIEEgZmVhcmlmaWVkIGZyZWVzdHlsZSEgLyBUaGUgIlIiIGlzIGluIHRoZSBob3VzZS10b28gbXVjaCB0ZW5zaW9uIQ== +TWFrZSBzdXJlIHRoZSBzeXN0ZW0ncyBsb3VkIHdoZW4gSSBtZW50aW9uIC8gUGhyYXNlcyB0aGF0J3MgZmVhcnNvbWU= +WW91IHdhbnQgdG8gaGVhciBzb21lIHNvdW5kcyB0aGF0IG5vdCBvbmx5IHBvdW5kcyBidXQgcGxlYXNlIHlvdXIgZWFyZHJ1bXM7IC8gSSBzaXQgYmFjayBhbmQgb2JzZXJ2ZSB0aGUgd2hvbGUgc2NlbmVyeQ== +VGhlbiBub25jaGFsYW50bHkgdGVsbCB5b3Ugd2hhdCBpdCBtZWFuIHRvIG1lIC8gU3RyaWN0bHkgYnVzaW5lc3MgSSdtIHF1aWNrbHkgaW4gdGhpcyBtb29k +QW5kIEkgZG9uJ3QgY2FyZSBpZiB0aGUgd2hvbGUgY3Jvd2QncyBhIHdpdG5lc3MhIC8gSSdtIGEgdGVhciB5b3UgYXBhcnQgYnV0IEknbSBhIHNwYXJlIHlvdSBhIGhlYXJ0 +UHJvZ3JhbSBpbnRvIHRoZSBzcGVlZCBvZiB0aGUgcmh5bWUsIHByZXBhcmUgdG8gc3RhcnQgLyBSaHl0aG0ncyBvdXQgb2YgdGhlIHJhZGl1cywgaW5zYW5lIGFzIHRoZSBjcmF6aWVzdA== +TXVzaWNhbCBtYWRuZXNzIE1DIGV2ZXIgbWFkZSwgc2VlIGl0J3MgLyBOb3cgYW4gZW1lcmdlbmN5LCBvcGVuLWhlYXJ0IHN1cmdlcnk= +T3BlbiB5b3VyIG1pbmQsIHlvdSB3aWxsIGZpbmQgZXZlcnkgd29yZCdsbCBiZSAvIEZ1cmllciB0aGFuIGV2ZXIsIEkgcmVtYWluIHRoZSBmdXJ0dXJl +QmF0dGxlJ3MgdGVtcHRpbmcuLi53aGF0ZXZlciBzdWl0cyB5YSEgLyBGb3Igd29yZHMgdGhlIHNlbnRlbmNlLCB0aGVyZSdzIG5vIHJlc2VtYmxhbmNl +WW91IHRoaW5rIHlvdSdyZSBydWZmZXIsIHRoZW4gc3VmZmVyIHRoZSBjb25zZXF1ZW5jZXMhIC8gSSdtIG5ldmVyIGR5aW5nLXRlcnJpZnlpbmcgcmVzdWx0cw== +SSB3YWtlIHlhIHdpdGggaHVuZHJlZHMgb2YgdGhvdXNhbmRzIG9mIHZvbHRzIC8gTWljLXRvLW1vdXRoIHJlc3VzY2l0YXRpb24sIHJoeXRobSB3aXRoIHJhZGlhdGlvbg== +Tm92b2NhaW4gZWFzZSB0aGUgcGFpbiBpdCBtaWdodCBzYXZlIGhpbSAvIElmIG5vdCwgRXJpYyBCLidzIHRoZSBqdWRnZSwgdGhlIGNyb3dkJ3MgdGhlIGp1cnk= +WW8gUmFraW0sIHdoYXQncyB1cD8gLyBZbywgSSdtIGRvaW5nIHRoZSBrbm93bGVkZ2UsIEUuLCBtYW4gSSdtIHRyeWluZyB0byBnZXQgcGFpZCBpbiBmdWxs +V2VsbCwgY2hlY2sgdGhpcyBvdXQsIHNpbmNlIE5vcmJ5IFdhbHRlcnMgaXMgb3VyIGFnZW5jeSwgcmlnaHQ/IC8gVHJ1ZQ== +S2FyYSBMZXdpcyBpcyBvdXIgYWdlbnQsIHdvcmQgdXAgLyBaYWtpYSBhbmQgNHRoIGFuZCBCcm9hZHdheSBpcyBvdXIgcmVjb3JkIGNvbXBhbnksIGluZGVlZA== +T2theSwgc28gd2hvIHdlIHJvbGxpbicgd2l0aCB0aGVuPyBXZSByb2xsaW4nIHdpdGggUnVzaCAvIE9mIFJ1c2h0b3duIE1hbmFnZW1lbnQ= +Q2hlY2sgdGhpcyBvdXQsIHNpbmNlIHdlIHRhbGtpbmcgb3ZlciAvIFRoaXMgZGVmIGJlYXQgcmlnaHQgaGVyZSB0aGF0IEkgcHV0IHRvZ2V0aGVy +SSB3YW5uYSBoZWFyIHNvbWUgb2YgdGhlbSBkZWYgcmh5bWVzLCB5b3Uga25vdyB3aGF0IEknbSBzYXlpbic/IC8gQW5kIHRvZ2V0aGVyLCB3ZSBjYW4gZ2V0IHBhaWQgaW4gZnVsbA== +VGhpbmtpbicgb2YgYSBtYXN0ZXIgcGxhbiAvICdDdXogYWluJ3QgbnV0aGluJyBidXQgc3dlYXQgaW5zaWRlIG15IGhhbmQ= +U28gSSBkaWcgaW50byBteSBwb2NrZXQsIGFsbCBteSBtb25leSBpcyBzcGVudCAvIFNvIEkgZGlnIGRlZXBlciBidXQgc3RpbGwgY29taW4nIHVwIHdpdGggbGludA== +U28gSSBzdGFydCBteSBtaXNzaW9uLCBsZWF2ZSBteSByZXNpZGVuY2UgLyBUaGlua2luJyBob3cgY291bGQgSSBnZXQgc29tZSBkZWFkIHByZXNpZGVudHM= +SSBuZWVkIG1vbmV5LCBJIHVzZWQgdG8gYmUgYSBzdGljay11cCBraWQgLyBTbyBJIHRoaW5rIG9mIGFsbCB0aGUgZGV2aW91cyB0aGluZ3MgSSBkaWQ= +SSB1c2VkIHRvIHJvbGwgdXAsIHRoaXMgaXMgYSBob2xkIHVwLCBhaW4ndCBudXRoaW4nIGZ1bm55IC8gU3RvcCBzbWlsaW5nLCBiZSBzdGlsbCwgZG9uJ3QgbnV0aGluJyBtb3ZlIGJ1dCB0aGUgbW9uZXk= +QnV0IG5vdyBJIGxlYXJuZWQgdG8gZWFybiAnY3V6IEknbSByaWdodGVvdXMgLyBJIGZlZWwgZ3JlYXQsIHNvIG1heWJlIEkgbWlnaHQganVzdA== +U2VhcmNoIGZvciBhIG5pbmUgdG8gZml2ZSwgaWYgSSBzdHJpdmUgLyBUaGVuIG1heWJlIEknbGwgc3RheSBhbGl2ZQ== +U28gSSB3YWxrIHVwIHRoZSBzdHJlZXQgd2hpc3RsaW4nIHRoaXMgLyBGZWVsaW4nIG91dCBvZiBwbGFjZSAnY3V6LCBtYW4sIGRvIEkgbWlzcw== +QSBwZW4gYW5kIGEgcGFwZXIsIGEgc3RlcmVvLCBhIHRhcGUgb2YgLyBNZSBhbmQgRXJpYyBCLCBhbmQgYSBuaWNlIGJpZyBwbGF0ZSBvZg== +RmlzaCwgd2hpY2ggaXMgbXkgZmF2b3JpdGUgZGlzaCAvIEJ1dCB3aXRob3V0IG5vIG1vbmV5IGl0J3Mgc3RpbGwgYSB3aXNo +J0N1eiBJIGRvbid0IGxpa2UgdG8gZHJlYW0gYWJvdXQgZ2V0dGluJyBwYWlkIC8gU28gSSBkaWcgaW50byB0aGUgYm9va3Mgb2YgdGhlIHJoeW1lcyB0aGF0IEkgbWFkZQ== +U28gbm93IHRvIHRlc3QgdG8gc2VlIGlmIEkgZ290IHB1bGwgLyBIaXQgdGhlIHN0dWRpbywgJ2N1eiBJJ20gcGFpZCBpbiBmdWxs +UmFraW0sIGNoZWNrIHRoaXMgb3V0LCB5byAvIFlvdSBnbyB0byB5b3VyIGdpcmwgaG91c2UgYW5kIEknbGwgZ28gdG8gbWluZQ== +J0NhdXNlIG15IGdpcmwgaXMgZGVmaW5pdGVseSBtYWQgLyAnQ2F1c2UgaXQgdG9vayB1cyB0b28gbG9uZyB0byBkbyB0aGlzIGFsYnVt +WW8sIEkgaGVhciB3aGF0IHlvdSdyZSBzYXlpbmcgLyBTbyBsZXQncyBqdXN0IHB1bXAgdGhlIG11c2ljIHVw +QW5kIGNvdW50IG91ciBtb25leSAvIFlvLCB3ZWxsIGNoZWNrIHRoaXMgb3V0LCB5byBFbGk= +VHVybiBkb3duIHRoZSBiYXNzIGRvd24gLyBBbmQgbGV0IHRoZSBiZWF0IGp1c3Qga2VlcCBvbiByb2NraW4n +QW5kIHdlIG91dHRhIGhlcmUgLyBZbywgd2hhdCBoYXBwZW5lZCB0byBwZWFjZT8gLyBQZWFjZQ== diff --git a/data/c4_s1.txt b/data/c4_s1.txt new file mode 100644 index 0000000..40da378 --- /dev/null +++ b/data/c4_s1.txt @@ -0,0 +1,327 @@ +0e3647e8592d35514a081243582536ed3de6734059001e3f535ce6271032 +334b041de124f73c18011a50e608097ac308ecee501337ec3e100854201d +40e127f51c10031d0133590b1e490f3514e05a54143d08222c2a4071e351 +45440b171d5c1b21342e021c3a0eee7373215c4024f0eb733cf006e2040c +22015e420b07ef21164d5935e82338452f42282c1836e42536284c450de3 +043b452e0268e7eb005a080b360f0642e6e342005217ef04a42f3e43113d +581e0829214202063d70030845e5301f5a5212ed0818e22f120b211b171b +ea0b342957394717132307133f143a1357e9ed1f5023034147465c052616 +0c300b355c2051373a051851ee154a023723414c023a08171e1b4f17595e +550c3e13e80246320b0bec09362542243be42d1d5d060e203e1a0c66ef48 +e159464a582a6a0c50471310084f6b1703221d2e7a54502b2b205c433afa +ec58ea200e3005090e1725005739eda7342aed311001383fff7c58ef1f11 +01305424231c0d2c41f105057f74510d335440332f1038ec17275f5814e1 +05f12f380720ea2b19e24a07e53c142128354e2827f25a08fb401c3126a6 +0d17272f53063954163d050a541b1f1144305ae37d4932431b1f33140b1b +0b4f070f071fe92c200e1fa05e4b272e50201b5d493110e429482c100730 +100a3148080f227fe60a132f0c10174fe3f63d1a5d38eb414ca8e82f2b05 +0a19e83c58400a023b13234572e6e4272bf67434331631e63b5e0f00175c +54520c2ceb45530e0f78111d0b0707e01e4bf43b0606073854324421e6f9 +09e7585353ee4a34190de1354e481c373a1b2b0a136127383e271212191f +0f060d09fb4f2d5024022c5ff6463c390c2b5f1a5532071a31f33503fcea +371d39121605584f48217235ee1e0602445c162e4942254c071954321d29 +4a0900e63e5f161e15554045f3594c2a6a77e4e52711602beaf53ae53bed +29011616565d2a372a605bee39eced31183fe068185c3b445b391fe53232 +e4102337000303452a1e2f2b29493f54ed5a037b3e08311b625cfd005009 +2d560d4b0618203249312a310d5f541f295c3f0f25235c2b20037d1600f3 +2c245155e8253708391a7ceb0d05005c3e080f3f0f0e5a16583b111f4448 +493804044d262eec3759594f212d562420105d6a39e70a0f3957f347070c +e72d1d1f103807590f4339575e00381074485d2d580249f744052605e11d +e131570ae95307143a71131729552d001057a4540a1f425b190b572dee34 +2c1655342f02581c202b0a5c17a358291e1506f325550f05365e165c1c5f +e318164df80b043e5406296e5359271d152f552e155a43eda81f23231d1c +001de0413e174e18192c061e4b3d1b5626f90e3e1429544a20ee150d0c20 +32e902193219033c58191302441a5c1b584825ea140c290927aaea53e23c +3a36363a732e32ea3f0e430508204b332c382a19292d5b291122e123446a +1804115614031f5f571f2b143c5d3c1b257a4b37350f18445a3e08341c3d +21f2fb250b2e55151e77253a3f0e5f4b2030370a4155e720e73914e35a4a +510a55583a3c491221397c123a2b14a8305b3b09e71b241d0e51202e1a32 +1b51202f4917232b512a141d6812f03c455df05e5a1c2cee14390b3b593a +5f5731e5203116ee131a4a4b24112cef5d0822f035e6547d3a0014462f26 +0028fb522104f771501a555d3f581e30e9ec3e49e3e63123432f07794145 +1459f6312f000e5a1373e346e40f211e1b0b0e17000f391f170552150500 +7e301e18325717e3412e022f087be30e5641080151357714e0e0eee15e11 +533258e9360f513b083aa51d2824222f40200a470537ecec392d31070b38 +07e32c180dfa56496a461627542115132a4c284050495b23e2245b093159 +2d3c230a1e5a300f6c3e26ed0d1709434950fd6f1e121335054129e4e4ec +ef22fa2112311b11584ce43434f46f521a215433f9514fe33d313a3e0838 +34e7f336270c08010f2f544f0f1c1e235c0222644c2632efec061de2115f +121a42395d4c560d213b0c0a26a7e4f4382718153d5e511158a10b2c021e +e05d414dfa40222f0c382a03235f4d0d04372d4b7855105e26e44f2e0555 +7f3a4f1351f85b0344223e1177e14707190c0e311f4ca633f5f3e9352372 +01424d5d1a322a0d381717130e181d07240c2c19ecee750b1a37085d014c +16012c5de55a0314a8260e2759e439123ca0c81c321d454e4e0ee14f4c1d +0b1415512f38580e4e2a227def242643183c224f0ea146443403022fe9fd +43eb2b1078322a02192d5b5e0c360d584d0b5e2c13072912ee32f03f4155 +002a52553e08361b0be0074b573e201c164c093a5c0f0159333b59770d5b +38e63c1c5244301a5a01f26930321256143e1ae05e1120a9eaf20a192d58 +7d54140a152ef4035f09083ded531ee04df55848020656a1342e502649eb +0c211dfe101702015516341136252f3f06f73247133113f5642d083a3417 +015e3d51433f3c003e5e28030b1d413eee186824504b241e0f0d32373e2b +2d465040ec130c5c0e2704aa17010c40095207223669110f22f45ea155f7 +14552e2b341e5ce0195351066a23e3283e0ee935444b255a1c5c3cef7614 +372b453d5a357c05142be65b3c17f92d2b134853390a312bf92a531b513d +5658265f4c0ce4440a20322f591a413034292b312206a01be6453a512d21 +1c585c19f31f785324f8583d1ee02620342b10a236263f105011ee5b0e14 +0f522b550818591a752e5fea0e033322ee5e280a4a1b244f5a2b35341255 +39093c1ced331b264127173f1312e2455fa33b31012c1f4d073c553f5d5e +18f82d5d07e2430b3b3c1b5b49effb0313173f5d4a2e5c134555ff6b1d1a +550a20234202726341190311295254f4064205aa515ae0145a23071c4e18 +3f2047024e3ce4555a1b39fa145455012c3afb0f2d11134846182e3c575b +e3e456571937762828065443153b51152e262f09c937024405284f236432 +012f580c3536ec5c021574541d5c41123a4e661d5f0f5f344a083e3a5e4c +4216252d01eb0a2a4623621b48360d312c29f33e380650447617124b3e71 +54141e59323606390204e95f1206520e5c084510034d30171c5e744f335d +1e30061401600b342e171059526d1949431a3f412f56594c183711ea4837 +3131254f11e76f550e1e4d26f1391f44363b151c31281ff45259351da0e6 +5def250d0f3505385f22e9f4112633005d272d092e0138275851f943e90e +0939165718303b445210095c16390cf04f19450e06f4545c0a0c320e3e23 +1e0b0b1f573f3d0fe05d43090fa8482242300819313142325b1f4b19365b +0d3b2a5d271e463d2203765245065d5d684a051e5815265b52f3171d3004 +6af423303817a43324394af15a5c482e3b16f5a46f1e0b5c1201214b5fe4 +4030544f3f51151e436e04203a5e3b287ee303490a43fb3b28042f36504e +1a2d5a03fc0e2c04384046242e2b5e1548101825eb2f285f1a210f022141 +122355e90122281deeed3ba05636003826525d5551572d07030d4935201f +2a3c484a15410d3b16375d4665271b5c4ce7ee37083d3e512b45204f17f6 +03222801255c2c211a7aeb1e042b4e38e8f1293143203139fb202c325f2b +06542a28041956350e292bf3fe5c32133a2a171b3a3e4e4e3101381529e3 +4a5209ef24e5f3225e503b143d0e5747323fe7ee3d5b1b5110395619e65a +1fee0a3945563d2b5703701817584b5f5b54702522f5031b561929ea2d1e +e7271935100e3c31211b23113a3a5524e02241181a251d521ff52f3c5a76 +144a0efee02f0f5f1d353a1c112e1909234f032953ec591e0a58e55d2cf4 +efee0cf00d0955500210015311467543544708eb590d113d30443d080c1e +1a562c1f7e2b0030094f051c03e30f4d501a0fe22a2817edfc5e470c3843 +1c3df1135321a8e9241a5607f8305d571aa546001e3254555a11511924 +eb1d3f54ec0fea341a097c502ff1111524e24f5b553e49e8576b5b0e1e33 +72413e2f5329e332ec563b5e65185efefd2c3b4e5f0b5133246d214a401d +352a0ae632183d200a162e5346110552131514e0553e51003e220d47424b +1d005c58135f3c1b53300c3b49263928f55625454f3be259361ded1f0834 +2d2457524a1e1204255934174d442a1a7d130f350a123c4a075f5be73e30 +0c0518582d131f39575925e0231833370c482b270e183810415d5aec1900 +453b181df1572735380b0446097f00111f1425070b2e1958102ceb592928 +010a4a2d0b0926082d2f1525562d1d070a7a08152f5b4438a4150b132e20 +2b395d0d5d015d41335d21250de33e3d42152d3f557d1e44e4ee22255d2d +4a1b5c272d0d1c45072639362e402dee2853e51311262b17aa72eb390410 +e7015f0215352030574b4108e44d0e1a204418e62325ff7f34052f234b2d +1d563c13202346071d39e34055402b0b392c27f552222d3deb3843ee2c16 +29332a521f3c1b0811e33e1a25520e323e75e01c17473f55071226120d3d +210b35ee1a0a5335222e35033905170c4f3104eb032d425058367d5a2bf2 +1e553809415efb1c460f2f0ffafaec491e4d4e49510452e8245a366a4106 +e1f92cee0e10142514e7ec13155c412fe901092f1f0fa738280c5eee5e04 +3526291e0b2a5f486a3051041f4c16372f5402e6f70b31a03525190b161a +260e5e1f0c2e4d7528ef11552fefe247201e4752085c1da903563c162a4b +2a14ff2e3265e604075e523b24455c364a7f284f3a43051d52152f1119e8 +5f02e55a4b1300063640ef10151002565f0b0c010033a1cbef5d3634484a +1b121c585b495a5e033a09037f2d1754072c2d49084055172a3c220bed4f +1613400e1632435c0018482aa55b363d26290ae4405ded280f2b0c271536 +4011250ce02119464a1de43113170356342c272d1d3355555e5706245e0a +16272d5e545953002e10020875e223010719555410f91ce518420e382456 +0d4037320345f945241a1d090a545a310142442131464f4d10562ae4f05a +07ee4d4ae12e571e313c1636313134233e495459e548317708563c2c1b2f +e75803294b36565225552c3406304f0201e43323291b5e0e2159025c2f25 +5e63194411490c44494232237e1b323108573d3f391d1f3537e4165a2b35 +51000a3a264c503b5852072a5636f04f5cea58a42838f5fca876415c3521 +3c14130be511275932055a30aa2d03470c51060009f210543002585f5713 +10f0370c5823115200e5015d083e2f1a5df91d68065c1b03f0080855e529 +02ec00f1462d034123151ba6fc07eb3d5e54e85a3f3ee532fb41791a060b +0c29274232f93efb3d465544e45e491b042ced245100e3f05c14134c254b +5741235f051e080401a8013c065627e8ee5432205114243d54320e133f2d +4a4d181635411f5d084e31ed230c16506d5125415e060e4dcd0e5f3708e3 +2d531c3e22065a5eee07310c145305131800063e4a20094b2006ea131240 +e7335c1c4308160be6aa551a0f5a58243e0b10ee470047683c345e1c5b0c +5434505ee22a18110d20342e4b53062c4d79042a0a02422e225b2523e95a +3252212407115c07e15eee06391d0519e9271b641330011f383410281f0e +2cee2b355233292b595d1c69592f483b54584f7154fd4928560752e333a1 +17272b272f110df5e91c560a39104510240b5c4b0c1c570871e422351927 +c32550ec3f132c0c2458503ae5241d3c0d7911480a073826315620403615 +16e11c270d2b010650145de2290b0beb1e120a3a354b2104064f3b533c4e +505746313d4d2e3455290a281ee81d50007e1148252528025237715a342a +1c0a13163e404e40242142061d34185421160220fa031f7a423a08f2e01a +101d303802f51b0c08ef461259315b553823e622a12d565509e23c624139 +0a3d1309e4384c0eed383846545a035a41ee1771513b090a031e15f45159 +2d4944092a1965542507003b23195758403e175a0a450c5c38114de21141 +eb100fe63a031c4b35eb591845e428441c0d5b0037131f5c160a31243619 +c155ef0d19143e24392507a202581a25491b135c27571d5c5b35250f0bef +0e1d510556485e39557e044e2cf10457523016473f500b1e36370c17591c +7e5a19250a5e152b46f5130a094cef08e84704ef10197324464b0114017a +3b56f126390008343d3c400232ed201667211f0b1a1413080202530b08e2 +4912321b61c90a0cf6ef0a0a0c0f17fa62eb385e2616194526701aff5fe6 +2c57114b0400152d4f2aeb18ed41386c2e3a023a281d1a311eefe750ebab +3a4353282114593b3e36446d2c5e1e582e335337022930331f211604576a +295f3bfae9271ae8065a3b4417545c3e5b0df11a53351c78530915392d2e +074a122ee01b17131e4e124e2322a9560ce4120e37582b24e1036fe93f30 +3c08290121090ef72f25e4f220323444532d3fe71f34553c7b2726131009 +12e84a3308590357a719e74c4f2133690a20031a0b045af63551325b1219 +0e3d4fe03f56523cf40f29e4353455120e3a4f2f26f6a30a2b3e0c5b085a +57f3315c33e41c0f523426232d0651395c1525274e314d0219163b5f181f +53471622182739e9e25b473d74e1e7023d095a3134e62d1366563004120e +230a06431935391d5e0b5543223a3bed2b4358f555401e1b3b5c36470d11 +22100330e03b4812e6120f163b1ef6abebe6f602545ef9a459e33d334c2a +463405faa655563a43532cfe154bec32fe3345eb2c2700340811213e5006 +14241340112b2916017c270a0652732ee8121132385a6c020c040e2be15b +251119225c573b105d5c0a371c3d421ef23e22377fee334e0228561b2d15 +2e4c2e373b434b0d0b1b340c300e4b195614130ea03c234c292e14530c46 +0d2c3f08560ee32e5a5b6413355215384442563e69ec294a0eef561e3053 +193c100c0b24231c012273e10d2e12552723586120020b02e45632265e5f +2c175a11553d4b0b16025e2534180964245b125e5d6e595d1d2a0710580b +213a175ff30855e4001b305000263f5a5c3c5100163cee00114e3518f33a +10ed33e65b003012e7131e161d5e2e270b4645f358394118330f5a5b241b +33e80130f45708395457573406422a3b0d03e6e5053d0d2d151c083337a2 +551be2082b1563c4ec2247140400124d4b6508041b5a472256093aea1847 +7b5a4215415d544115415d5015455447414c155c46155f4058455c5b523f +0864eb4935144c501103a71851370719301bec57093a0929ea3f18060e55 +2d395e57143359e80efffb13330633ea19e323077b4814571e5a3de73a1f +52e73c1d53330846243c422d3e1b374b5209543903e3195c041c251b7c04 +2f3c2c28273a12520b482f18340d565d1fe84735474f4a012e1a13502523 +23340f39064e306a08194d544647522e1443041d5ee81f5a18415e34a45f +475a392637565757730a0c4a517b2821040e1709e028071558021f164c54 +100b2135190505264254005618f51152136125370eef27383e45350118ed +3947452914e0223f1d040943313c193f295b221e573e1b5723391d090d1f +2c33141859392b04155e3d4e393b322526ee3e581d1b3d6817374d0c085b +c2ea5821200f1b755b2d13130f04e26625ea3a5b1e37144d3e473c24030d +ee15025d2019f757305e3f010e2a453a205f1919391e1a04e86d1a350119 +1a5beb4946180fe0002a031a050b41e5164c58795021e1e45c59e2495c20 +1121394f1e381c3647005b7326250514272b55250a49183be5454ba518eb +1ee55936102a465d5004371f2e382f1d03144f170d2b0eed042ee341eb19 +ec1014ef3ff1272c3408220a41163708140b2e340e505c560c1e4cf82704 +274b341a454a27a0263408292e362c201c0401462049523b2d55e5132d54 +e259032c444b091e2e4920023f1a7ce40908255228e36f0f2424394b3c48 +34130cf8223f23084813e745e006531a1e464b005e0e1ee405413fe22b4e +4af201080c0928420c2d491f6e5121e451223b070dee54244b3efc470a0e +771c161f795df81c22101408465ae7ef0c0604733ee03a20560c1512f217 +2f3a142c4155073a200f04166c565634020a59ea04244ff7413c4bc10858 +240d4752e5fa5a4e1ce255505602e55d4c575e2b59f52b4e0c0a0b464019 +21341927f3380232396707232ae424ea123f5b371d4f65e2471dfbede611 +e10e1c3b1d4d28085c091f135b585709332c56134e4844552f45eb41172a +3f1b5a343f034832193b153c482f1705392f021f5f0953290c4c43312b36 +3810161aea7001fb5d502b285945255d4ef80131572d2c2e59730e2c3035 +4d59052e1f2242403d440a13263e1d2dea0612125e16033b180834030829 +022917180d07474c295f793e42274b0e1e16581036225c1211e41e04042f +ec2b41054f2a5f56065e5e0e1f56e13e0a702e1b2f2137020e363a2ae2a4 +53085a3b34e75a1caa2e5d031f261f5f044350312f37455d493f131f3746 +0c295f1724e90b001a4e015d27091a0b3256302c303d51a05956e6331531 +e42b315ce21f0def38144d20242845fa3f3b3b0ce8f4fb2d31ed1d54134b +2957023141335d35372813263b46581af6535a16404d0b4ff12a207648ec +e4421e301de25c43010c504e0f562f2018421ce137443b41134b5f542047 +0c5600294e085c1d3622292c480d261213e05c1334385108c145f3090612 +062d2e02267404241f4966e6e010052d3224e72856100b1d22f65a30e863 +324950394700e11a01201a0564525706f1013f353319076b4c0d015a2e24 +2a1be80e2013571522483b1e20321a4e03285d211a444d113924e8f41a1f +27193ae2302208e73010eaa1292001045737013e10e4745aed2c105b25fb +1b135d46eaef103e1d330a14337a2a4302441c1631ed07e7100c743a0e35 +1a0957115c293b1c0de853245b5b18e2e12d28421b3230245d7b4a55f355 +e7360e2b3846202a2926fa495e3302ed064d127a17343a1f11032b40e8f5 +06e8f90a3118381c5414157d1434050210363e30500511a00a3d56e10438 +30021931f7193e25a0540ef52658350929380974fb035b1a5d2c042959c7 +151b0c24052d0e56025404390e5a3909edec0d03070f040cff710825363e +2a2328120b2203320810134a0c0a0ef30b25460bec011c1e26e913575a51 +e12d0948ed3c511416151d1c54082b3e385d14f838510bec4e4b5f585321 +1559305c3a49192a010f04ec11001a3d5a5621e5535358353206521f013f +172c2c155a3a322009505c290516a2c4e4405a1e0a1e353b6e1a5a4e2f09 +552c34e2432b0df1132b130841000d4007232339a2092a593f142b0a0117 +0931432e452d3aea1d02587d3a3e56ed2a3050e2f9363df366331e421947 +0250094823545b20163f1d0a36a92228ed25564d1a304deae8035c32370d +4314380e264e2359e6a412504a424328e84434ff30236649353315344a00 +25e33540550d3c15135b0eed451cfd1812eaf2063f085d6e214d121c342f +37513b2d0a4e3e5211372a3a01334c5d51030c46463e3756290c0d0e1222 +132f175e4c4af1120138e1f2085a3804471f5824555d083de6123f533123 +0de11936062d3d2f12193e135f38ff5e1a531d1426523746004e2c063a27 +49241aee1802311611a50de9592009e936270108214a0c4213a01f09545f +02e14d2babee204a5c4337135821360d021b7831305963ee0737072f0deb +1512371119050c0c1142245a004f033650481830230a1925085c1a172726 +3be62f230a4b50526ec9345100252aa729eafa59221b3fa517304e500a15 +5e57f231333c3d0c470a47551733511031362a3bed0f334a3f3136104230 +eb24015d051a151f245905061a37ea273d2239fe02463a5e314d565f0457 +23025f415d290a594e3b5940313347a11c5e41531ff15a385a183829780a +51e0035f2deb3b163eabe8550e2e0414491f573b5419234a28183044e112 +1d54e8390b26585f3aef5f14206672240c4a5e5d31e01b4d406e351401fa +e555173e242c753b275d4ee50b2f26501402a71b1b5733ec19ee34284aed +2ee8f023401c09383b084d623ef324ee5a33065a6d5e365b092c5d0d4501 +3f4e024d4b161e144d5e3b140d1e2944465b491d265603a705373c231240 +544f0d4ea6091e00e62d3e130d4f005139f339001a3b480c221b730be75e +5f1f4f3e0a0dec3b5128e32960e42d0fee02275528154b10e65c36555a2e +ea3e311b5b0f5f220b1f1b2914f12111f41213e06232224df5ec0114470d +51203f1e01e5563851284013514a565e53125223052f47100e5011100201 +3f5bee2305217838582be55958a00245265b0308ec56525b5c114c2d5407 +e6e74818e53602160e45372029eb4de72754ec3f49290d2f5901014c0e7f +08e715e612380a5c1908285a1222073a023c562907384e4f470444483f34 +1110382b5225343ba6092133483e2d683e1e280227084a1e405e3a341513 +415f240f0c53e3f7196e2252fb0105347f345e531f535a344bf439220916 +5722e7f7fa2f4c2e057e2a025e2dec31413439aa12265f5a3458f81a4b15 +135839401856f337a72fec475a060de239a650163a55392a5b303f051415 +56090f18023a2b16e2364407050d48e1541408281d3aa3e84c5b264c1f33 +1725f9540aec5e10ed293e4e5a5a2d2125f053251a55395d1c2044022231 +292d523ff86a180620075f325e02566659f30423525a053a01f0087f4b3b +17fe493808f25309251e1325596ce32b42311e5d0c2f58652640582a4b17 +67381a5afb7128150a0043e45b173d2111155c49092d2635370a3a201826 +e62d021d36e03b205d5f1f295c094608342a412122583f3bfc34190be62c +393a055f59060d454a235326e844243a30285c14e316272524f4f0444f51 +352c3c5b2b5845244f55494940194721f80b120f07392b7c2c5a0508111e +2f1219430151e60f11150b101e295736361b1e053e4d08f83f230e2c383a +ef5b1d492610e834330f5cf3a2485d324f2822084f41111f582957191b19 +1e3e223704fe1d2e1f592753e5550f15170b231b4234e945301f5605a670 +300d322759ea0337015c662a0e073809543f2741104835512d0624551751 +373727ef1f41084d0b5c0c0137283b1337026aea1c5ae115064ffa183402 +09152b11e1233e5a0e302a521c5a33181e180026463744a82c024b4bf04e +1df61df1263fee59135c13400950153d3c5c59183b020b1d2d2c492f4968 +e2000c405a01ede30c4c082e2537443c120f38fc57c43651423e5c3beb1d +1922182420191b293e163d58020b005f454a0621051a38e80b090a463ee9 +39513f2d47042c0fe5134419ec48490f150f323a5ee7a7e0201e193a5e1b +2037200a2b1013567b35fb4a0f322c2f49435d091920521c302b413f5f35 +775d1a345b483b35a02a4c3e17ee3a3d5a5b57153613264f23041922432f +35125b3e0a1d2257eb002a26455e1a2f042e1545e92f0b3408032c4f3551 +2d4c392321300a18ed4f3e2c314d20500052aa3917e55d0d29500754282e +381b2e263758f63c474a1c23110c2d5f1c220412e91043580656080c0427 +081ce1e5350b6a3535f0e6592e5b543432340e38f008e0324102e45a3f25 +30040c181615362e4d1016160a4a5c006eeb1d2422355a3f1028ff192a07 +53f6354d4b5d121974245c14f0225713331f2e381810101428571725e432 +1a2c06372d5b1419742150042d25003c2650512834ef16e51d183f0f0508 +3d191107251100ee2e4125405a44174f061e0e1e5959e606530e06ed245e +3f592d47512dec5922500e460e1de7183b4c3c2e583942255a0c5d4d2305 +3438001e482a002d56113a1fe13bed542d3508e22f4e22221431121c1539 +ed445a5d28415073eb18022ef836274d573a48090f2a663058194901405d +215b143954fc313c1e28584b51e729ef31013b232bfb4c52e2322a2d4557 +5244102e1c3d304450ee01761924e62ff2173305e15809102b2125284dfc +171a3f010f3639056f2be71c2047581de32e05a20833e1221b0e25362459 +2958280de238084f5a1c292e005be71f3b311e1f415809383d3862260238 +361f56ecee120156375862eb3627185c2519545149e2e50b1f3b0c4e3352 +e6115f440634e4005d273611e41c5d383c3814537b3d23362b084024345b +10370656372e0236eb4f3303e216505f0e465228383729394faa2f205f34 +2e125b2f2c1d0f1f170e0c51331f0c06291610345c0603791f33253f0e0c +1c2b080526133aeb3e23571d4cfa1e48057a2a010a490a50391b09514f2e +59383ae11237e5450029162d2e1d3e09221a160e42ea06ea0ca7c7ecf4ea +3d3024f34d5c07464bea3b185e110d3a10395d3b2632343cf30ca2e6065a +262f111c0e15441a4825111b185f1e5756243206125f4603e97e79582d27 +2d5801ee2654113e2da00b58e9260d643c10423e1d1f42093b0d0f7d5102 +3649211f210456051e290f1b4c584d0749220c280b2a50531f262901503e +52053e3e152b5b2b4415580fec57ef5c08e5ed43cc2d2e5b40355d0d2017 +6d3917263f030c4b55f0025d501e57504a122729293c4c5819680d3001ed +1e313323324e5e177b171cf70c371541395c0e2b7726e42505483014362e +1910e4f7253f0a012057e03b1e3b4201362b224ff60e0b3a1d115b043957 +200c1e0b242e5e3b4755f61e3be05c040908f1234358e55562711d2efa0f +0737e0160b1d13132044080d2325f1f0ee2f00354f2106471131020a5d0b +3f21060de62c052a17576e2ce729242b3e3621300627f01e52580a480050 +1b381a11351f4f5d22040c3c4b3e7d263714e8e61a571d107a34260a4a51 +edf52314e111207c0b23eb482f441d211f306137152407040e08530a783e +3c054e2d4e2905275e640220f74f1a193f54e1ed5b4e2a290eab27a55147 +33522817335316ea2f3df957e25e02030601514f09f74c2fedee102d3114 +5d05231d03313826164156110c44e4111f4658005e115e300f413b430300 +380bf53a4331f74627492c133fe8eb3141ee39040def040c1a0ae914e3ed +5b00f0211f0a091e05582e22f05a5d262e0ce352251d25100b102b11e339 +36053935f051f959093252411e2d5af81f360c0fa15d0b373b1d26323b77 +501424184202206215e05944505c4817514540445b0207025de05b050932 +0a5a114515536f553a352c513f0b12f700345fa51d5efb28222676e559ea +561b0557403f5f534a574638411e2d3b3c133f79555c333215e6f5f9e7ec +6658f7210218110f00062752e305f21601442c5310162445ed4d175630f3 +0e2154253c4a22f02e1b0933351314071b521513235031250c18120024a1 +e03555453d1e31775f37331823164c341c09e310463438481019fb0b12fa +37eee654410e4007501f2c0e42faf50125075b2b46164f165a1003097f08 +2a5332145851553926523965582e5b2f530d5d1e292046344feaed461517 +583d2b06251f551d2f5451110911e6034147481a05166e1f241a5817015b +1f2d3f5c310c315402200010e24135592435f71b4640540a041012ee1b3f +5b2010060e2f5a4d045e0b36192f79181b0732183b4a261038340032f434 +3a5557340be6f5315c35112912393503320f54065f0e275a3b5853352008 +1c595d183539220eec123478535337110424f90a355af44c267be848173f +41053f5cef5f6f56e4f5410a5407281600200b2649460a2e3a3c38492a0c +4c071a57e9356ee415103c5c53e254063f2019340969e30a2e381d5b2555 +32042f46431d2c44607934ed180c1028136a5f2b26092e3b2c4e2930585a diff --git a/data/c6_s1.txt b/data/c6_s1.txt new file mode 100644 index 0000000..cecdb81 --- /dev/null +++ b/data/c6_s1.txt @@ -0,0 +1,64 @@ +HUIfTQsPAh9PE048GmllH0kcDk4TAQsHThsBFkU2AB4BSWQgVB0dQzNTTmVS +BgBHVBwNRU0HBAxTEjwMHghJGgkRTxRMIRpHKwAFHUdZEQQJAGQmB1MANxYG +DBoXQR0BUlQwXwAgEwoFR08SSAhFTmU+Fgk4RQYFCBpGB08fWXh+amI2DB0P +QQ1IBlUaGwAdQnQEHgFJGgkRAlJ6f0kASDoAGhNJGk9FSA8dDVMEOgFSGQEL +QRMGAEwxX1NiFQYHCQdUCxdBFBZJeTM1CxsBBQ9GB08dTnhOSCdSBAcMRVhI +CEEATyBUCHQLHRlJAgAOFlwAUjBpZR9JAgJUAAELB04CEFMBJhAVTQIHAh9P +G054MGk2UgoBCVQGBwlTTgIQUwg7EAYFSQ8PEE87ADpfRyscSWQzT1QCEFMa +TwUWEXQMBk0PAg4DQ1JMPU4ALwtJDQhOFw0VVB1PDhxFXigLTRkBEgcKVVN4 +Tk9iBgELR1MdDAAAFwoFHww6Ql5NLgFBIg4cSTRWQWI1Bk9HKn47CE8BGwFT +QjcEBx4MThUcDgYHKxpUKhdJGQZZVCFFVwcDBVMHMUV4LAcKQR0JUlk3TwAm +HQdJEwATARNFTg5JFwQ5C15NHQYEGk94dzBDADsdHE4UVBUaDE5JTwgHRTkA +Umc6AUETCgYAN1xGYlUKDxJTEUgsAA0ABwcXOwlSGQELQQcbE0c9GioWGgwc +AgcHSAtPTgsAABY9C1VNCAINGxgXRHgwaWUfSQcJABkRRU8ZAUkDDTUWF01j +OgkRTxVJKlZJJwFJHQYADUgRSAsWSR8KIgBSAAxOABoLUlQwW1RiGxpOCEtU +YiROCk8gUwY1C1IJCAACEU8QRSxORTBSHQYGTlQJC1lOBAAXRTpCUh0FDxhU +ZXhzLFtHJ1JbTkoNVDEAQU4bARZFOwsXTRAPRlQYE042WwAuGxoaAk5UHAoA +ZCYdVBZ0ChQLSQMYVAcXQTwaUy1SBQsTAAAAAAAMCggHRSQJExRJGgkGAAdH +MBoqER1JJ0dDFQZFRhsBAlMMIEUHHUkPDxBPH0EzXwArBkkdCFUaDEVHAQAN +U29lSEBAWk44G09fDXhxTi0RAk4ITlQbCk0LTx4cCjBFeCsGHEETAB1EeFZV +IRlFTi4AGAEORU4CEFMXPBwfCBpOAAAdHUMxVVUxUmM9ElARGgZBAg4PAQQz +DB4EGhoIFwoKUDFbTCsWBg0OTwEbRSonSARTBDpFFwsPCwIATxNOPBpUKhMd +Th5PAUgGQQBPCxYRdG87TQoPD1QbE0s9GkFiFAUXR0cdGgkADwENUwg1DhdN +AQsTVBgXVHYaKkg7TgNHTB0DAAA9DgQACjpFX0BJPQAZHB1OeE5PYjYMAg5M +FQBFKjoHDAEAcxZSAwZOBREBC0k2HQxiKwYbR0MVBkVUHBZJBwp0DRMDDk5r +NhoGACFVVWUeBU4MRREYRVQcFgAdQnQRHU0OCxVUAgsAK05ZLhdJZChWERpF +QQALSRwTMRdeTRkcABcbG0M9Gk0jGQwdR1ARGgNFDRtJeSchEVIDBhpBHQlS +WTdPBzAXSQ9HTBsJA0UcQUl5bw0KB0oFAkETCgYANlVXKhcbC0sAGgdFUAIO +ChZJdAsdTR0HDBFDUk43GkcrAAUdRyonBwpOTkJEUyo8RR8USSkOEENSSDdX +RSAdDRdLAA0HEAAeHQYRBDYJC00MDxVUZSFQOV1IJwYdB0dXHRwNAA9PGgMK +OwtTTSoBDBFPHU54W04mUhoPHgAdHEQAZGU/OjV6RSQMBwcNGA5SaTtfADsX +GUJHWREYSQAnSARTBjsIGwNOTgkVHRYANFNLJ1IIThVIHQYKAGQmBwcKLAwR +DB0HDxNPAU94Q083UhoaBkcTDRcAAgYCFkU1RQUEBwFBfjwdAChPTikBSR0T +TwRIEVIXBgcURTULFk0OBxMYTwFUN0oAIQAQBwkHVGIzQQAGBR8EdCwRCEkH +ElQcF0w0U05lUggAAwANBxAAHgoGAwkxRRMfDE4DARYbTn8aKmUxCBsURVQf +DVlOGwEWRTIXFwwCHUEVHRcAMlVDKRsHSUdMHQMAAC0dCAkcdCIeGAxOazkA +BEk2HQAjHA1OAFIbBxNJAEhJBxctDBwKSRoOVBwbTj8aQS4dBwlHKjUECQAa +BxscEDMNUhkBC0ETBxdULFUAJQAGARFJGk9FVAYGGlMNMRcXTRoBDxNPeG43 +TQA7HRxJFUVUCQhBFAoNUwctRQYFDE43PT9SUDdJUydcSWRtcwANFVAHAU5T +FjtFGgwbCkEYBhlFeFsABRcbAwZOVCYEWgdPYyARNRcGAQwKQRYWUlQwXwAg +ExoLFAAcARFUBwFOUwImCgcDDU5rIAcXUj0dU2IcBk4TUh0YFUkASEkcC3QI +GwMMQkE9SB8AMk9TNlIOCxNUHQZCAAoAHh1FXjYCDBsFABkOBkk7FgALVQRO +D0EaDwxOSU8dGgI8EVIBAAUEVA5SRjlUQTYbCk5teRsdRVQcDhkDADBFHwhJ +AQ8XClJBNl4AC1IdBghVEwARABoHCAdFXjwdGEkDCBMHBgAwW1YnUgAaRyon +B0VTGgoZUwE7EhxNCAAFVAMXTjwaTSdSEAESUlQNBFJOZU5LXHQMHE0EF0EA +Bh9FeRp5LQdFTkAZREgMU04CEFMcMQQAQ0lkay0ABwcqXwA1FwgFAk4dBkIA +CA4aB0l0PD1MSQ8PEE87ADtbTmIGDAILAB0cRSo3ABwBRTYKFhROHUETCgZU +MVQHYhoGGksABwdJAB0ASTpFNwQcTRoDBBgDUkksGioRHUkKCE5THEVCC08E +EgF0BBwJSQoOGkgGADpfADETDU5tBzcJEFMLTx0bAHQJCx8ADRJUDRdMN1RH +YgYGTi5jMURFeQEaSRAEOkURDAUCQRkKUmQ5XgBIKwYbQFIRSBVJGgwBGgtz +RRNNDwcVWE8BT3hJVCcCSQwGQx9IBE4KTwwdASEXF01jIgQATwZIPRpXKwYK +BkdEGwsRTxxDSToGMUlSCQZOFRwKUkQ5VEMnUh0BR0MBGgAAZDwGUwY7CBdN +HB5BFwMdUz0aQSwWSQoITlMcRUILTxoCEDUXF01jNw4BTwVBNlRBYhAIGhNM +EUgIRU5CRFMkOhwGBAQLTVQOHFkvUkUwF0lkbXkbHUVUBgAcFA0gRQYFCBpB +PU8FQSsaVycTAkJHYhsRSQAXABxUFzFFFggICkEDHR1OPxoqER1JDQhNEUgK +TkJPDAUAJhwQAg0XQRUBFgArU04lUh0GDlNUGwpOCU9jeTY1HFJARE4xGA4L +ACxSQTZSDxsJSw1ICFUdBgpTNjUcXk0OAUEDBxtUPRpCLQtFTgBPVB8NSRoK +SREKLUUVAklkERgOCwAsUkE2Ug8bCUsNSAhVHQYKUyI7RQUFABoEVA0dWXQa +Ry1SHgYOVBFIB08XQ0kUCnRvPgwQTgUbGBwAOVREYhAGAQBJEUgETgpPGR8E +LUUGBQgaQRIaHEshGk03AQANR1QdBAkAFwAcUwE9AFxNY2QxGA4LACxSQTZS +DxsJSw1ICFUdBgpTJjsIF00GAE1ULB1NPRpPLF5JAgJUVAUAAAYKCAFFXjUe +DBBOFRwOBgA+T04pC0kDElMdC0VXBgYdFkU2CgtNEAEUVBwTWXhTVG5SGg8e +AB0cRSo+AwgKRSANExlJCBQaBAsANU9TKxFJL0dMHRwRTAtPBRwQMAAATQcB +FlRlIkw5QwA2GggaR0YBBg5ZTgIcAAw3SVIaAQcVEU8QTyEaYy0fDE4ITlhI +Jk8DCkkcC3hFMQIEC0EbAVIqCFZBO1IdBgZUVA4QTgUWSR4QJwwRTWM= diff --git a/data/c7_s1.txt b/data/c7_s1.txt new file mode 100644 index 0000000..c02ff8a --- /dev/null +++ b/data/c7_s1.txt @@ -0,0 +1,64 @@ +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 diff --git a/data/c8_s1.txt b/data/c8_s1.txt new file mode 100644 index 0000000..6f74d41 --- /dev/null +++ b/data/c8_s1.txt @@ -0,0 +1,204 @@ +8a10247f90d0a05538888ad6205882196f5f6d05c21ec8dca0cb0be02c3f8b09e382963f443aa514daa501257b09a36bf8c4c392d8ca1bf4395f0d5f2542148c7e5ff22237969874bf66cb85357ef99956accf13ba1af36ca7a91a50533c4d89b7353f908c5a166774293b0bf6247391df69c87dacc4125a99ec417221b58170e633381e3847c6b1c28dda2913c011e13fc4406f8fe73bbf78e803e1d995ce4d +bd20aad820c9e387ea57408566e5844c1e470e9d6fbbdba3a6b4df1dd85bce2208f1944f1827d015df9c46c22803f41d1052acb721977f0ccc13db95c970252091ea5e36e423ee6a2f2d12ef909fcadd42529885d221af1225e32161b85e6dc03465cf398c937846b18bac05e88820a567caac113762753dffbe6ece09823bab5aee947a682bb3156f42df1d8dc320a897ee79981cf937390b4ae93eb8657f6c +ed9eccbe79394ca0575a81d1fa51443aa3e83e5e3cdb7a4c5897faa6b4ac123c1dde2dff4d7c5b77d2aa3c090cebce70340e7f0e0b754ca26b9c108ca0dbfdcd8aa230eb9420654b252ffc118e830179cc12b64b2819f81edcc2543d759c422c3850051d543c9bc1dcda7c2a6c9896e1161d61c3c13c80ee79c08379abf3e189f2ecf5f997db17db69467bf6dfd485522d084d6e00a329526848bb85414a7e6a +4c0a7faa755a8b877860d60f62e4a85e7319b39628d509211a00026d4d278dd7746c6feb6bf232d38298a321e48f8172eadb7782e188e1342bf088814329608ae756700d7786ab99602e83ab084407e05c349a0d72ee7662005784694d98fdf1d2e08efce71d14d940e7c4105d3fa5095454fe478401ba38f98a4eebd38477c613972c86f08e69f9e82e1ac09e67d81238271bb492da526bb1897bd330fe7b75 +dd6a92b0659968f01d7b638960d747f7f0a0b20460de239b8f16d5a95936d1a4d9f4d3c77f5b14942d91304ce572dab54c8e4c01cab0df8d7653f0ef9fd486191a0ead2f1cfa71b30d7653322fde828b83f4ffafd2060727acf2c0d4062ed24fc9608bae7ab851cce4fde56d4ad593ed775ce856d7299e17d5f88325dddf7e268534710d3510ed24093d217f199afdb650581ac7962d0e281469e040beae01e1 +b1f6508b1321e924066febfe8030a908e8086eb5ac423895b74ae91b9cea65e9d4249057b23b970e23f0b87b641b98cbc5fb7438a2844fd949a937f05f7670462266c3927177a2bf3c5873695ba9334c0d57e749e2132df586899c88827cad98efc7c9d74f001f57b31d3826e4448067ff43b2ab045a712372ce8f8a229e845289ecaa2e038eefc9ef4a710509f4ea14e695cf44977cbfbb1a9d806e43fe1af7 +cc230392928a4146fe2dfea83e2567923bb2676de8b879e12cfaa09379bef6e599aec3187be62a7335b90c269247724880835747e302068a3325ed3a02feedd5c129aa8d2846db88e29ae896a8d9355483a54bbd70e1ed628c78a8eae97a3518f33b6d9c4d04ef3bf79df7cd1042eaf209d6d52a9f1f293ae3a699a3a3dbad77a9cc0d4b0b47db49397949b61ba6be52a140a3dc7dee41d106609df433d587c3 +da56b86bb13657ddf180fec84bc8070fac27fded7d3d659d7951d1d7bd2b3c015463c2118fc4be14cb3c21f1249a6509d5b409d3181c447f0146ea80efc324fa5484d4fc6be037993d75d4f5deeb33ca0c71fca4fcc19ba6b6d8900ef0432e15d82a1a9e494f2ef12d4d3e3f1ec40f5ce75a6b0b2fd21656aae26e47af8ae60d5fba3dd86c9fe2dd116f8f443408c004ee826168dd888b3508b4eba633eddb60 +855ad6a9215ba9db1eb2fc024fcafd9af2d31b81a60341b6024231cb1a6a1e291598cc886f042851c8f218e01a57141932b6cdbf3f19fe705a7fb7db0c9833f49a30906fe7d5e2a178fe4b92e089829e7c14da0659ce76441e0c17a54acd7b3840769496868907e83e9d2fb9fd2dbf1a230a0ee15eb978f2d6e12f9d63a686bf0361503a4e4234e8984c68b0e0882f3a0261bbe1d248f4c107ce2453ae63cbe6 +abfb8e76094d4c27a44f3170271c9c0e312c192f42bc9597c928d8c9e461f5e2ffb555283e4c07914f82dc6dae5e8f3b3d4dd5cb3ab1ce8f0d8cde59ad92d5db56e2c6f584c1ede4a31403141ba42b18528d501f62c5cad6950c8d8f14c1c4439dcc27f4f20cecb1d7559758d5080d88fc7ec9045a201442027baad56ae952c5be100b55291d843720c92d10547022b12505e2000084ec72b069afa60f15258c +0471984bac512f7b486ddd641cc31823e66c7050ccec2ac71cf85f3493b32d8776e9d486ec29f8ab568451c8b60527d52b15d152db1b072acce870e3ff3541d814c52bc1393e416797a423c88d1563704ec8c16d820320063fddb89592d5c2a24e978fd5fa44fd0f8f76821b359caf041edc75f06c235ddc2561198553f1cdbeed11ec62cc9af29235ff619caede84679bb25f520313543becbb79becbfdd509 +629f595ea8e740e8a3505770ef70d3a6ab74b17c6879a1983430e607b04c232657b8664557455b0432bc8c5913d520c3e82e29aede851d30bb2796216edf3addebe34afdf478d76077429006fdfcdc26dc054c5829c08cccdc330522a59316837b53cf5c38bf84d5ab1a536371e50d187f2aecb260ce79caffc3ec77a4bf7698174e1fcd4635648144e2d0d983715656f6efda616271f6baaf9d79ff8ee7c017 +d72574d26e0273ac584fdc8e149196b528b3c8fbbe053616b5017e5638cd6ec86119712e32ecff6124ed3ef268d6a80f3e7e9fe9f7e891acf29392fd0391b77c0da13559c9aa963ec91882ba1e53d81d07056eef4535776e894154e07edfd0efd53dbff0a1ceaca47aeca4bdfd733d7a262a67101b691862d773c15e90254786e28ac66e559eaf756ca2ba342fcf07ee14c011998683e335c4f3ec60c9c15f15 +6037304b0a7baad814a3b2ef8fa0be71beef741ab72785537e82b844609b36fc07822abc20bc28afeb8fc92fe47aa5164d1b7f89039094ee894d24f0fae9b32b14008033361f90c0b1b748bd555a0026d2203f1949b082421e2c7039d450890715dc7b5c7538da3607b7fe232b19fcb9b7dac6e8ca55fca01e12c868edd170f17567cca171e1c731d5c1573d1635ca8dd60eae9089b762d677aa0aab2ef95097 +251c7b45c01ad596fcf1732283be13cf9d2c0ff8f2a5b93fbedb6a79d28acdeb54f88e24ae1c9a02cfb0856187217287df5ff63cb43595822853f609a789b3d78fffadbd891a90867b1568e2cc551d362ac257a94d91a7eb0028e6e9bb958ff7fd203b84ed085760d4c349268b635f0759f356b7efcfbbe3d9b54583c43a38590b171e3d554b67201b7f7353ceaeb1fb098046a173c105b07031e94185d0bce0 +1b7ad8547ce04b015719fddaab0464f3a510905ea630a8feb7c9097486c52d7b4d41ef61351ea4086ccea0bcee8275cafdd00730d00b126d8dc2b393cbd99f6ca9fdc43e595aa1a5e664d6a49577ef73e1242ff47529cb7c106c772991da48bfd0c190eced3845546a021781ae0ff8acd94e44f67dda15637c5a46072d3543fa251d36398786303e7f2192c71c60b48f3f0a59e21014bb566ca76237022c041b +fe2977f56f9f414ddf898d21b3e13a48116dbf3ed13253ee69c2005ba1afd30d0665d03e76820e172af73ee8bb3ac387170f81530d73debf97d9dc73b6f4073089ef48ef43f24b0a0e69e2ed1f469482f9c74ae3618399bf6bf99bbf9a6476ed1d3398bc9a59f90feb131b2147795c39a38ec4ab883a3732f9a4c01fd3600047973b361d60f8457e63abbd29d7d73415d89c852305649125533ad73828e34d2e +e437a598aa79834390aa01e784b78bd452286d6258dc6ecce45f8bbd9f372a74c775c19eb37ea5308205ef05971cd6c7ee041e5504abf8f756f2c215563b12cc234ab1d9d709fe31a77707d2c7dbd5a88c75b01746675e3f242a56e222fb60206c88472deb198e3cc1a22ffd4005e2da131ef092cc78ea954d85453a29faa8185abdc35fe1ce1484e6f2f537fbbd84c08af0070ecfabcc3678b083e48a2abb87 +972d40a3fe523a117967480b2e3ae1b7e513be738d708ff579e856b0bc19e2da4d4c1d2f3143720bbf3d58f1a067799e8209cfb3f3a4f75d920ef269e839ad3733e99f08b1785dc2f053b37c04a4f7051aef25f27c335bf3ae4873cd740e088002f7ac499b78c17345426e159fbada0af882f47b78c0dc520b901573530082f0c370bc8af1ccb4ed58b08806ebcda8b10ecd3d1d4b917eeb2cfbaaf3ee967eac +05ab18cccf11529d76c7fb4c4abe28681241d2cbf92db2cfc8739f7fc1b7a4dad4804a8ca0e8e33bf1e9737211a168f457176fc40be85796fba14e2945476e05231b58a4c41d250cce3eb19b507db40dee7c75d80721f32e15f02cb2ad21ad0ac0624b97ebf50500b613ea4fb875a1aa99ef6ffb1143e5d87b3111563d4a1a6bad39c1173448b7b1874a4e990fd13c4c93234e1ad3a7612b0454f83c0aabd22d +c95794a41d00f818a2d9a9c3d8367829b5c47be92826245228750113158a654316d860deef07d9b26c50a04fa5b0073ce1b8cb46177b0b0c02423807bc7979076bf5d1f17e32b06a6d5ff8eb906585f570eec97d1223c516faf7fca2ed26e3d86716fe238f44cdbe0290c7d87a36bc368dd09198b1b7abd2745c0f06042f4bd5dc49697a0a8d2fd88f164b57f67615f401172d3eab3694980849edc60703754d +96b0db74959d0c3ec0819f3d7239d8f4e74c0fab50f8a99130e5d2c80e4917f7632679ef0bfd303ad61fe005fed62afd574a75a014042a4b75eac3948886b2c99cd9bece7cef4fbc86c816b9cf6441040593759adef2ce62fd3bd97dc4d496b02c3a883172610af18ed57322d98b0b7f51776229969e27a06b6445c22651b6aea4dda50761d04ccb3bf1d99e353d11b967f24c08c1eba16beff4a2898ff1e641 +9eed35024a40add6409a9690e570ef357dfc0b38491706783dbb6043bd4fcdaf01986fcccbf89f15bc53fe4aff70821b309aa5cec59ef3c588c1042593f9994644bca862152a20bf94dc0d288176eb9f49b7f814bf35050e83b139d2dbd5f08d3cef35e271ccc6d8074fc5fe1570886a0746ce19be8cea27c4382bd04d8d45c7b7fd9e3e89ad38eb37656577395fa0062e5f8e15be2c9a4833bb1f2fce90bb86 +b5eea554b30b184185899a3c7c312cb1ba0c3fcffc406412e0b3aed64f3125cd227ec3c338af76fa5559f70f7a71b85b362b97c72aa402864f5fe572cc749d00a9513080fb0484224f039ac899d22934d2b24e3a3d96982dad4e04eb20974a2bb3b082cce404fb81739e49225fd3440e2c8ea6ee34e85706597a2edab42383adb134ca0a6461af0a530dbf35add348a0b879ef28a66a940ab13f113572e18e2c +9c87acba9cb25609ff339e3ebc5cecbda640690223d05f67c3a394f451b869c5822d6120976381eaf127624a055493b2dac4376c8bb87c0a0372456e17ec09318537b4670061a8c69da4d10bd6a24ae0036eae1fd7e486d0d184241cf22b9318158534439be1a9b983e7f8d5292b423db57144cd68e756380debd54eb6589322b7793289d08cd5b3149780b791bffd25b9318ea1e30b031171cbb2d09feb9715 +6714d76c6b6779e80bc285a60e8cc599e15d68dc86b48ec6f236bbb4e35d6275380514d9febee8bcebac6f125affe10ea2767c31bf0a52bb1652116fbed7d348bc0e7163b6a2d3ad2d3871f972e9539e8e37bb90a7955a44f865de35467d7da098991760cc56d23690c12ad7049b0dc05c21d31ccbfc08375c1f171efaf223d1401ac4f1aa59573c7c429ac8b5fccdbfcdc6bfb78a378334339eadcb46ce23eb +a5954615b2f4fb346d49e867dc890c4aa0a29c53f80e2c23c73a6610aa64095df8817d34189b79b68984c9f9e2f92c52066a059dfed97e610cfd0f23cde1f4ebede8a049300d683eaf8f38bf36c627a3e8f3a68e134f162d021711f79d65570835a33f207028f5330c03a5cd3467b563bf53d30f0bc6ccec32c3883992314f6ff55d176814a0b213b124b69c0323ede75c4ea53021561d38ece0c822609f6a30 +162189160d7682e4a71ab44ce4bf5c74fd15e01d2ccec755a0b1a9da4b07515d79c9e889bb372bc553d2f1f2f3cf8acef44f29dfe771dd06e18ba68b6ed5cf59b923d6182f4e4e5f5a043fb4be4fa98515367f3c1db2567f7bd9fbce48782578800cee1fd570fa839a5953c1962c2871eae88f78c4830a58cf5311f3945ffbde897d1305076e72b34ed4a00ac3c59aa4e70325a5e40cd332a663835e81042283 +85d5934dc56a636fb3bf963e9b26dcbc69e3227461861368c23fa43f989d85ee1ef7e661e193abbf7926ee82878a2cdfec132e75660b32684df6cd35965115710be138877dde090c4bc01cdf43a82476b330ff1d1f2989e88e0d7eb4108163f99748289fb5264f44935b4f8428b2cdc49e95dbd8cc6dc4a2957f23ec7bc549ebb09d025d8e9d0bba765daf955e469f43f706d901a323c63ba51fc55261d9ad9a +72d8a640970bf9ce9f9f3a87fb908eebf7e603fb0589a00893381cc6662726130bc564c42ccef93447bb74065650138396d1452408641b05990cb6c0e73b1a93bed3e428c70a0a073777089d555b9195fb371bcaff16a24487918737f2fe4d43597664daf610196b3dbc25ade7e68cbe3be39712a1508e3a85531bd175c6484a48058104d6d6f18fd71db2bfd61e72987b092edced52e3f848e7a39e90ed07f5 +18a498742ea54c0f2cb6548b014bcca1cae40ecac2c92a36fc359b95814471ad4f4d22782aeb8d6f66136c4cc17cb38a339a825e44cd6e41e75c578f61271e0ead4db3f46fbf55596e3c187a0a47645da7e671d1d7390892c0e8cd7e939de470cc21a1d427db9214c6e92f49ae747b00bd646bae4be4feaa0f7f14feafeb5de3bd3dfbb6c4d2320fe0598f99006c4e26d41b9245c6dbea2b7d70bc8176ef7e91 +7ac2e99dd57d33ec02f7f970ea2a2a30b4521b53b18768cf885577c2a0ddebc837444c363ffeeedf87ddc938114c0a7e75e53798938a87f6c9f8c45f23f016b47af4475a5182b08c1f4c3ddc0537906a7f0b6574583ede7c1e71e0ab2bc0b89a1f2d08aea2e2df1447e283d7513022500363ea1a94d2bb224c5483fd7e921c0d86470a02df90065c3d2a68c80526c27b7b1a4e8109acf29dc15eb3f0ebee54a2 +9b6211054cf023051dc6de2f154d048b28760d017439e4acf15ef9399c44ff6f792698b9f68b8670ce4ae92acb7f3014db8d76602d0e105e5c25a8a89cdfd67a0ad97742ed351921417ba5a3dd6093b65adfc81866540d2717d6055d11a901064edf74decf459f91c5b01877cb849603c00e992a3fd2dffa0e4275ca8fcdf1f00d97cb9018cea728f84a4a037276737bfe22e913fe4a019d56b4f4b810d1578d +73bfef877342cda6e7656622003ca23cf207d0d87cdf7b710d6f5a203d9d0a1873f7b85541b75bc29e8d0fbb1a2af867f796715e7aaf0325b54a0b0eac532267c1aa4d92204538fd71ebe107881504053ca9b8f60664c6303256af6b388f41ae196c9f0dd566937ac72ae61100bd37404c4a3703bccf6d1c570768f8011771c40f9d2d7e0803e67fea300c9f175f3c81ea98e766ea6f962ca0f999e0dd59aa88 +e18d180805692f072599de2ce7d7bb126f59aa34cb09a51c9c4c4e8cb939211040421ea2973f1b968ebc1255ba836bde06ec9c81cacc7827af41c4e1d1c3334a9365c6d20c248214c30fdb59955d27625f02a93ecfac6392fcd8ac871ccb45efd710b93517922e82171a3ab1c700912014905ebc444699b5fdc2256c84ed3cdcc2993d54c9b971c7200cff0d3776be30eeeb3e7e3533bba3c9242b056f1c3db1 +9912d81db2a9f7fd7aa71ee6d6bb3d4a2171891764b8c2713ffdc385fc5e477be9815ed3ac2cb4d22459ec7bc1899145e6010ac52a43f83c9a9bede255d9f9d56ff129178311cd369f65e3e652324d0cfabbee46ae060bb0b3028f7ccf392f94fbba7541bfc60b40d046efe29d3f973eb73ea064eef35a9d6d1b9e874ad5d0242dbc49e05c89f614af30b68c3d9ce86a2a105da1603e0bbc5ae25bb97eef20ab +ad273780a54c9b86c78d0edde05767983a2850a4dc517017d72f99fabb86387f5ac51885620376419a2835b59c5713dada58caac9970d67262902046ec09d8eb1f315e4d3e345ada768ea5ea795673f7243df722b0ad97de29481a351d9234246c628d5947e1c84d68145e4f37dd229bd35059713d7ef7ab426a6f4f2d4467ba9edc7d580f4d4dc451e05235eb78e806fa8ee260cfeb99ffb0affc5f073d47aa +5f1938686b21ca887be812db8a167d3c02ae4fa5b2dc3fff62855f1fd4fd223a33cd79f49296259801ca9199e4906d5dd59346e91426e2edc6a4867657b38634025fa48bae33b11e340c2dd179a933f4a8d246ef9081a3d5bbea945bd2847aec688a7cd791451a2cc5d49273a8341fbfe7d4879211e17e2483585aa845b30c6630183eaec7c56ab83a97e6ad6deffefd4afabf7216f0907747f73cc3c3cc3a76 +5c221b27b55eb9a86e9c33ad4821a0ed8231d498a35a76a6c9d090f923f507eff17cfdb871becd01adc48b2b595d815beccd8331ccb811b830459d9ceb4c80caa3435c8a240d37b29896d0c1168bcdfc026c39a40c7fa25e543ec6996ceac3450d512c5d469a2716bdb6cb05de0eedf84ed1a65261f3c120ce6073e74ee61a593f43d11357f86fb5b067e49e646f6120e2d1a566af2555c914a9053659e4644f +1c7c75d5dc7788fd522aa5b9802829db5905b1473a6763d5b8506780620d24ad33e750770845fb1db3ca2a6ec80b3166d77d3af62ffd55e5b5f1e691da70baca5c2e09dfc9756656660d3fdacb54a899b17c1acd833e914c84827a7bde8edd9b2334ea6f97e8ae8463dfd55190ee40265ac501fd3d4ccc3d4902e9d3cadd0bc0b3bf7368bd2448f0e0e041e3dd6252f1f2cd80cfa9e57e9cd2ac757d43751dee +edf041362a040258e823cb5b43362cfc999a6c7a24e5ad328e920f2bee58c68cf915e91b242100c7dfc09d548ab2149513badc28eecd58af740eef50c549bc817e229df73abfc183cdbf6f17720c934a6d911d40c3e57f9edcd3404a0fb9ae3c7b018dce904e1a9d6fa4583d23dac50fa24b2d8390eb0b013c86a8c4567a1faa13614917685abf7d32f049d70f5e598d79f9af8d57cce9722ae27a3d770f4b87 +5891e30532337de940d24eecd41a66b6d67fe95468d87b5a435c7ccf878b84f129a5da489fba7a6b1703a0d25ff3cf5821ad2ec5740323074f39684a5ee5f77b0cf38c7a26e5ffc0260ca7ce107a43986d74cfc884fc96cd5e547534722d6f8fb8bce5bde6b613edf0c58a885cc24464c32756b7508831658792a2e53c27c4bd5705df47221d0df2aebcaf96f62c71b9f295dea67d781b1f8e4e4c388c5da113 +b66e364a5dde79e0931fb1c7011cacdd4200f5c2d3ec1890a8328cb76d4f6188125c395e252ba5e230740fb639b8fcf3cc4376df816e336337f381f5d20332c55af9147e2f284000ccdef57b71f57f86b6580d3fa07df96312c18839267f613436f3d08bca2d91c1fb30354780fd00ebbfdf364cd4d95642afe0dc4c802399627f3035a80aef062c2cc32e4d10c315f9bc2cd6c8027adc0e84c6e7175e41920d +e6fe6ba39e62b5d74a2a5626edf7af4361dce0c68375a41da8d440542f2d7153a5c17b92a878759611b04352882bba761eab7c83ce083937cf70f7ad847cd831bd7bc78f2176f6cf7e6970b8a8d31b950ef381a3d758df9baa3327d8341a131e3974db3ec9aec1025833df854bbd9b8ee9040f7aec638b3204e2615f84f06de88350eb8239586f8d7f21a16065af4e63d0a1d357c9b97ddb5a9a9d0e0a7a07b0 +5a398d3939915edefe5a9a868d0007ea4290e9ad06e9cae3e3f5545146e1097441906cd746e742137d72b57db769599072c78745be3a61e75cdf39b36ed16b981020fa80683e7acca0a336f5d3e1114adf71b87b42dea425772feb5e11230f617f32bf4cb79f08059025ea758b51925181b06e7069b6e8c4417a41ff1bb356199201fcf2be77b8245bbd0c7bd5c4112d71e02ef3388cefc65c501387202a5778 +a6f59254597a80789198257b1c4afb3e3e1115b6abd757e4bfbe9a37b8e75a9fcf169909f75e9c431567f89599cc3f3def026657fc611336f691525a3f6f574f7c2ea81cf5b91fffc4407b99506f06daae682253e7f53448e17582a2a0f4e0446ba399f33e154dd83a2f50c90be6a194c6dae51d52a9f357d9307af734a9a517f9cbc37b20311cafec6786008ef81b5b5d7203c3507a12758dad1da672dcd089 +9fe0c137a408737bf0edda26dfed799300636d1dae89c806e91f0a7744a1e3a0c5817ccce30e4d0df932dc54271ab7ca6ce58e070e28ce5a7c08897dfbb261e54ec9781dc748ca1ae95874f14fae8fd98aafa2f85c66f3c4a09f0cc2e8add7b2e02777ff943645802fda6ae863b250f4d44e481996eb3d833c91e73a054c3787619a3e483ba88c78e3fc44cfb83223654dbf83f81364e80e20b667e926793575 +a7768cd20873b4bde23aebe2d75fdf7e89aebf00b0e29af26788cc8a39a20529ac1d56eaa8afd7e441a1194545ffbd5a253df1a45b7852cf4dd48934d8a6fddf83c2c2d08f8cef7272b72d8dfaf3353e230ed7f35a3c0b0c3815d59e52a0643df16cb294f36a98ceeeecf07ac03971b90791c23c3c44a6a9d8d03d11172aa79c399cb42f11c228bb96b8a73eff4de46a0748bd9cc2ce210423e9b36f357c671c +9c1511ec979946309b7ebe7cd852e70373b9055d1385b26042fca1f5a281b27094469783d25fcb79ff237a4193976d1d2e99cbe62729335d492af4df8cd374eb93299ba478732a0a35866d154ab247993f5a6d39bf88aee31d59e6f85c7ac540f26cb8e8f445176ea9d9901d4c006b8abd3549d0204c086bc37587f7260ac298bc91b2291241daa3c7cedf1dcfc4a07f5334561e562188a852d37961d77139de +dcc71ff06ae66dc397898a78042c5dbc7ba6e26239e3768e73dad3d50985eafc608bf11aa65b2dd87a2fb02106a0e2bdefdd2642eed4332601f5e5c1de1820391380cc20a965c7abd6e29334ad360719c32ec8141c5be5d5f20666f33a9888882ef713126f1b5e8ea670c4bbc89d659906a0426df9c6a8b508583299b5a23b187a1471db4233518bb73360a9b4da1ad89ab7f6a0801a3b469119dfbc423e2460 +fc98eae247986086a16a4c8f1c03fcde1c3c166c6f0fab5d69247679b8df05c7870b57ac0663598c1467b2831344ef18b2821e18513230c85cb66a738e41f7f44922efc06e7516c24a2e24718cccd1fb10fa1ce8415d96675f26137368d157a0199f4c083977a86f50579d786662ad0735f9f2c6757e7a1e9b21f0b0538e8118e4ba3824dd4362d4de815018b286863cc98bc0301093e29b6828796b07ad0f03 +72096e97889f59b90143012cad09621824645bb73933e7fc1678f7fc0e481160ae6f582a6571aaea67f17961207718056e22d4eb802c75093d8b9eead2a572307feabbbb50a724314535c2d7f8f4dfe447e48546a74109c3d2757b6b1d13401a8918ff3efa95301a0f1b9383a4848cd981fe7f51bc97123aa4178c69171fcd7e402a4f8d2aa9fa6b37b961375c1c23c9776549192fbd014b723c3b18e94980e4 +294d751a11c97374016a320cb0bf8055aa626efd89b001a2395015e685be976e3609769d4aef65a7a52d05306d072d00d96bbd97c7303f72a8636be03121b3dc038ed9a83a09b12a9441bf6894528bf05aef24910b70287c36bbfb008971971528da3985a795b53278b7895c9f21c09727e3503c5ca45726ad1dd3f9ef4b98b2f7148045406d6239a764196885718a142a7b1556ba17065195845af76fc114b9 +2614c5dc2763b73eb35a47a25c9a345bbbcb63aa0deeea220dc53212b484bb979cc7140c6fb8ce85925f3de771b96d3fc1876aceef6cc5163fd9234a17b84b539e2c1d8e25e1dd630a5de4a09b0189ac067164d57d9ae96f4026acdf30ceb0c1587a35873b560f81eb456fbc923d062b12d14823e1e8f979375c125a8750a0eba3a60572545ad6878da4f8dc4ff6287b3db60f79f62e71cc4208357d77d63f30 +e6d0c1937e2c536c51bcf19327a29e42521626310ba1311ec07176ccf1843d3b20c380781d6f9f7102bf612e2c3da981124dd6b98c1592ae151ca74916502f69567a47a4d19d3aa5df761e36ba63f29893b870a058cb28a57ebc1996156c610d7d8398b2dffdccabbb88bb2ed3a4d31659eb5f4d8d9ecdc24b93b67fadc1afda19d671fc8776bfad98715181a03b995042195d04362e6e67bf178b6c30e35504 +8120ba7045f7a9406aba4dfc0b38113652f0974a193e6d7c89cfc90af4274ad5ba6885c67afed567ba46c7b0a49ca1c3e511cf09c39ef1d316a40bae8d623fed41b0264c823107c4bff4cbbe0d8474eaa28e3fa0697db02afe74f73dc3a93bad499200183d91ef8cf2daaa964f0495cdbb0edbac1e1c3ca11f95045ff21a82c3c48bcaed0bf0318fb5f71083b721939541b284d89c529b51c64419a9a382a469 +635596989e52fa53e7d9e7e64e44ed7939d61927a1d703e2528d999eb59354a6153094ba23ec33685da97dc1cad44a794d87ce4223017e38964c9a6541da2a7ba15042ad615ecc3f9e705d63219dcdfc2abfcb1ddb13a59d305263a9e8cb349154cf6d0ef595397e8ae5ce469db0a19ef94bb0d29d639d8de08922a19fe70cc013dc35d8703e81faf31a4582df916d57fdcfb46cb013fce5eed4a92bc266993a +0ebe80e9fcd2182a5b78afa930cad0b636b2f0bff5006e8f9fa1e61f38a60cc89e69fb209017e0740a476638b76022d473a2ae0c9be53dc4af61046a286f4f756540479f3d7ce2a8bc376174845770d294a0918755e94a583f641ffeed2b0da563a2a196d3f149a0b3a2464f6965932722292cede87c509c3f2dcdd13c28f4363c857eb5385fd9847c766a4508dde8db43faa64135b951674f7ca0097cae40e2 +8bec4c9cdf0d91fa4fe2282b3491dbf21e375f75abc4a56c407068b1daa9c066c7d84d6b207c3d37eb326ed858f83fb26e3d36ff8624377e244a3d98f21d7d518d6a54f56ce0d3d75bc8919d8cd0cc05b3c5dcb02e8cb1f4a762e8fc76666b968a2e5a07fc37d59767febdb40579fea58adb3c2a86012c05842329eeee21d4d2ae21a424f3c656b37262b1fb49db8caa2f51e01e0212a112b763086022cb84d5 +eb5deba76131a74bc74af0167c011f74973281bc2be283d2fae7a2d37b675d0cbff05ace01f7ad510ad4d7e668337b566e44c2e017da24f2625a7360bacecf6d8e06ce1a2a16d1c36bcf6e7f6a8261e30119bfe42e0c9719bdcf6bab85d81888b0ba95524a7491f795d7d0408a3eed101d349bd9e7527900fce1e7c3048955319a8712c96a0222b4b1ef397467fc48733148e01076b507687b1dd44606365fd4 +e50069b11598be706fa281e34685a742ebeb874694a704a83e0fdad38009a56f91aadd07778eea5c472c8ebf31b885fd9fbbcf5c67e69b8f8d66d478d8faab05b0ff4fcb23a57ae92b7c643fa2975383ae410a254732327ea71e43963104ada6d8b1226ae12dfec697eac6269c2d4982cf3482300291fba53bfab6bc35eae2e23dec2776bd5c62d08d6696e688216294b04cc27c36e6195d0d238524eaff76e0 +6f8a4b9afe48394b1498da14381b74c1fe78202740befc9638063f02558db37d07865c65667a5fb74c402d884752cf5767b2fe72bc2f494d9c5fcbbf8a5c27428ecdb0460b45f3f81c169b200ae3df2d031a6e57aee64df77005a12c251248e8815f4306732ffc25e07028064c8be6ff567b750a1302cba12badd6c58401c767c4acbaad3066de04978e1fe4ecb1883d27e14a61027b633b5c0aa4d9d5c049e6 +df8fabd0276c0671dfe70110c367c3916096e9fa2c11fd68c9e5254dd19155e704a535de2cfdf2e49a498026c44f69e747a3e169b5333209516f46b8947bebd2e045f18b43ca71c7ba22d5f314db2e834b8455095cd4f16c1e7d611e32bd2d2e60ca8b532dd8b81978aff08f91a76f74dfdb7b0e80632837d4eb0850e0183b9ef26ceefc7b79ae332701228dc8f03eafc3ed238ad90f84c409b84dba0852998b +c504bf5d2d4c60e435f13d2790b4ccddadd011a83fe46b1044aa3d731eaf270a32a6223c4d4d590c2ab739da7c8c3f40f9c444c7fb72fa0674ca2c424e85d5ed0865e865d356230c78e0eb138a25fb601226a352817165b3f9d74104c826de733fdc5d182ad72b5fae99caf78d50a83ed4c29d52c49b3ee4c6c2234e133a5855992cf70622e4b109fae5aefbb9c35eb2e70c170ba0c73024b2e234eee274c023 +9cc79a16010c54042fd3417ababb2c35885efa6f6d6eb038a73ebb78779e0562acd4c041f602f70ef4a003d2773f91684955c56b9cba9e4d8afd477c48a170901e10b0a46afd6bafc222ffcbdf484d6bec242314f7a1dba70c71515bd89b646b0c115f83b9974c06ddbc3237ddc41e0a3e39f20494a81fc6bf4ea39bfca127ffc0472d47d18af7e5958061691b6fb946b2301ad3af7d83d899b772d1dc99290d +0ae1a45422f235d1384fb78de2223956a94fa4ca95da18a4718a9bd9a8aa481c1761449178c8f5c6f989d67ef822e3f04ba837a8c00fc65f256d6040a7757588e2bbc0e49632befe723190e64463fb65933ab9e483c8993030b7752c87c5e2a86be543774cf321f078a79099f49a4f47398409a7e7c63cbc45cce30cebe7625e4ac7d1a99693ebad56018205d58d3d847d9c26734dfaf61d0dea4fe97d7b696c +871c43d879e1c26c5a4fe347363301f309079bfd12498ce1c0f4fdb25f2bd16d32951d3afe7c86ca4dcf498a1d751f4e810b5db09c5967ac1f4698e25ff8431719e64a65d5dcd5203b4589d1bb1395b3ad91dfd98a0c3812054a892304bf2c839e87198c97ec1add230e27ae7a77672ecf4dd7437322f6dcffff61f72e0928da18086cb4911c9253c1728132379e7f2da963596fdab76afe4864cb0da5ec82ef +9636aac7d58076270654761611b445f26981dca15eb722d647ea4187bddff3993c18f86da35bf3b65b033ac201cf36e2caa8b0395a798685360f32c16d026ae2dcb1ff5830feadaf3a4f3c7111f6a8ccf4a82232af38fbac70f42e44a54c966a0c1efd43ca679e67276d7f331bd2a2c6493367e815ff5a01c55bea9a3ab72e6255a8643eb9361f59a18878e19f803f35ad8653426dcdffc0aabd22c2f83ba485 +1cfeb40cd6dd4d38215706a8c79bb5ab61e7e64a46616d9d24973aa400f4d64f32f89ddef2eb652c97e3a633b771150bed0cf22abea2d77a445ef894eda35c49a12ae645fb5f2e207ef8531dcec96e79f62cdd68a28ab062b47e21978de285c439d5fcc5c8c8f1ae1b92c681f39e1c62013f4f9b3723a3167ed77976a399574e58c82d9498072e52fa67dde0b315c31f986b389f090d34f4915b4c1b545edeed +bf8e8ef17ddebe4332b9225c59ef3c66bea13a4876b288575eb669a6d292a92524fe395468b0831a8e3711c449248bc00dd7525c3d4ee2e51ec7b166ab1743b930b417be1ea95910eab11c3d537ee6b1d96defd5ead7ad7b91ebdcc87d55dd92c8cd8bf48f865bf8c50e4d645c4cbc0fe639733e7f22e69f37a8082b89ced3f2ce8ec3af3025359165d2b1cf5b32d3e153b48a03ffb408c07e15089373b6ede3 +563517a9319b861e7de18e3b4eb5798b79d7a766145aa727b5bef9cb00a0fda2fec7cb9a60020ce11e2c59323752c5420fc16bf51db63997d9a981b6be1e8e067d588ccf1e1e483addab6126de539bb11da8dc0b0452198ad80b07c8fa004e4bee61582573eb151752626a7ad276ef72b2683a96ac16bd737e76ba5b0ef92cec2ad699ce173c1100188c7bfe974579de69add387ff5aec0eda11423058e7184a +ba88956c4b2f1d102ed6aedc976d401c882e548322d11260a4913cd6a692f45ad686fe71945866e59ea1ef4247411df8f4daab1b2c04571a802bcdcc33c82982f5a8d6f640eb8ffef80d35fc3defae0412d34eef6d547797dbf8a5f2ad41cb672378b887c509855b1bc48bf2dc65d82eb13b68032c73b51b510f1487865613a3035f6bf2d28c485378b7e3ec16de75d1599e18a494003049864c16ff3df396f7 +0da7aa0791c748ade375cb27a06e7678936edee2d7613d925d56fcd7d080c9368abc9dd9e096557d95b1dd0b30cf1379f539241046b60a594eaf70a0dc70a2f5ca3e5129be8b025890fd60a9f60fb3f369b4e7b01f71a1b4d53251631f1c95bac15ad46d944cb980f8aeff3e7c817a83a719aeac0b253963220aedb5afd20875cecffb24237e62ea90d3f89cbbe08e8a7ea1442ce08bdb253a7d5d825e520b41 +7c42c2bd4e3dcf1604998f68c97b972dce2d7d2e4aa91337c497e8167df04c652a80a947ef92caf164f13574037ca56709abbe5bc4ed5d0a2b6fac4ba577fe21d3d57fd689c5308422ac1147e9859fd100e184b4a759498680daed20d6c38b36873b324248edc8df3d97b75250a8bdc2a6250aa6100b039e2d89ccfbac8ce580cc60d774d9dca7560e9f054378cd321a9fb1ab3d0cdda40950ed97d083d41672 +e1f7b433ea6829da76f20f6406c2d106ae9af4603d0b2f4c3a5e910f554fd4fd37282128c90fcd9d09c39e1577b584515598ae7147b9eb9f0a8118c39d20684da9d75f41f2b995967bc14bc6f0c0fc4de4e36b9a9f5f28a7965f9ed09c806a0e7e122753d1ae5340f9f3022081c3ba46eb584cf3a20488025ea738b92f074c08eb5dbc18dc9b7b73aef4d9cddb1dba466a2cafffbecf72eacf5316bd6911063d +2e616a4afac8ff14997875e6af38c2afe9d6d072950c8352a992a799c2ea7e67fcba8ce9f814850a79b7697896d93be69b8e61bcc38f93482de016d7c423f56aa552af97da41a0c766c562f7818fb85f8f6d4286812c7aa9c813a5337bfaefb07eb26039e558abbe50aaab557283727e9abd0d7248f4c9051d26d216b2e0ce48633186c14cb1779f76890ecf8ace3e46b46dbf61e97b0b592184d1728f7a0b2f +c65208e98fcdd6aa99ec2386a4c9fe171ab4e7fd2785e561d5431390687e54a451dd64e11db47ae68b955bbf9812ae1511241919115a1773e91f2f10b050d3bdd3e3de85484507685cc19208430a73bfeeef9ad5703d122f41267a3201235f6327e566490ab79413a640244a30a4f39ac16cb632988ea6a113a23b737b5b9caaccbce581c23ce1a2e2d10cb55f8f0246c3b6c833e51d979dc55c58ad619b573d +57a7ea611bb1fd027168546218b6d693dd1c7f36e17e22ef18bb0893295e39fe560a50e87e37a796af6b837611390d77cc4506ac270bcdb24440aa7166e8c7b73735f00ec7e57048c6ed686ac3ca582ef5d5e4d315193b64f1b343422baa83f8f0185730fcbfa086a5ba17660ebf11e2c30e1ace11820ac8f6ec5831b9d1da8e1f8eb4ebb63e6580e52d365726dda5c8c3b3e3e7ece55a90cd44af91e29e2daa +7c2baf2d810982cfb42d88578ba534beb667fd3fabc08187f8cd081b286ab01a3c86312579484ecb4dbfb34cdc999ea0a7eb306309a25a2ee2821a4e1ccb9e73f173f8ffba4952ab1ffc8dc56ccfff3684458f2187b16bb3d7ca8b04d79ae55187951ffa02c6082be9f991cdf1a8da01a6e1cc1939dc5ea62fdfa16235ebc492e9f373dcced886f6ccab84aa8cd1a65a3bbff4fca574beb78c2c859fdf2e5341 +cf908d17119961c0b68e32761a47e65de9a0c19139c8d730c2e286f1726c777db4a7eda6638b2f4b14e4d2143294ef803c0d6a8096dca846b800f1b60589e38bdd0211378f9f84984f2224ae96ded22ad00c3565174d5bccbfda546344b2e12aae9fc421deee7691d8a076b67c4fa29aba650e3a9a25913dbcbbdbeec0441b6683395e00ac49e0de56dc2d7251f1809c6e1463e2a25b9452a18097063cfe170c +ee715dc3ae77c9825286c1f85d07eecb285e931153fdb104ffef7b207a244edd71200d7f536636344da91a093f5f9167862cc879073fcf31a10192190381ac054c4f1034011f8379b5e9202ec911f8a13bb16254924a3a90dea47718ebbdf7ba54516994dcb5c377e522631da0b22bc6953d63f06808d62943ab6d4a1ecfb6def827ed9787ad92645f82a73f9ac8426d8535282e977a968a0068c06d10248138 +33e3eefbe3683fcbceade4017b01f4825ad12754ecd46bd9ada3fd864e2a362d5239ffa96b2310b10848fa72312207db91a7385160909a20256ac8d345efbc11c4ea653ed156d9a79ca03b9cc3c6cc4c418fe1188ef9cb293da50e35efa3c6941accc18a275a36b9d5b825e216109dff61aba2ef63ecd1ce19e6131a233990c97b403c8f53691bf7ad711d42dcdbd28d0e9265ba1396a961edb3f7fd0b65225c +c33ee6bde2ae0fcc7e45c080516704533aaa1bd93198ec29bece1daf59dce7cb2bdd03e136a49412b2efa5eaafd40ccfb4db03eda231caa3ca4f27a7fd45719851014edd840f94515355006f10209abbd3ba778fe743c64bde25a8fa166ec2be579e314c204196603194bb498264ed1bd0532be423e6129564c83bc1b5e75b57167d9a48445c2e1240ad0fc05615f9a7a5640f9813117a94087712fa2b9a9907 +0402e3dfb95a6f9d5656eb216048d399566cf168610652e0d4c318eac6d6930da9d8ebc46646841bc9db67ff08530977e48c9a9ec715d2b14d6db4d9ce7c4c1e3e8f205b8924b0a6dff21608ad015b46599284f5d71daa26e6fbed67b10c62cb62b9de163b9b2c38efa926de47ba9523d5bd9835a7470d2c02732d6549f082f38a39e4b17d9ddccdc1c3f56a25d713f83f82fd52816a74c1620bd6aa5f0461e9 +f69c9b51ac03c43c9fbbf45979b2ed25695d9511e91c307f6a8af95b208f3ac91c64066f0744f956625e83eda34ddde6f41ccaa984704c4652d3c5ab6116eee088f96db303b5b62347a5954df990f11ee52c7b93197af1189861f0dcf70d4cb6d1a417b76ce3f69e76e312f00a9b413a3bf6281b7299ee1920c0e12a908ce3a6dc9f260856fb1207ee03e227f7e52405c0d30600f079fdaf8ccb1c1984806f63 +e7e8a2ccea1420a7ef916f4ff11a7d7c9ce59671a8e319a28394924f4d9513d355dad9a4cd4ab4ae142c29096a8a06cd65bbaf8cadfb288384a14829781ffd1d0028babedc9e01396f70b9f55ede0136b13acb63e273508ff29f2be6c96ab06939a6aff015853e40732282e559eec31a205ff220fc81d4adf2d85679756da7e12238d5e2bb3b2585440cc33bf51a652c4bfd13d6e23306c555c595a70daef040 +a6110e5a6f1954f221fd1fad1d46f873ebcc12b113b2f5db602b61844abb88b388a4ad8e7da9920bcada4ad6694a916302bef75f3d67e30d36f01a0f2b19b54fe87fd5542e9beb2ecae3ddf58c5fd0c53c713b35e1fa5d138ed0d1c2fb23d80e097ef8651fedd04704b3ce9c5dec41582a75716f7f6ab6a84b2bd58b85e670a96bf28676532a7e667752545d1dabfae827a60c17e7d50a9b055fbd46300fed23 +e2b52b85ad60e58b71d8770a34d1292940a4d1f237aba02295166ca2781e7648d2d392ebfd1e1fe4fd4c17f1e9a6753923d71cb0885622b446307751f7859beb944c214e92973060a6837489228633b314ba352096a12ad7a96bb74841764180b3d194154f30a3a55ee91d99cd915f369e773c70af0bdbb657af8d141eb869c99ffabf0aa238ac959dd7486e8a9f34b6eb9acadadaaad4ec271663c5b49b154c +238587d193f3f483befa803e462049976b2a46f9709819dd40dfc460609dd6f3e080c17d438295b547e9758762fc8d7a54d259d59ce42a8cb237c0fba449f80ab0dc572afa6cb42e0bfef3527f491c1c0074a753afdd6fbade2a9b05211f9913f6a764ae231949d32c257b7d708379a4ce7ba934a041b96716c5f2f4ea9d7ef0fb4f45b80cd164bd08e17a07a2d59918da5e1e99ae86fd3d37ba048588c0ff60 +7c7117ac1637be1806ff4bad67cc6cefd37a6105f719a2ca78777f9b0ff0beb95b799dd02d9b5e75805edc6b8904201d146ed3d30c6f5b7cca111f492f385829eb783d6c1b6f2cfb42389721e657497722d0034601e175f255df58193a8c9aa85a255a58a27d0bda12a3472cf2ecc92d1b01adf7527d59f28f197ad9cf8149e6c2edbbb63bf62ad4d8ffa6af063df3bd2531e30b8b2f0eedc4655a85a0b1507e +6b455a025a012f52d23328bde5ce12cf34c857dfe3a3cf0103623f2e23cdbf7fcc8ea4dbae50270a17a42bdd40e7bc70a914b38736ab8add806b76c729de80e4ba32577bebd3399cfc7fdfe5a6fb26be3ecd6bc10f58fa9af8196236d260ba651c87f09cb5074651899d2da94fd68ec04d9595bdfcac13a384567da78a4e1f4aafc7a6d96bd06e56fcd37474ce43a94d642f6c372e81a1e7ec9e918d8a9b7547 +7dcba26ee9faf206a569a2a0d0ac5d18329eab019b2d63810666d8ee3499cd89b76ee1abbcbef4685d66f59d075dc14ebe0bca909d0ddbb49b804638f45fc680cab7ca5fd6a71173fc86415c154ddae49c770e715e5e664818f33813e7fb87f66f124dd777a7374c44dedc1dffa2b6bbb6f1d78ce79c2bd75ec39a553a54e1a59358a818d8573e063b043f65efe6c0165935eeac7fee7539efff7fc9e990b8f8 +15d2ad5936987551d8e52429eabdde68a7bd063e67e01606876ca555a2eeac073b4ebd5934b64e211f38fc958888dc986c8c329fd225337c01e1ca992c6a67e71be713200548a3643407f2882699ce67cf441f47a94107bba9229b2723dd6ed64825419b1161a34705e75213eee5887acfa99498810e8cf65ba69bd4362908ae98a09ca1364a73ab43fb948e2d628c4b48cc9f5926911813397e02a34d3c7887 +c02fef796de9393640c495e177c8366f08b2c7e2714d8347e7e0172733062d5fa2f605ac626dc1d2620262e758127f647a7bf86f42484b20eacfec3ae680154b20041419fa10b206e3956de8e3e528626a984dc5a0c1d25f6353b1bac4ea132b7d4f5bb24e35061d0800d3f2d9b4a3db208aa118064d055dba3954908dbd1a9b5811145c2fe2afd2567fe69bb12381eef2d8aa54e2bcbcf06bdb5d981112ab4a +61d18c04b2da973cb1b92831e03f2a719342bb1db4a0be31090c037f3b921cbb514958bba9154b33c9bd90d18072ad405c45acd11ffb2cfaddca7b4f379a05f6f406ba92dc573829ec01a45a5dd0a4b11a37447379de1e88fe4aec80412bf880680930641604529657dd3911a9fb183938e58f027737c8084cc49b48e6f608852fa47f7894f9f221dfea77ccd266c5ab72e6b474047482a5a484b99302360d04 +42981c52740395bd1fe931ac4d69aa079ff3c20c8f41e0573799e4ed0a131d18edc87d5fb5ecb1f83fab3992f65b1a21fe3b3852e21e3a7c14c23b9e925fbc6fe1fc54c1de075366f2a3a98bfb1d2f205271d9cbecd96524b025c43ca146e9edc7ddda466955936bab80844b2216c733264814d15291504bfa86fc2e2731eb4d60a968db11d300312918ef521df4e271a7eb816c6f82365a32d2879b1076f9a7 +a54b59ccf99a9bd1d3123b3dcbfed82e03e32cd8913238cbf15fed0554d27ce353a0e559321b7041ad315bfa1eaef5e381f8e944a277e869a4c141552b2b900338443f6ea1f5c4598978e91d40fb05f70e9f4e7ff097dbcfb7fc738d726d4ac3ba445f8f8092dc02efe1d522f9b0686f4106d8fd1aab9573b7301596a895e0dd5f1e5d9c190724f815b9e3af96312977606dd95e7d12bad197aed3d4e8990183 +79913e2226cb3942e2035a2fce0425cd76bb9a419834a87db2616d540272462e2b9641d70665cb5ed77169fd4f91997fc353b1d2c67173d678458f669b4dae1b2022dbb982a47e393053f9a4f6251dbcbee3f956e5c94e691b164e952e5b6faffa13fe54292a3a4bc0a24380b0aa37f21b46adb17459fc050ba0aa06e2617a0f08b1996f5fabeb738c166c8ad64b46aa669709651f8603acfcfda921691c9bc0 +cd0ec41b770b73b83fba99249ac7d4c9ae390467f939acc45da75f80e3c2197a14e388109a48c41b59a5876faa6a487585fb6f443ce81c0b6fa2d3029e4892b21e15080500eab6692a207c31f9b95a854bbe7bc5de2f21466b31acd8c33c316e5929c4f60652ba999cd1953ea322a70a856ac421c5d3679760dbfbaa3774faf277466e3992496e1e7f5171a1d38a90385ffb5014fb5cdbabe6a498c2a1dd0eef +9ea41993cd1bc03f9b930559d80f522ca9bea117815a76b6ffc016cc5c6dd219725bd27d77eb2d86043df3012672f07f104d99b213febb67eb7afbd029d711dd767fe82c8f6c353113b98581fc77d239deee6da238b03ef4d9fe1f94583f4ca46d03e9ef6b561482b5b2e94490e9147439be0614cb76a94ccb0f157a7d5f9fa9bb88746d1404204a455c486839ac9bdd9f8b7b9e87e2c446928ea7582a70369f +f3ad1dd610276dd58969641170f06a02f9ae902ff236bdd5f225d1aabdd33d18a9cad219f661ff9e4ee980ad2c9a7d675d29653baa66b8a800d0e5a3e3043ccc179576b1738ca66574d8357ee51a419091d1b81204956947d7ed886605983a67db21f128e12178a25146cb31b56481a516a618158f06b693fd12e95a2e719b8a2c59d1317d042b61dbac1162036d20b1f42e470e70beea90eb8ac7d3dd3153f2 +a0eae030b02cb9427fc5b3331f87ee11413673029e7b704c12fb6ff793990000b07e51c6247c2821b9a7fea5519cb59d205010a0a8d61f14c3a6e6d4a0a56e074b47be6962bc50efbb87d551f7f2edb050a16dea2ae242b545dec9991c7c0657ef9c8b3eda246bc83daba3d0b4d736e9076ea00880d8cd584c82b57f394090f673c630102b40cfccf88b89cde18dac32b701fafaef40b6b4da92610d9de35506 +89337575556106dbc8d63e5398c3f80907d2e2dc5173f9b25bfb189afdbe93e91ee9e6381b37b79d03298af9ec4347f5c2a6ee7c1db060784793fd841582d756e9ec899d066afa7a87e480255fc2bead29446e121690f7db994904bdeb37b52baa9378948da479c1ccc1a2acabee961338ad89384383a5bc2800a1212be1609b0a93fb957f773e8c706b0aba324941263c744cf82e6fcdcad3f61fc4d9079c4a +87c8bdf4644c06b3d0392b7eee0bf9b112bc90c505824ce485607a039ae032b3a1b08d04340cc03af12ef6c1a409af124a49cf8e109f48bbcba4a5eb555681376aa65ad72205bcc53b5fa98a1005cbe0c34fc9e245971ae6743a968094ae0dfde27bf0454eb6af260150391efa1c853800f958e7aadaf2c93fdd9b892d666bbaaa7e23e303ad7932d2904f416594a58123208bb1dd84b161c3dc522fab3ea5dd +5a010dd5af854f928deaa20794d9cbf1fd8fc4c5cd06d311e09cab2bb1b8e36e45bacabf9b2e246cc8e6b2e1728893304b8c476a1dced4561404d53f3816f7af63ffa5576589ee33cfeaa43a9e5861e21b45a81bbb8300c4bbac95b9f9dbb9c50503ff9f6e4cd9cb9106cc8022454bec97a386705163904597fdb86c582301345a624e69cee2b2f18850b6e0f05fae3dee26628eb85dc9929df0782008397745 +1387f82d0416cb810910caad7458eb33e5f215d25b6bda3c74797ae0f3c6011b8dd73776dca3b0e3172e1ba6d73ae5c4ceae3d9a569f4e4db4be567a9497b4fc933c3ddc9c95893f7ca979a31921e8e71913395ab2eb62abd43050528d93ce77addb618c6185ae2dd06879cfda10964e1b08068d2ffe18f69bc6f6d35fba952c4a7f6e51fc1a98168dd42444f6958cedec4b4ce7146c64da1833e376859ff5dc +a7c53662c30007687a7d69b24b98063308c923761636e43f7cb14b4b865e6f73df2f0cabbbe7d31e81083e91df1eec9602a369d5b0c54c32107f55a4866cd92743ec804256d9c67a7c2be4de7ca39eea31332d73acf470561886571c3635cca62bfa3c96aacbc72a458f1173d77306e52a1fc651d5be9cf6fa163a147028366412144f6f69de1bf87aeb476084e8146a4ae0f156aa8292287580432f82ce84c2 +d229258baa0f320da070138c1c542bf342de984bcb1914e1ec00c79ff257f89168783ab7502745fb18482cbb73bbe25eeb887d361bfb5eb4045268714d9704a8ec12eef23e301517fa5608fdebf41fc2cbfb2259847ad673b2052160ad1cb9c4f39dc8e2f83dbeca3e63cede73c72c437be5b202f9a0ec62bc8fb64da39abec120f559ca5b864c82a7d0996fbf7e79e280036129610b9fb875a8d415e76036b1 +b148a13d9a04ba6ef17afb0e25a6c91a454ec0eded513a567a9824dd3cd16770f4c1dae48854c2cf557139640c1cd121cac974f74f7001aa4927f6bdb4e0fa73676855df520e2af6ac785a420e43e829fa4e77e5de386d58404d42aa57bf56467f98322275df9f1a72fbb03fa8ea8b84356bbcd7159c59ef283a1aec240ef5d25df6e2aaaea36826beb03b0826d4abc8f22837812dafe6c9623517471fc653b9 +10673c1edaab3f72c43bbaccca6d7e57c431173d959194936c77abe1c9de51121a96c3259def6e13ae0e4b5273851211147f5bb68eb2d6b8afef610d1d441dd62c1d4b58cb07ae0fb1459fb4adca6bc4ceb668ff89078a5f7b551ff4938ca67b868375b561b02a4ae0d9deee674f7c463da3c6e3f906691620e8bcd771e2e6dc021f96de49dfa2814c675128c3dbced0c180b9e9a151c4deb1657c8eeda9691d +90f51ef3c5ec5c487661f814db06fc348a865813d34c5c07d1146922131766c363d264cd54f6cd7a673c9195807e0c6d1bcc717e0def10c8993d646a6805513cf6164d17f4b4ea631903f69d0729669b71e595bc6d9a6c90d6e7ea07c5ef5b3475b02e9c0499143574e3c3a5e7113ee95291f8a4afa383b427d520025e4b84843e923cbe64ab00a2b66215e298d5dadcdfdd95c202936353121b25cc57461099 +83922bda8f9e49a140380ee6707373caa0809408561947696d097bfd375764761d35641adf23b73b28c03d337e24c2c2bbb677510416bad360f6daa719aba4cd33df035e3a5d3f92c6e1105dda2179ee66707074c4ff424a614cf0f65fb6397c0342817a0900ea1622189d6e1865f42897c14e1cfb781078d934bb3e9b82c56379f8cf3a5414db99b26ea0f76db908112258b60062ebbb7ed0f70df3bbe98c27 +a85778013115e2e1438383ea9cde9af751416d76c312b21eaf077510b0d0701b13af17f464332837ac3f433d500f13097c3128f63bc9f170f64167b40cddb61c2246376fb21fc108f7db686c5fc58a3339616bcd45e12b67444020bafe2720108ca1de21475d0d95b87488339329367ca9dd59fb795215eda44f374385aa5e79b52a70c6989f16dc39bc0f350eff9679d00b995d4076df12dc72796b8a8f50b1 +86ca1968815ac1b731eb97200a3709c9b4bbe6dd5acac1374445d30f38344f071c0c2ab1f1848ef3c4c8c3c50a698cf81ca782287452365b3c71d8942e4c434be00fbae1620e6ef353eca4a567360c4215b88af7e4e880a064a9beafcf7b7f66b788eec1d9df52a071dbe360ed995816d8a1d18f685f677f9b3c85d68e967d5d50e7cface661062ae15524784812a2e37a4188659c411f3a9f3237076298aded +4b0528929f64f67fb8b3aaccf224ec75b206f0162b45a7c43c51623691745b6f5e550c640465d72243f9a8431fc8d3d7ce5a4bbd7368e68f73542fe801e2e0a7dd3561168eb1ac6f2ef03f68ad364bbf0be5c7717c8e6089ff2e1c9da9370aa3354928eb0d8e0981bc18b7652aedc48a767639a2e597c3cfb1273479ff84a93985377e5b66ddcfef2932d16ceead9ef45de94f8c240c84ebe83c264da19ed41a +2a3e7d226848d48144a7f14097861e791c5d8f89b7118966806fec7c92a6a078a65f8d40469d37bd60da1a44da63a9ca1680ce16d16b1eacf3be569bba99eab9a522894ddc1fd21c2ce5c5211ced70b638cf5ae3675054c1c25b9c0c83af2bee3733c602c7c57a518e6fd16eaf8f2e65966017e6920a3c45d182587aabbfe63da3cfbe3aa1baf3814c0db0a54d0ad24dfc98f010628c0e772382a6e64e848e39 +142576258685aea788c4bbae38cc0e56d161cb4c61e964f8593a0863d1187d7065ba7fba1d5635d08a9b817d307fc1aa50e3d2ccab9a3282b1af89c5923d46bbabf7009f874387084d97fed40c5f0beeccad4cf41b191f8eaf47c59afb577e6786877e07d7e9c4df5974e9b11212f2dc7947a3d95283622adfb5cc10ea8fb22981e1a423d01870fc97e3e6d64bb7523690a44a1434cb26dfba27efa2e99ff8b5 +453448b64fc193c1b14f30a38e8dd341abdba8f2a441f83b699de49a9ecaf89e0ddb7aa55ec8031fd78a106d859aa716cfbc7eff87c2e615894bc067382f1a75029251b152026f7e3d2e6067a560853d152a7986a24d68cccc17ac69d43952987a37380d10392f9b004f1f56a141cab65adf9f3ecba1468be89506c0941b5b59330325a6a8c7578b47ed80b8859544046b709b06fc73d682e420e74a458a2d56 +31213438c9334719556b0da94761a36f6619df09d11dce7324555ddcc5dea353ce5ff3a613e11e2363fcf80288b5cd4dcc6fbbb0e798e1fdd81fb0d0c8c046cc45c9f1d4565c7a348eeb4024a69fae2a83786c13942ced7e5612c097f9d0aa221a6c4a43cae656d99de0b9d82b55d1bfe59b9b150ffc2474a7fe43b4ad85ca9cb7bf0ec7fe30d1ce55915d289c51f467167febf98196283f57e6f93aa180770f +8f59fabb1f74e632870a2450cc065e7b7cefb160b94a83681ffc09d13130df3f0de8afb16eadc592f5db7a27ee5654eaa7d106b176c792405295dcd1c8b186e6c38f4ad0b352768eaf68e3cc71b0decbf056c52dc77759b7d6360efeb4a314f63fd5c1d48a519fa4a3a76dd920d7e66b65b98e653230b1c08efe0a60dee8584215f59827c5d7cdfc01e5ab541a685ecdaa3561d8abd4b313197102bc544e31ea +9bcf858f8da8817c14d138d086e10ad9a7f6c888c525268005f10900ecd89fe557c2369e26b74657d6a6a8e361998885562ad1853b88da9ed07ff9ed93431f7f8f71f56a301a48b8c297c15259113ea6441b655e3de65e614fa7e62b70113eba7546fd45f7047aeb873675e22016a34c4c45a43d5f0caf47e8b0c0862dfa9fc6fd2d47dba41ea0255f5dbe87ef16b01161954ef4fb1e07de2a88e5af40d7cf34 +2fc2d979753d8ad7be05e124d5221d1b0987f0c0a0c3f7a45eb20dc78ae781296ae97fb03953d5c166506df0eba71ed1d255f24f4927eb2ca3ad779dcdfe6b4bd966d4e004797230e9d638632c4757d75ca8ea71d2ae788cf1f652ecc3e5e2f4618f08c34b314fb2dc8812a38a68f6d68c3c6cdd443b83b0db40d633e5c6987fea55ea81e33f71dc7cfd89177a8d6250812ec1254f8172f0703aac81afc284c6 +50b09e60a3f73eb762e044c0ec9d2e3f69a3a1d2938c810024e27bf918c36fb124ff343dc650d66dfa930511973b0e9d9f38d7d29f9def91e0a709b8acf229042fcba6eb12646fa1fd15e7bbafece40078d16af566cb5c638c5982c056ce5ee618943f8ec23ed12d837ce26c86f6af16bd095e84df2b2e41a3f1793fac41f3e198830931258f22b6e488f1267ed6c859ed9acddd2de63bb1536524ab60ae1518 +7f7b9bf30bdbd3ff69056cd772cd7c7d36f0ba3d6faec40bef857382433ad28faa7c95b4e5fb0b4df3352858baeed36cb2197c34f1d587f40885a72d9c06914b7eaef9cb4ef32abd6724d62a625fe4ed94ffc8a8d651a3fcb420e15c93bfd034c6fe564c61855c97745d8f2b0ea616720597a07046bac8b12381811679eccf38ec1f80683eced2002d0cd2887024a12dcb377b52c26a819642568c5a7bd47943 +b6fa8ec4e0614ae8d1cd80d53ea6b9ef4897daaae766ecaf63b89e3b2c7aefe41538e6ada1a9f2f127a369d137ff9705e128f7bf929a77016306e524e0f482fb822a513dd6c819834c1240ad6e05ab328fdf1cb0d6b0b84a1060334c3e0c7879769067a012734adf4e5205e920f6ab17e93e194d2fcb57bf7d0fbcfb8986945eef133dd2f01c1155a64aa8d3ef1591fa77cfcb1a2d0d91f5694b1c059ad348d8 +e914f7f3b3e74ac771edf57ed0962f94191fdd13144801978356fb76f04035e8394621a31306f157039345d828c96bbb42a107eca547c2f2d7148fc53f1af4107c5251bdf432d0fc66f79b6d6d0bd4f1d85f15541f306ba3a8ebebd17b7f7476922455324cfa0ca47fcbbb258baf0b2df7d09dc41f52284cd7dbf7d665786c86a4bc5940db36b2a1a80d0069921b50264fe6c1af073c18b86e2ea4c6ec512eaf +526c57ef7d5f7b1de12585a25fd7dd4046d75a7eafce16228d45ebf421e37bc6493c77627e36e431eacb67970a3cd2df757202dace88ae53690e8ac3f566dd615bab61a24a5ee530a26b9dd919d1a0c520a675394db6c94e461a3c0006cd7c422994b714dc57c0f026eeee5396e3e188ee80485d091927f8dfbbf3b2009323d2099c58dbb9b256afad2d24d1c43b4d15934d6616446d309f45723aa1de923e48 +6e79bd2186a5c84578f4a895f43c9f7336a6c7751262f4ab2db09a3af67f14b8dcc15a79992986f63db39f9ee04da469502ad90cd55f556a67c524b9f610d05eaba8347fb86d7bd1367115a22c5138d60086b90fba66d74bb5901b7cb7386006b77bd196892846e55ad1642d9841ed43c59391e96a02c9c58ec094f211356640efd40e50f3d5e4a07fd9cc8e0784ab6e4e38007280849f27acf065835ce6b037 +c48aa6bd467d3e031e1afa40c93bd789fae12de227a2d989db062f69147bd4dfbe364897e0861f14c2a8c86716930f3bf217cf918303ab96e5c76f6b222e74eea2142a4ccc516d25104a43c883fc593568e68f6c25ff40c5cbb566f425e7de43f12b8d69f51861f4ac1ec3076d9e593b1a8c67a5af7a7e4a7345710e1e716e223808a24ff25946494db8bd5e47b05c1be530c685cea68a2494275c330beb3bad +143c6e7b667e6c8872108da70e4896accabea7b76163b3c4ee3bf0f3551a24d86aa4ae029579e21e23ca5504658721a0198d79b64270e9f248d588fb6c5a7f6f4f618fba10b03b4d687aba7d4464526d27abc7ae7d3f3669340f1af280ab5efdef1364450cc076e3e046c2fb532edcde528a4b7d393ff33b2bc530066d208d8d7a0229012a1f0f310f3f2345ff84fc68ea8734d26718626a06b029b5b2762c09 +df37782dc76e95d8c84cb883c5517025b42a04de301aef0656e1bf09fa152ea9382d8218ab4dc7d23f50542559b8d2242fed01bc44e560a75f6d2bd3783d87e3230206d4c5f0e046d4da193807f4a53eb0f7234ad4e6c3246ae8f848d7af73a6976f2fb9dbf3d4f190c2e94bb8d98127df13ec6b30a6781bd36a9a90c0e1f9a09231f309ad2a89f94cbbf45f9e0fe34191faf95dd1482a6fea01fb1ed677bc4c +5c4ca78f8de3527788e7d1efcd6aad0adc3878ea70993ae20937ef0a601730494946f078de2099c62de9af1c47ee4f18216ed5a7268464f210374dbf421d55449c8f399d8824c5a0ff8526a940223ee999a6f945f0ba3eaa672c434ad867ac7adaa46bd3289729c6c7d920dd0d8237bf678d88bde91e0683e72e88fef50bdb23cceb6270acba5aeebd0a834ccf99cd3e6bad8c158f5819f1f1c785fdaa3df505 +d880619740a8a19b7840a8a31c810a3d08649af70dc06f4fd5d2d69c744cd283e2dd052f6b641dbf9d11b0348542bb5708649af70dc06f4fd5d2d69c744cd2839475c9dfdbc1d46597949d9c7e82bf5a08649af70dc06f4fd5d2d69c744cd28397a93eab8d6aecd566489154789a6b0308649af70dc06f4fd5d2d69c744cd283d403180c98c8f6db1f2a3f9c4040deb0ab51b29933f2c123c58386b06fba186a +b563aac8275730bd4cf89ab32bb4b152be8fae16afab58ab3ea0e825c8ce28ddbe26c8cafef763f1d9c3f30d60335cd0b765b98a11d5cfbe7a2d75e8f8a5e851ee6a17de174d8bea5c1e089beffc99709d6dcc03e578220eccdfa99d3fa0a3d2f6736de041cd783ad7f866df5dcd2a752cfbfc380cf84da5c5dd3fc486cf1adc14d29d9e91737514e8c67d5c5aece4a19216e2b069f53b8ab4acaef17f815004 +61750df604b4ddc333b9e669b218e46bbe50be4b2d3a76e8043e79e98eac204797138e0eae7f39ce256a1a315476039a8f0776a724270f32b32c973d6316f281081970dccb8c13b9f8df927b1dd29eec37f4b68a3e3179a62329bcee1407d00cb1ff2a636a42ca8e939e6a9c56a382289ae5cbb7eb07df1182a7ef10882f28a7fe5d8582a2fb93b4fdf02ccd5b9050b60665771a21c0f35cb29eea40710dec75 +32d13a6e522e33e556b5b084b1e6811157e88ef0a897b6e62d31c984a51957eebf6b1e46ea80b1def31d34a99bb5a630d9127537537dda3f88eaf2bee4c37d859cf91387963e67748e7c09b3e0c1c8ce56937bded53e2b353dcb39e8aafe8ccdf71e82d88df62184750b125bd09064e296c5caca1122ec13aa030cf79ab9394746a377ae2e93a78986fa3531098c3e904a6b0cddaa4c8dea9b807cf63f464dce +06b741133a836fc85541f7cd567567fb972db462ecbaee424d5b6739d1610b49c782f090ebef367313424bde55f1a0778136b12736f85792d242f6528631887dca216e4b9c68be2d7c070c3a552e97659d27bc3783e0039aa305f8c468fad6b4611c80c8325639a8fcee045877c80d5ef7852775fbe2984cbb0b7373957d888607141129bfbab0439a13c0ff96a1867006e80f3169b5463f5578ae0663fba8fc +1a8ee9810be01477ac27603d611511406ddc13dafb632055244d1aa9c48bd25220cdb6d866dde2a038d6970831059e4cb9f6c99437f5b18d97701d15c353ef1cc3a102a2aa224b28d8aa3a1b5be2c3cb843b0c358db57058862df57bb72d618bc11bdf853ee3f254a90ae6bf281bf71689d30e46d09cae6f312bda779a09927c6bf5a78e0e3bd633788b84af7bdf832a5130c503c6f14c207d9b37d7000860ba +dbdb1be943bdd334d497b2667ff2360ed4d679742a349a85720b118c5a6d72d82c7bdd8b3fd4b6ff2ac48b501848114a3392381cc1bbe96cfe561c09381af8a156729fe9ff32ab9bb5af8381e8bfee5971be6ce4f93640c53adf6cd7545ed7d67963207ff164a8bab4f237509b4a1138378b25fc13dc18674cc8dd38b56e1a5ba56fb91206cf8cdf916bd761a38b1a69d9e19269351dbc149c6fd936441d4feb +d4fd8dec0a4e36e1ee573e4a9229bf826140d8693004efbeee7879b4d4396d647710584c7abd1322a72ec272e77d0840780e03a3cbeb6f1bc034d3b43029d159cb305962fd00525a2ee4c08c8def3300be80acdbc9b72a71992f29dc9e7a2e0e849acbabd18ac462410223565dee57b6d32b462e2c72988252596a0f54c450274678ade9ebc1cc9ba13099b0243619f25cc3e3092b1188e4ab81813df028b61e +f870496fd68a5cfcab9893c5919bddc1a3700a2734a461a349c2c63cf84c11b0c6746e3d749aac5353f65033646ddf08c017acfeb1971fde9eaa28d52a0940bd547a009a48e2989835f8465dd9398e8842483e5cec0134b98186dd5afc2cc67b5ca3018d291e651ac0f76311777a88265ad7a8c7a5113823adaf09c215abb421971461502cd3db04631ca690e8a47f2fa26645b70553b8920c991d14029a1f15 +df0ce1bd0178f16591520f2151a53cf1a7d093dc2fa78802b0e24f53227a41d43a027eab6356ad4fb00ec1f0b25f5ab4c6cde3b21dd369048b7b03c48dc8d50ba9c74f0861c0daefd2eb3ee175a70624b10e347c3cbdb3366f82f4cd7cc547fdb8aa61fd8d625f46755e9f4de0c26342b18bf9dfbd1291972adc88b71f268b550715e379ae229c6193ab8a9ee63efb654d0c02558540dd41ce8ded2861a942bf +0584e1b50d800f10f6ed71c24bbdf57e846549f5dbd4c19a7bfa6eb5f3eae1c9300cf1a05f79a6b6505c22d7d51423bcf90c804b78422eb106b6f6f1824a8ff4e636d7fd56a487b9f84d506e1d31cfc81083070096319497a3026a72544296da46c55595812d5f291c40fc5c79361584c222d72100d11bb7f9b3a7d0a7e9319d021f320865f9e78668ff2b53b1bedf8cc1749df956c62c663dc2849eaa6f89bd +bb9f7d72594a7784b9542c489c9968720a7b63909c4b71549b48f53f760ab672eb0148cf2d551fcd75c92748f4d83e237d4183ade38df1fc83dfecd1873c3105b62f673c936466c2b54af3efddb00818ab838e28580ce2dea3e7a7975f8a4782780b2aee0da2ddeddf1468ea68ed08bb37ec9b6093848189530d79c6b83f93cf44bfa4815987d0b5cec886dab7ce4d7ca69505a312ee9dd5ec14e373f3b785ea +78f49a8bf13c09d1456dba5adf8ed318d4929a1202d2ee4d9598ad5f82d0c065886d90de5b0db5aaa360730c555bea866c2eac517ac780e9327778ad14dda9feb1eef63e65e5b91e594fd369add2113ee17792797622cda7849fd0ba6d1d25ae824321251aa739efb3d3d10671ab0fcde2712d206a46a7ad18affcc2322f596870e83a9870264d2c93decba66fb61e1e5cb9fe1d6437ac794f1fede9e9ea7190 +0ec873128086df42cd3c655e4cd07de6fd0130442d65481574198ca51b73bb44675fac69f109164a484362ac834a46c952b29f13a19629c47e4dd45262b24fc51ee787daab371a210ccf04744b0a4ce0e8d020ab6d89aac2b4d938b223af2b8e52b86318abeb5c9b8c549a0eb737f2c70d7c50d6d6acd000cc263d673dd9e8e51de2ee9d9a4a4c01750f3810030dafb6adae804a8d4937ddcf6a8b154c5fbcc8 +6de3e2bcc8f15fbbe9d4f5b8ede81c7b1a22ccebfbf57af59bce75463ee333aeda67fd01148cada144b6c87d77aa073ba97e71439732348b447c848497cb1aa3b17b4ebe5547d24cb8b4bb0f600f1b2e17f44674569beb563df1d8d65b88ca151dad2ea419f15d1ecbddf4842abb8fe9155c7d55e8331f87b945ab58e9cff8783620ecb734ef09237980ecadf42349c29e48bf69821be8fea65f0028514d1218 +68519e1c9cb71716eaca86409cb3fcebd0824f9a7644fd5ff7b0cd8d119cfb338faae18866a79c8531b119a3790847636c0cfcc36a5e067d254b73152f451d26be586e5a5af4baaa949d25f93127a21fe5d8d78c91e6c501e31ae790ff1538fb2af33d21c3098cbe2c31949732fb7ddf4a2bc66a2f2a6443c1151bd0e4ed376ebc0806ae7e6f040728e5ff5b8a2cc5aa05a6fa5d6ecfcee62e1a769a08553df7 +e9192de6e18c2212a9dd0a15c6d44ff8436c0c55b601e489a04e8a5dca6d1c27d89cfcd33058732cd62a6e2fa3c2c68b7160dc742636c3510257be5b411c825ed415f0499c162b0d6c7d4e4681576484e9a83e42f37ef1621914c0b1c1aef1d13552e790d303feb0a9d37da4dfb1d8e13bb168f569b4d2de8d771a9fc26aad060ad9d2859b47fbe516f182cae3f1dc8b2e10193a73fd85ab18d861034996363e +81b452905a7ce49cd1970c2528a81bff37438b0d5fa46be3972a8af9ea42de08b1323016acdc098076a1d96b23f5756aaf17f21a7466e8f9e35a57a1e590531e00e3e06e9139de0d689e7f4dbe4c51e24e27f1cb449904245172a1233414274fa8610296e6ff32efa95fc109a430134f72d9fb7fe3a8f757f247fa479a6be9b709a5755881989137f344cd2863e6662f8920c4d4f9d56518c29bed26f7cdbbe3 +76b4bd5e74a04c232114f7bc2dde69ad03016a4d9caa6221e5af5af6a21d5a6cc6467f7bc474ac39a9e63fb29139fd5568f17dd4fde624602ab51fbb7c342efb32e74ef6beb3f0f7f5d8f1dd182d8508ae76426f709ce483c59ea4fb5d333f436246dd55e2262d5ea838808065a4b675ebdf4096eeac9c2ada7a4b5ae996497349eef97f297c819b4c3ef3f7ea77aa5040f993846d7282e4eade08ef7b69e280 +03c8fe0d084bf989374efb55a1d23d205c771d95028fd2df5d9894e553ae3ae8e495ea9339dde7cd03cc8964a7b09d48836845f4d5e772ac57ceb6ed8132a091db8d580692b5255e5d6304a42dda211b202e95f4a1a1c75b27bf9fb16a376b17325657005cbc1030e13689552163a51e3e0c1cad3f5b9668106b263474221d3615fa81cd5188cda9b3a936cd5353ac22874ec3c0809d05d8e54027b0d86935c0 +fbc9664d90ce4e0714e0f7218ce2a4fbb94f0c11fbf801b593862f268c10dad88807296c34ff60064eabbbc3148fe24645ceadc927d5c29ba0bae863f36baba73af13818e18aff57e7c2a07d42fb9d4e377e0b7eb5cdf89688255d7d3d1b17adfb5339c1eaa893fe50acb8ca84cb6f41ba7273ce67270a0fc575248374e70f7816e1153e0ba2a2b67ae774a6189be6adca806a2e737f5fcb90119110e85c2c08 +e15363715632ef6a4842fa179d62ab8c00048a1163deb50189c0f39138d2893e43272142693d461abcc0af99b766e1c57605220835d15086ad0c05bb0ee42eafb41f70cbbe6bb43b416e2e096444a4958a31c65813192aa6c1e0234e14805ab77d7bb1cf043e0167b415b1fc8343a8d2621d3468c0b18f6b67a84175f19557c16b2eb7d505fc3c5d2a2bc0dc72a54a4ade03a1d9ad4cbc8446ed62139d27598d +a89be467d7742e8b406b26d1eca536631d095a40fbc71c16f81a88596c6f4dba12656b3b0be04e584d27f015f7ec6175497de0154814317e1545920094a00637caf5504fb3880d848aa4b4ad9dd31066f2862be860698158a46606a527e96d817670bb05692e6bf4dcdf35db9300096b6215bab8716c5a6c6f3253b2c498a2049ef1a17581fe495c58fd801ef0d01c6cb3aea3acf263822df61b8346167bbda4 +8a16f31ba90de9c00b91a469e02536743080ca9665520e469e46770d5824b61e1954d4170284470a40867497cc44274ffe0937c9d2c2c1c573082617c3ab0b525054da5f14fb0b996c6682a4298b04dd071d9d069ce4b7f45fc8b4184c5d508568314d852280955e3bd0278b72bb39e80d57eab841fadb5d56a382a71329c7666a09e78b907cc90ebf3e442567a9cf9d5adf50b7132cace6a7c682327c86ca06 +0acb1c12e8b403dd557fcfe6e0fe39a0abcce80b16c7df02046d4803246fbdf32ea630fdc1260c1ada965fa49eeeca41667bb14005f1dc41b73f6a4091e202f4b69958cc2018ccc4ec596c4d8ebbda4f33a7f669c6f6e2a473cb02e14f3681adca3438489a339cdc104334e4299d368d95c36cf3a59abbda58cdb0b063157258e47175a867f84efa0c840891c2704c4ad8dcc2d3261eb6dff8aa9d954f362dce +0aa7e6dc522878e4b4285f6fb4c6d910dbf6dfa9cff497e4e5e9acf94d9f875b6a44e104c75da1c18cc8c38fc5dd6a996c65c20ccbfe59f235fae69b335b7bbf1b7383620aac0e3263f2670219225a0af5d7705432bdbed76428da335ae0d1458c31aab01d201cb391fa400c60935dfb71c0cb1c518f16d24defc2c6cc08f760f57d065d6a130dcfab41e1982d111fa5f1a6cbd559919b0a6f176795a490143e +82c2881ffb531d9aeb8ed181e45c840274e915fe7c68fe739b389c47d7ec37bb0b755682602b410701935fe06f3913b5c8c2673005c9be92ce2eca5254db1780131f88a9ffb089aff222a31f5bd5a1369967d346e51a2f27d2c1c3c1366a294fda19fdbcda143d82258b5b530a8cdec3f9c45894be4854f2ebab12b4a39652a6f0137c3cd4ad999d6ec99e2e2edafeb2380f4baca1d58226938f11cfae22e1f5 +fbab9368d87575fc20764e143a03c57a9adffbce7c0af1763fa46b72b85371c774be61389effc8c6448d85ed3e70241140c242207d6d710823247fe03c84e0f4495aa61ca533f4dd76a84ab01fc147e28ec3fd06ac8dbe5599c7111616feea1b6d5670b216bb9a00a36a7060f83968154ba95c87da09f7de7b8de2a64c9561cdd77c707a7bd457eb864ca32276976822c9738d66825e4b2c933ebe5b19293a94 +62d68f62c6c31a6b4823d70e14b6df69d85e932562469a57f81016d714572aeb33722860d9472d95bcf11037029ae844f435af4f80d8d3e32daeddad9c8c1706a11362e1b297f12e5729817fdfe2c56e14ebcfeff64f288701919a996278337eaa020ca4f0499693e3d5ed635a884d16b308f91e43a1d35a65a77731b6aba15c53d9c7cb27e934d50f618bff9ec6c5009df25ff2d63d4a977a7fa08fa9de8dec +52aed070d9c9df7f80a864f11359a784e8e69f3be8243c8870cc55b8a3028084e7c6fbeba45e753180902cf79961b8842df8c3397f4998240586b26d3f32d6f407b844f8c876783b36f13d3f426eb7f5634ed36cd9e6858d157f129a93c9b9173972542225ec11d3efda6aa4df27608154b946ee76df14fa38dd8b0ff19e57f24fe406723a67f2bb117f54ffd75513f4d8de3acda796be3226347b062a7e93e8 +41521f410309aa525e0d24a8fd69d0ed6fd71a85211276ce60f20dda50efe01f042a1e742155cd1e177cdd91036c51e289f051cd9fb3e20dbc3514bff025acb220fdede9cb397c9872e506b54942c440eab63e137b17cbff7b9809a58f2911558a334d1b2c8516cbe70f9c31d521d2f68f7599a936832a4a907a2bf6b3ce956a1d9c21e274b31100837e4bb822c186edc4c5704ac586311b115201afb34ad1a3 +3ec0710262ae38c3c3c2a05341dff0fe57b28bc9631d59b03307c0eae22754bc4b2fa823fa648ca6dec00839df02f5946323a12f90041b927fe0b68302ed84a4fc7d8032360ab9617c9f802ffbfe2f76ec35446202241d441f333ea97b3a16458547861075db10245eb723d96df6dfb6824b27203c06d7f34bbcc80cdad693c5f10bd825aa55b9026b93c4a849d19e685a65e56c7cd69e6a62efcedc22f29032 +2698bebac58f462c4e4220862777c895237de5093deb066e722825cf0fc948cb0726be48c01d2023a420e372662585f0e3b0d85794a57f3359c1d99df277498ccdbdb710bf1e9dd4d1cf67ec0120c00751282f3788887e9dfbc0052f97831852f464e487a01e402396c9db16eb4e6e3e5b3fe0bd5ed35885007370c019fc8ff1b4626373e59e39e40e1a826f753d8512dd0ea1b006982412a58abcface5cdd70 +afa29dd110fd6c1140461eada1f28fef7dc356074b1779fd967e702964b49a8bf76d01b69e0226c4965f8a579a54ce37cae60455512f5c3a67162ad4dff46c5070f8b4fec0f3df2d3701768f12470f1bb7391b75bb30713c553daa1b590739848d1fe725de478b93984fb0902c05e8025cdc27b4ed12fd3a35e5a0943070dac5f070d5c1930a84d24883f2f866b7c10a5eaf760ea8b4b536011ef1f6aa3e1442 +4b8cdafe7aaf62ac32eccfd683e33b97a1cfd49d3403d05e45f88a6221ba91f4e19420fd6b54afdafcf45c7fe4fdfcbba32a505275b708acc5d314bb6f1dde73504c16494be36bc04170b200cb310ab02af315885fe4fdc7863802702a7bfb2299c1d29c58c9eb081327e7d5213caaf3c95c8b8f3fdb348420e14563eee44a2c42fe5362f7000765ee762f634c00cc5245ad883c8ec86d940961bc799d0b34a7 +69a64a43f08a01fb5778150f25e401a56a5b7761016fe389bb0836cb58beda6daea8c351ebf995abaabb9647038c526f265958cf7744b2f2034a6e57584cdb4a390097822eac39635fef8eecc037eec892e03584f4ba48446bd3abf4da7d2e2fba5eac5a54185b746b4dc5b22fbdf15e0b1e212730c9205459e77c747d37269c3b6ff4eb4a6547670018227fd669b9d7f2b71fa11e6611b96b89bfa617d834cc +8216793155a04b38a6a759447b149f1f70badc37fd0be10c91904c98ff54b7e846f16bb18ddcb94da5ea275f0270d50fec803ba0209ba89748992bdd440f010d5925d74c75fdb5c08d6d5017b27036a15f6c09e6a644734a54abde1fc9e2fdc27d768786e47afa785068ad7ec89cde11bd4e0936784f92ad95bcb2618992425262f1e24aed99dbfc2e7e3fc2409d9107f35fbe0a2734c74b011ffa2def2bbabc +ccd01f5c033f0672397c858c9cce261bd38c3d0639cd6c224b87404405deb1d6d37cb48334bdfc6d3337e969e24afb9fdd51eab9cf237ba617d90aeabc7a235f55fbaee1a0e0438f0dc452741283f14857ae33e12842092757b7c9b78a4d17d6ebd53ede39c15a10908f81c0f54430a12da0edc260cb7eece8f1de17280ac4825bee176dcc17342409a86febce108309d1453be483d2a93d41cdd4cb25a74076 +1cf44e4b017034c6a48b7eb00c1d29ff9175b1848e7b28c92999553ba4047f134023a59fcfb8ec67ec68f8f550f88929e8f1ec08d132662f8cff6bb26c73e91f6137b13341ca46fbc78bb1adfdc146c0ef97e268db9678f99103292587a019eb96032b209f971902295cee4394d0f9ed3dbbbbd31819ce89dca4c79df06b18890dc06b7a18e555b594ca49ac2567dd3effb8cb0e1b4c4b00e8bb3652fb80bd6b +34a65910de02e60fd24e7e9d79124d47a22795aa88b237606ede9794d4e202a88f7a30a8364f8cecc26623a1fee2c7b635717d4a1adc4c708c0cf75b862789bdde08a5e83cff3598d3f350e8da8a4524597cd8443a11730f9b25914aa4baf76602f7728d4706f2d27f2188d6148c2ae86eb478bf13d6e88116f6f83d464ba94bfde3ff1a959bf017bf520829f87e70232dd90ec9887d7449eb8c4ac00fe0024d +06b11e5c1616cfcf0b12b37260b1c692a925ca660bb201055faef857ea5af91b710b262dafec77bbf56292e4c17cd41998427b9451a83123463669fce5d5bbc7b91682d04af65fbb6dde85e68f6498160f8fbacdceb9efd141de27f17f505695d9a153472524ec9f5ae888da1921c74d620632bd1a46335339c76914283a44842e92e88f9542afc72b105df70775b95a046fdd067c416a6f4e6fb9008e439ce2 +d40836565695f9c60cb8c14e2f5029d0b424786ddea0db1a4f1257d3b49bdae5e5211f4e366032967735651604913b4a1202387b22f7936373bf8f275d93885c4a5823b11c25ab423252b210d409512eee359c443db4e9ea409559f3d8b4d8f83f4250304407bfa73af3c64c5c2928f7a5d4b72c4c3bc654e54c32ec2c21a4422fe8b16f365278ce0953f3531b01b42ee86c508370a5fc7c30caa70c35209f5b +89242509fe2a81d33361f6a5bce205f6a003d5c9cf062b94139a2242842df6adc6227f1ada0d2108b7fc3bb67f7f56481da6db804f17888aac79cc9cc96f4a46299d0e031c14ad3b03938b374d351222fe6913515a1e27978827dfd2b44b08d0c144126b1054cfcd6f9f6f23dd604e588ae5d59d7d34fa196c3631ec51d67db55333e436058300a86da2b41340f06063f970115442c0a92959930d1d678998b6 +a6105724daff1aee3f1cef70545a6598af5edae58e9442e46115fd9c40ad9665be161868d3cb927fae6821753898cbff472c63034a87d603ccbce573dd60612aab7594ca72eae7e6b9280c157f8e09d421a5e79fa30225556b4694164d372f9ecb929c66e372bc90cb0a0b5ac5e00442aca84575723fb2cb8de354568a11bdffde58df4c631795358fab233cd152f05c533051cd782ed04a6acaecbb112e5df1 +83081eb3c0ffa6a31464bef8f2162800db954b7c591e6877fd1239e11d34b4cb880f5d6a16f656c7ddac424bb7d070e0d7c06ec7a876e62b1985ff19273e39087455bd45e9f9670db0c53f613bbae387369374e85f9dd3aa59feff07adeeeb48b8ba804e52edb86a1dc775f26f44bf6d98a6c545e06294bb91c3aad7dc9c74bc9fd3d50587c47f655552d8f334f7433b447fcd93346b00cf94d409149179abaa +094e8f596d63b23f2e84cc1f497b317b0eb7d16c1b4a84516a7303695fdc7621897582f430a93a2d71ad81c7384cb7048ed2603197322d1db1756d9510ce04d85cabc976ae23c0f9f0c21397eeb15cf1e7ee6cf4a2f0af36eab3de56c2d9ffb82a25e363f7e78636834beee3ae8c31672e014395513b2cacf95ee06abaf8efc0abe7d71f830460ba98d9453f6a19769a3c3a5f7787c63de02734b306b7618c5e +fdc6f257d53d89239637b754a7daa4123f8117ca6de1c12711a62b5d84f79a202b5398045bf870bb17a6de293e22ad501ce513978ae6dbd7d815bea46e88b97db2d060867294331e83e609329bf7ec5218e35ff966512fbb0f589c3c99189c1ace8eece35c9e4e71c636169265dea4b8b9dd77a68bb89f3996de8ca7b883c82e412d4c6838b5fbab99c54551be6cd2c9c2bf56230ee7df31473eef108798a186 +7483922168980b08e93ceca676e7dbcd1fba48dc57feed07e007ba960d355e6ace0e355febf3d6c4bd84743264efbfa6538deb9426f6105fcdae23b97f2d9b3cc04f67dfb0013a609f48876979d7ee1f992d48b491778deda0b9d534c725a24c2da5b9031b210f5d5e0910e655b88cf8320ffd0899cf8837ff8f37812c322204ea6dc6af21bf7b6b8c7c2863e1586c3e9978a632c5dc7e2f9ede4876cd6581fa +7087ece83be7f847d6f24aec67a80b21a57760d7cf90ab520a32f8f4cd17b6efdcf3a54da2c7da763e63c616ab2f67cab97d64d68e768e7cfb5f04c76e279a7fbebe87b339579fb0cbf4692672d14de1cf806a6bec7b8c403d77b623b341eb50944784e92cb5d0342f6b693acc79a6568b3c7fc2ee93ea47ab6b30436c843f5462d1e7d155b7f0a30f6cfbe2b7b0036f95e17953a32189bc70fae9b667ebefae +b49c94b4ef277fa560681ee9d6592cae31240588f2d72a86ff6a2b334367c9e1a7f8c390cf41f756bd7c90c194e2025d0dc37cc5d6ac46a066590cdead1ade5a950554c0e8367b474bd0d5866e60618927e4d6981c218e7eff8a4a20a71ea331a3ff5cf223af046eb7ae3a6183d0b2345714e41a42c02987444c88a18f04120aec1cc88f1fe0855fcc6497c1fd2cc503efe81064f4b6270aef56dfdea8316099 +e05aaad657e76e27b295aa656e22f43b47a4db338e95a39825762f39e6e29da46556cb46a76c45d6ca5e61c8d8ff2294fc257aa5f6d44d682c7f51b1bc32d2f6bbfbd6d5ceda5670055e92edd99017d4593e5fc49d1378c774dd4fe272bea67c0dfef85737d507369ad8b609c4ca3e5ba52d1ee5217e4457bfcabd06d8d4b5501e8cb95244a5eafad84fd7b2f87c54fa26e1365f1ffcaaa50fa159fab1e7bfeb +b3895f21b709d6618fb326a474c929e111bf0ed3a44e6f35808022c2da657c0bff05860332fece4a9fe1b49188e7c82f05e99f28b16ecce9568b1f746380fcf00aae1f19837753d246a5ac5c5a08d8b4ef85aa4ec8c3b17e024126eb50e81c840a0f93450ca01e1ed5117d817f135f7512ecdf1fe3bc465c51763fc967788b343db8ab16f9927326513a92e096272f86e515a19338e33bf0e010bfe98af332de +e7a7defa6acc34edaf6867ee76a1917eade7428120991a3ab66e033fa0f39ef94ca30d8b7a3596d7679ae0bf6249cf31fe661ebc0e00a03055f461b518074dd7b302752152e777e28fe1512ba81a60a7fe8566cac240c3bf6628c758feeb458b73dc7fb08fc6dcf8e3fd036828605a4380bcca584588498b141c327635c10ae8a84a345dc4b92fb1c711ec91205cb259bffd6e5509647717180c8fb8cc518182 +72bf73883eede4c7afa2db08d5d19c084aff5d215889d78467296d55f46b35955e676a3cdf41862dfebe021c091a48f42c9f4735bc3e2c22ea6f481646195c0f0b98338004ec69420d29d4dc9cf3462e6fa7d22a4461588a6ae3d4003fd5b468dfee34766dc881b88d109bcc038bf86a1f442bd92effe7ae5c3b56de08ea502f93f9d5aba2126690147b77b8f7aa921bdee57835d6a7d55fc875e839e88d2e33 +7a4252f313e9357485d1a2e0e1417989cb6f2c80c19e2aeee71477f5e0748c3d264362e1ba6bb05d556236879bcbd5da52af725ad1aea29e955217dc8a5b13397647a6d57e273afd3a884175703c59a61956bda54c6bf38f327c29b6359c7a1bd3a5f1bcf3b355ec3a07e29010cb742a462edb9ad2142920f5a8e1e1fe87c533389bb99cd57fd5a73b812bc24034eb62c2e3c17c2b52d0f8d65033c8b0b92e6e +3bd94f9b4f7ec90219acb46c1ac6a8b597db73f8945f3f3d88a5b99c72f7d112e39bbe961feb0b635d4e5709c6270ba848a0233cf216ab252dbf129efc1b0af8c8c3cf54bebaa72d5252848155b84d73486bbe6f49d4393a6c22ccf3c103666ebd738f24dc573a69f6f58672d100c5aa57a893eabfb34e4270027dbd9df9b7dd94bbe5ada3f5045776133b9d0cf826dcd1b458ab635cb005493167560cc23a4d +c12d6deacc0cb3256e8bb49888cfe3580bb7c397270ff4dec562b6c2a81468c831c303982112e4fab409b3fb51af69e4ef6abbbd1e52f12de44693fcead0b601da48dfa4781839f7c2b43fa11f86f727def889835e10ce34afbc985cc978dbe3eef04d3533b49d2fcbb4d9ad61311a4859cd802f5e6502ec87fe987f13ea54082bb6e5f6d6abadf273be65ac58221d9da10e8c928c8a90627858fb222a672161 +ac204457694bdbddc21a634ab13743a37f67360df1adb7bb090b5649ea63136dfc5408c3feecfd3b556a428bdafb58cfccc58ab4c96b409708a58e5a8edf944f95a710bcbebf004ef37654d125314b4f447d34ede6936bb1ec51643aa668456d7a440e947deb1eed3355d23678c5feebe0c9bd24514d07d03a9212c33f5aa0c8d8b75e6388204caf4d7264ddda2e062f2ef24dc441fbf9e928559daad5922057 +6303b8bae1b14c332df030685bc0a0adf32bd8b6f1431fca361cc555ba91b013cd3d07c525bc110760ab36cb47897e777c5c83c6896a3b70d0a4ca8f405ba22554079ef658f976351e334ece622acfa29c03842761c7dad92b03da3e80be471388d955f6cc73261e889086be607567a1ddf9bf4c6813e194157cb928bc597ffcbad2e851eb11d0bda4e8b3806d37d7e5eca24546e2ccf68542d9dad23b9335e2 +2b05d320464f3fd68e9c8461689a47dad22ca4877d10539062ffb1c22b599586daa9233e66fe12c3bf08527e1ca5ed250a6544c492bee68284559aa29516e83e6849e0dfc569301e77d3beee392ce5a134367cc78ea5283be538e88152be0230de9e7dc3ce05737a7645788cda9202362c5b4c5d792fc20f24f5a232fec877f659c558e2d4639c7630064f8ab91d585170387740910f96fc92a2a188d98d31f6 +e8ed25e6f2b5cd72277cff0831b34d9980566aa183ec0e171ee3539bcdd70c831154e2c415c0a4dbba166cedb8ff10e677546845f85c081aed78a280fde896eab49ee37f88afb3e85676bcaf4cdb8acdf528ee703dd5ec05d7290ec200bbda77df57251e31ea70c6785c72c9a4b19439fa6f45f1713ba7890d983dedd45e2cf05f07606db3000dd60b69c3363efa71f9a6ee1dd8fdbb15b15257ef8b8754e349 +e59604ffc914374ae1ef25c09e3eae08bcec726f68b58b1d8b514e26a0f32e091329dd591da4c203d6cdb2b47ed82e608965ccee9f4ae34374bcf6a6f6d982004382287087c322ba96e1a2e178f142a302725a434d03a6e9ae4f9241bc252ee92e7752be3ce483f5b89ce8e455958ed4d1f60eb23724136c04f54f03434925636d6fcf2ca884cb0b91031cf905b9386b3bb623e4b25dc59770b7bff8bc772068 +c4f4ea4fdd7233ec7d425387cfb2ed5ff71cf31624bac99b18e80d1976968ac7eb62c1fbe008951b781f4ace195573b3ecc4cdcd8f4cb7241d963cab67c030d609cb23f00c71e8f7c885ce8e948d61cba17657e9875e15f80c06168f798646b1e618801123929851a4c7838f97efd8b2418a894d3e7438c3331cc63cd87d1057e6b3f29f40c7a8da7a5a2501718e991c30f4f91fe32a19e6e313603424a56f89 +f1798850ee36bc07a1c9a95352c7147b9a083c24a0b3c655e419e02358f17a08235d4478815b065565d3c366044d19629d3420074961ee72583908d8d0de84949ee3a7889cbbbee77df74273b97ba5d96710df88816a3a6e180610d2c7c43305939cfcb36486cb994f843fc4acaa467ea34eae07d3170f0d40eb1e5a86ff9d17650844b33086f5e36b70c1383ea59d0a442b6b7bcf7b52f2a2fa91a2a2b0e6b6 +4cbd2002032ec927bdf62aaef9c2f8490484db405c68f6b65ce06f27fe1253bfec1e66a9aa34e0ff290ced66e0efd026f69082c91e78cb8b5006972d77ea501cc2e969f52069bb97b8fd3ad9cb70dd832c39904837de32195ee6642ac274117eab545e58caacd00b31f5d4603fda91fc249eb5d2268d95e1a323da630be718e5cd7c82722ea347322befcd8cd7a85e180a34487319ae14c105e8db5754c347b3 +acceaf70e88969fcc1ec9286388b5fb2d086d0d22617186b509b0675193070b773eb057772cd1d573fc29dfa756372c2b3c0e7610f479bf871fbc8f7fbeca8bd31c480abcf18b94ba2c9b2af81687330bda4aa170341c41c6282f58bb420120a6d3ac54e7eefa12cdba25724753f840f4a886817a566ca62e7d195a5766b6ecfc2df80f0401af48020da44c0eb7b387a502f075946707492f125452aab1849b0 +7a415b762daa914e1e9e29e466d4e780cc67fc0f3e662b2652c0864bb83eb7cbdac4ac6e56c249b2e38228790ae1628d95f16a04832d70941d44a3d39409a942a64e66dc1995605dff569a140724a6ac7faa5247f27d66e026accf9dc6fdc2db8040d2519c6838601a3cb8b10afb4db257dfd9743f53ca09d940b18b9989cd64f174dd8f74a19006ac446b12c03c1df38446b08d05a701f19e1c4169698df731 +4c251a5a914cab979d07f19c3da38e3ad678c87d17822b1109cb718fc909fb9ce285ffcbf05facd6eb1f348ccfbcc5b3c634946a49213564aaa34408e8d8f1b26dc531d3f56deec0ec5223589964b296a62b0c7eb3785c559447b73a098859d8dfb6a762c808d190f1439048a9993aef5cd637eadf28499239abbe7d9dec6e2399581c427ffe2321e8288ae147174bde7cd811bb3c78751dab36fc0f87092a7e +27a9a955ad770f38912dade0a36ff8530fded4bf57aceb950a10afe66bc88b2c5efc7a563ef269d00d90d5b169bf2c6791eff0cf9507160b653d0e19eb050ef90b04c8e21597d44705a6267f05977cd3a0cf2b7d6ed8130e7e80bdf668bb8ed75df11c4ea7def35cfbbedb043dd449c900f3dd213eb5d52ebeece6071589016a56ef0d4bfd8fe621b58fe1dbfafe0658a96cecce18eea088e4606346dce5e7c2 +95b3a1adbcf8d2987c2f2cba58d5f0a4ef6e0301e186b8d62a59acb6eb4be54867136f319f97470fedae743acd6bb0231ab7d60450e9856989e92c06ec014341af3e69f5e54da348868528df895b56458661d8fdebb3eb4d9aba2b854e897b7ae43cbc1c0f53c959e9ee3d2c3c0e87bed291053da3aaf181a40cc6f25c067be5bb329fdc22068ef11566a08c7918125c6eaffee31147b8004fcba000ceb9802e +a6cadd53a2621482b7d66ecc82dc4ea6431bc0191c3801ac6b705df38c7fffe469043e5002096aca4aaca5ef033cc2c5f884d5c339d9a648ffa9400098c7851b9f5990b3771fcf55b196b7c2723085ad11268daadd411967a6a545986c93b86b7f72387bf68dc6ae8dcfd21c7f57ba70b15f3f5517c5585f345ff751c7bf21dc1a33d396ba180a2cb22998fc05ca47b5525a2c150effb15ffd681006c479f0c5 +06df04188832b10afff94209d2aa1c8a123702de28234dcd3e0a7d36c1aa8449e6fa55e3e1e3d77d8424e87a45e38697755f84c49a99473797268113eb69098888947526035b246d00a630f6201ecc4075d8aa6604de73e2119e264e4c96751f2a67a2e46cf467a0df8f0520bcf4762b2715aba266d9b3f5e8fa67d12f9caac928b07ac3be99f41120655aa77f6433fc264673a92929e792187f87b5fda50cf2 diff --git a/detect_aes_in_ecb_s1.c b/detect_aes_in_ecb_s1.c new file mode 100644 index 0000000..b9befe5 --- /dev/null +++ b/detect_aes_in_ecb_s1.c @@ -0,0 +1,38 @@ +// https://cryptopals.com/sets/1/challenges/8 +#include "util/hex.h" +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* + * data/c8_s1.txt contains one ECB-encrypted hex-encoded ciphertext. + * note, the problem with ECB is that it is stateless and deterministic; + * the same plaintext block will always produce the same 16 byte ciphertext. + */ + +int main() { + char *line = NULL; + size_t capacity = 0, line_num = 1; + ssize_t len; + while ((len = getline(&line, &capacity, stdin)) > 0) { + line[len - 1] = '\0'; // truncate \n's + uint8_t *buf = read_hex(line, len - 1); + /* + * there are 10 blocks of 16 bytes (128bits) and now + * we have to determine which of those could be produced + * by AES-128 ECB (electronic code book) mode. + * we can simply look if any pair fo those blocks + * (10 * 9 / 2 = 45) are the same. + */ + for (size_t i = 0; i < 10; i++) + for (size_t j = i + 1; j < 10; j++) + if (memcmp(buf + 16 * i, buf + 16 * j, 16) == 0) + printf("found equal blocks...\n" + "line: %zu\ni: %zu\nj: %zu\n", + line_num, i, j); + line_num++; + free(buf); + } +} diff --git a/detect_single_char_xor_s1.c b/detect_single_char_xor_s1.c new file mode 100644 index 0000000..5f416d6 --- /dev/null +++ b/detect_single_char_xor_s1.c @@ -0,0 +1,37 @@ +// https://cryptopals.com/sets/1/challenges/4 +#include "util/hex.h" +#include "util/score.h" +#include "util/xor.h" +#include <limits.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int main() { + char *line = NULL; + size_t capacity = 0; + char *curr_best_line, *overall_best_line; + int best_so_far = INT_MAX; + FILE *f = fopen("data/c4_s1.txt", "rb"); + while (getline(&line, &capacity, f) != -1) { + *strrchr(line, '\n') = '\0'; + int curr_score = best_score_hex(line, &curr_best_line); + if (curr_score < best_so_far) { + best_so_far = curr_score; + overall_best_line = strdup(curr_best_line); + } + free(curr_best_line); + } + char *actual = overall_best_line; + char *expect = "Now that the party is jumping\n"; + if (strcmp(actual, expect) != 0) { + printf("does not match!!!\n" + "expected: %s\n" + "received: %s\n", + expect, actual); + return 1; + } else + printf("passed :^)\n%s\n", expect); + return 0; +} diff --git a/ecb_cbc_oracle_s2.c b/ecb_cbc_oracle_s2.c new file mode 100644 index 0000000..cc58537 --- /dev/null +++ b/ecb_cbc_oracle_s2.c @@ -0,0 +1,88 @@ +// https://cryptopals.com/sets/2/challenges/11 +#include "util/aes.h" +#include "util/random.h" +#include <openssl/types.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +/* + * tldr: generate random AES key (16 bytes) + * create function that encrypts data under an unknown + * (to us) key. eg. encryption_oracle(input) + * => [foo bar baz] + * function should append 5-10 bytes (count is random) + * before and after the plaintext. + * function should encrypt under ECB half the time, + * and CBC the other half (use random IV's) + * detect block cipher mode the function is using. + */ + +#define BLOCK_SIZE 16 +enum mode { CBC, ECB }; + +static int oracle(unsigned char *in, size_t in_len, unsigned char *out, + enum mode *used_mode) { + unsigned char key[BLOCK_SIZE]; + int encrypted_len; + init_with_random_bytes(key, BLOCK_SIZE); + // append 5-10 bytes at beginning & end + uint32_t begin_bytes = arc4random_uniform(6) + 5; + uint32_t end_bytes = arc4random_uniform(6) + 5; + // allocate new buffer for input and extra bytes + size_t new_in_len = in_len + begin_bytes + end_bytes; + unsigned char *new_in = malloc(new_in_len); + if (new_in == NULL) + return -1; + // initialize extra bytes randomly + arc4random_buf(new_in, begin_bytes); + memcpy(new_in + begin_bytes, in, in_len); + arc4random_buf(new_in + begin_bytes + in_len, end_bytes); + // randomly encrypt with ECB or CBC (uniformly) + if (arc4random_uniform(2) == 0) { + unsigned char iv[BLOCK_SIZE]; + init_with_random_bytes(iv, BLOCK_SIZE); + encrypted_len = cbc_encrypt(new_in, new_in_len, key, iv, out); + *used_mode = CBC; + } else { + EVP_CIPHER_CTX *ctx = evp_init(); + encrypted_len = evp_ecb_encrypt(ctx, new_in, new_in_len, key, out); + evp_cleanup(ctx); + *used_mode = ECB; + } + free(new_in); + return encrypted_len; +} + +/* + * send 43 identical bytes (0x00 in this case), that is enough to handle + * random addition of 5-10 bytes at beginning & end, and check if the second + * and third blocks returned are identical. they should be if it's ECB, + * and they are most likely not if it's CBC. + */ + +void guess() { + unsigned char zeros[43] = {0}; + unsigned char *encrypted = malloc(43 + BLOCK_SIZE); + enum mode used_mode, guessed_mode; + (void)oracle(zeros, 43, encrypted, &used_mode); + if (memcmp(encrypted + BLOCK_SIZE, encrypted + 2 * BLOCK_SIZE, BLOCK_SIZE) == + 0) + guessed_mode = ECB; + else + guessed_mode = CBC; + printf("used mode: %s\n", used_mode == ECB ? "ECB" : "CBC"); + printf("guessed_mode: %s\n", guessed_mode == ECB ? "ECB" : "CBC"); + if (used_mode == guessed_mode) + printf("gussed correct :^)\n"); + else + printf("wahh guess incorrect :^(\n"); + free(encrypted); +} + +int main() { + for (int i = 0; i < 10; i++) + guess(); + return 0; +} diff --git a/ecb_cut_paste_s2.c b/ecb_cut_paste_s2.c new file mode 100644 index 0000000..2642cc4 --- /dev/null +++ b/ecb_cut_paste_s2.c @@ -0,0 +1,183 @@ +// https://cryptopals.com/sets/2/challenges/13 +#include "util/aes.h" +#include "util/random.h" +#include <ctype.h> +#include <stdio.h> +#include <string.h> + +#define MAX_KEY_LEN 16 +#define MAX_VAL_LEN 128 + +enum state { START, KEY, EQ, VAL }; +static unsigned char c_key[16]; +struct profile { + int uid; + char *email; + char *role; +}; + +static char *serialize_profile(struct profile *p) { + char *str = NULL; + asprintf(&str, "email=%s&uid=%d&role=%s", p->email, p->uid, + p->role); // asprintf for memory safety :^) + return str; +} + +// far too laborous parser... +static struct profile *parse_profile(char *str) { + struct profile *profile = malloc(sizeof(struct profile)); + char *c = str; + enum state s = START; + char key[MAX_KEY_LEN] = {0}, val[MAX_VAL_LEN] = {0}; + size_t key_ix = 0, val_ix = 0; + while (*c != '\0') { + switch (s) { + case START: + // expect start of key + if (isalpha(*c) || *c == '_') { + key[key_ix++] = *c; + s = KEY; + } else { + printf("expected start of key, but got: %c\n", *c); + exit(1); + } + break; + case KEY: + if (isalnum(*c) || *c == '_') { + if (key_ix == MAX_KEY_LEN) { + printf("max key len exceeded: %d chars\n", MAX_KEY_LEN); + exit(1); + } + // continue with current key + key[key_ix++] = *c; + } else if (*c == '=') { + // NULL-terminate key + key[key_ix] = '\0'; + s = EQ; + } else { + printf("expected continuation of key or '=', but got: %c\n", *c); + exit(1); + } + break; + case EQ: + /* + * we could be smarter here and prevent non-printable characters, + * but actually the task itself has a solution which requires using + * '\x0b' char in the value. so we can simply skip '=' and '&' to disable + * SQL-injection style attacks. + */ + if (*c != '=' && *c != '&') { + val[val_ix++] = *c; + s = VAL; + } + break; + case VAL: + if (val_ix == MAX_VAL_LEN) { + printf("max val len exceeded: %d chars\n", MAX_VAL_LEN); + exit(1); + } + if (*c == '&') { + val[val_ix] = '\0'; + s = START; + key_ix = 0; + val_ix = 0; + if (strcmp(key, "email") == 0) + profile->email = strdup(val); + else if (strcmp(key, "uid") == 0) + profile->uid = atoi(val); + else if (strcmp(key, "role") == 0) + profile->role = strdup(val); + else { + printf("unknown key: %s\n", key); + exit(1); + } + } else if (*c != '=') { + val[val_ix++] = *c; + s = VAL; + } + break; + } + c++; + } + if (s == VAL) { + val[val_ix] = '\0'; + if (strcmp(key, "email") == 0) + profile->email = strdup(val); + else if (strcmp(key, "uid") == 0) + profile->uid = atoi(val); + else if (strcmp(key, "role") == 0) + profile->role = strdup(val); + else { + printf("unknown key: %s\n", key); + exit(1); + } + } + return profile; +} + +static char *profile_for(char *email) { + if (strchr(email, '=') != NULL || strchr(email, '&') != NULL) { + printf("no & and = in emails pls\n"); + exit(1); + } + struct profile *p = malloc(sizeof(struct profile)); + p->uid = 10; + p->email = email; + p->role = "user"; + char *res = serialize_profile(p); + free(p); + return res; +} + +static int encrypt_profile(char *profile_str, unsigned char *out) { + EVP_CIPHER_CTX *ctx = evp_init(); + int encrypted_len = evp_ecb_encrypt(ctx, (unsigned char *)profile_str, + strlen(profile_str), c_key, out); + evp_cleanup(ctx); + return encrypted_len; +} + +static int decrypt_profile(unsigned char *in, int in_len, unsigned char *out) { + EVP_CIPHER_CTX *ctx = evp_init(); + int decrypted_len = evp_ecb_decrypt(ctx, in, in_len, c_key, out); + evp_cleanup(ctx); + return decrypted_len; +} + +static struct profile *make_admin_profile() { + /* + * first create a profile for specially crafted username (10 bytes + "admin" + * + padding to full block) to have encrypted version of "admin" end of the + * string: 0123456789abcdef | 0123456789abcdef | 0123456789abcdef | + * 0123456789abcdef email=aaaaaaaaaa | admin........... | &uid=10&role=use | + * r.............. cut this block ^ + */ + char *fake = "aaaaaaaaaa" + "admin" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"; + // 11 chars to pad, so we use '0x0b * 11' + unsigned char encrypted_fake[64]; + encrypt_profile(profile_for(fake), encrypted_fake); + /* + * now replace 'user' part with block we cut before: + * 0123456789abcdef | 0123456789abcdef | 0123456789abcdef | 0123456789abcdef + * email=jack@test. | xyz&uid=10&role= | user | + * paste block here ^ + */ + unsigned char encrypted[48]; + encrypt_profile(profile_for("[email protected]"), encrypted); + // THE cut and paste + memcpy(&encrypted[32], &encrypted_fake[16], 16); + // decrypt, NULL-terminate, & parse + unsigned char decrypted[48]; + int decrypted_len = decrypt_profile(encrypted, 48, decrypted); + decrypted[decrypted_len] = '\0'; + return parse_profile((char *)decrypted); +} + +int main() { + init_with_random_bytes(c_key, 16); + struct profile *admin = make_admin_profile(); + printf("%s\n", serialize_profile(admin)); + return 0; +} diff --git a/fixed_xor_s1.c b/fixed_xor_s1.c new file mode 100644 index 0000000..44a6447 --- /dev/null +++ b/fixed_xor_s1.c @@ -0,0 +1,19 @@ +// https://cryptopals.com/sets/1/challenges/2 +#include "util/xor.h" +#include <string.h> + +int main() { + char *str = "1c0111001f010100061a024b53535009181c"; + char *cmp = "686974207468652062756c6c277320657965"; + char *actual = xor_hex(str, cmp); + char *expect = "746865206b696420646f6e277420706c6179"; + if (strcmp(actual, expect) != 0) { + printf("does not match!!!\n" + "expected: %s\n" + "received: %s\n", + expect, actual); + return 1; + } else + printf("passed :^)\n"); + return 0; +} diff --git a/hex_base64_s1.c b/hex_base64_s1.c new file mode 100644 index 0000000..9d7af61 --- /dev/null +++ b/hex_base64_s1.c @@ -0,0 +1,28 @@ +// https://cryptopals.com/sets/1/challenges/1 +#include "util/base64.h" +#include "util/hex.h" +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +// Always operate on raw bytes, never on encoded strings. Only use hex and +// base64 for pretty-printing. + +int main() { + char *str = "49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f" + "69736f6e6f7573206d757368726f6f6d"; + size_t hex_len = strlen(str); + uint8_t *bytes = read_hex(str, hex_len); + char *actual = base64_encode(bytes, hex_len / 2); + char *expect = + "SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t"; + if (strcmp(actual, expect) != 0) { + printf("does not match!!!\n" + "expected: %s\n" + "received: %s\n", + expect, actual); + return 1; + } else + printf("passed :^)\n"); + return 0; +} diff --git a/implement_cbc_mode_s2.c b/implement_cbc_mode_s2.c new file mode 100644 index 0000000..6015717 --- /dev/null +++ b/implement_cbc_mode_s2.c @@ -0,0 +1,21 @@ +// https://cryptopals.com/sets/2/challenges/10 +#include "util/aes.h" +#include "util/base64.h" +#include "util/io.h" + +/* + * in CBC mode each ciphertext is added to the next plaintext block prior to + * next call to cipher core. first plaintext block is added to the "0th + * ciphertext block" called the initialization vector (IV). + */ + +int main() { + size_t ciphertext_len; + uint8_t *ciphertext = base64_decode(read_non_space(stdin), &ciphertext_len); + unsigned char *key = (unsigned char *)"YELLOW SUBMARINE", iv[16] = {0}; + unsigned char *decrypted = malloc(ciphertext_len + 16); // inl + block_size + int decrypted_len = + cbc_decrypt(ciphertext, ciphertext_len, key, iv, decrypted); + fwrite(decrypted, 1, decrypted_len, stdout); + return 0; +} diff --git a/implement_padding_s2.c b/implement_padding_s2.c new file mode 100644 index 0000000..304725b --- /dev/null +++ b/implement_padding_s2.c @@ -0,0 +1,26 @@ +// https://cryptopals.com/sets/2/challenges/9 +#include "util/pkcs7_padding.h" +#include <stdio.h> +#include <string.h> + +/* + * a block cipher transforms fixed-size block (8/16 bytes) of plaintext into + * ciphertext. but we rarely want to transform a single block; we encrypt + * irregularly-sized messages. the odd size of these messages can be accounted + * for using padding, specifically PKCS#7 scheme. eg. "YELLOW SUBMARINE" padded + * to 20 bytes would be "YELLOW SUBMARINE\x04\x04\x04\x04" (0x04 indicates EOT + * or end-of-transmission) + */ + +int main() { + uint8_t *str = (uint8_t *)"YELLOW SUBMARINE", *res; + size_t curr_len = strlen((char *)str); + ssize_t new_len = add_padding(str, curr_len, 20, &res); + if (new_len == -1) { + fprintf(stderr, "add_padding() failed\n"); + exit(1); + } + fwrite(res, 1, new_len, stdout); + free(res); + printf("curr len: %zu\nnew_len: %zd\n", curr_len, new_len); +} diff --git a/mt19937_mtrng_s3.c b/mt19937_mtrng_s3.c new file mode 100644 index 0000000..b74c0fa --- /dev/null +++ b/mt19937_mtrng_s3.c @@ -0,0 +1,20 @@ +// https://cryptopals.com/sets/3/challenges/21 +#include "util/mersenne_twister.h" +#include <stdio.h> + +int main() { + seed(0); + // extra basic statistical test; checks whether + // lowest significant bit is random enough :^) + //(borrowed from testU01) + int even = 0, odd = 0; + for (int i = 0; i < 10000; i++) { + uint32_t r = extract(); + if (r % 2 == 0) + even++; + else + odd++; + } + printf("even: %d, odd: %d\n", even, odd); + return 0; +} diff --git a/pkcs7_padding_validation_s2.c b/pkcs7_padding_validation_s2.c new file mode 100644 index 0000000..7e17e21 --- /dev/null +++ b/pkcs7_padding_validation_s2.c @@ -0,0 +1,31 @@ +// https://cryptopals.com/sets/2/challenges/15 +#include "util/pkcs7_padding.h" +#include <assert.h> +#include <stdbool.h> +#include <string.h> + +/* + * write function that takes plaintext, determines if it has valid + * PKCS#7 padding, and strips the padding off. + * eg. string "ICE ICE BABY\x04\x04\x04\x04" is valid + * string "ICE ICE BABY\x05\x05\x05\x05" is invalid + * (0x04 == EOT) + */ + +int main() { + char *good = "ICE ICE BABY\x04\x04\x04\x04"; + char *bad1 = "ICE ICE BABY\x05\x05\x05\x05"; + char *bad2 = "ICE ICE BABY\x01\x02\x03\x04"; + assert(padding_len((uint8_t *)good, strlen(good)) == 4); + assert(padding_len((uint8_t *)bad1, strlen(bad1)) == -1); + assert(padding_len((uint8_t *)bad2, strlen(bad2)) == -1); + /* + * stripping the padding amounts to just treating the byte string + * as a shorter buffer (len - og len - padding_len(...)). we can + * also allocate a smaller buffer for it, copy it there, and free + * the original one, but in general the string can be allocated + * on the stack as well so we don't provide any generic function + * for stripping. + */ + return 0; +} diff --git a/repeating_key_xor_s1.c b/repeating_key_xor_s1.c new file mode 100644 index 0000000..b1376f6 --- /dev/null +++ b/repeating_key_xor_s1.c @@ -0,0 +1,28 @@ +// https://cryptopals.com/sets/1/challenges/5 +#include "util/hex.h" +#include "util/xor.h" +#include <stdint.h> +#include <string.h> + +int main() { + uint8_t msg[] = "Burning 'em, if you ain't quick and nimble\n" + "I go crazy when I hear a cymbal", + key[] = "ICE"; + int msg_len = sizeof msg - 1, + key_len = sizeof key_len - 1; //-1 because \0 at end + uint8_t *xor_d = xor_with_repeating_key(msg, msg_len, key, key_len); + char *actual = write_hex(xor_d, msg_len); + char *expect = "0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a262" + "26324272765272" + "a282b2f20430a652e2c652a3124333a653e2b2027630c692b202831652863" + "26302e27282f"; + if (strcmp(actual, expect) != 0) { + printf("does not match!!!\n" + "expected: %s\n" + "received: %s\n", + expect, actual); + return 1; + } else + printf("passed :^)\n"); + return 0; +} diff --git a/single_byte_ecb_decryption_harder_s2.c b/single_byte_ecb_decryption_harder_s2.c new file mode 100644 index 0000000..f79627e --- /dev/null +++ b/single_byte_ecb_decryption_harder_s2.c @@ -0,0 +1,207 @@ +// https://cryptopals.com/sets/2/challenges/14 +#include "util/aes.h" +#include "util/base64.h" +#include "util/random.h" +#include <assert.h> +#include <string.h> + +#define MAX_BLOCK_SIZE 128 +#define MAX_PREFIX_SIZE 256 +#define MAX_SECRET_SIZE 512 + +static unsigned char key[16]; +static size_t prefix_len; +static unsigned char *prefix; + +static int oracle(unsigned char *in, size_t in_len, unsigned char *out) { + size_t secret_len; + char *secret_str = + "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg" + "aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq" + "dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg" + "YnKK"; + uint8_t *secret = base64_decode(secret_str, &secret_len); + if (secret == NULL) { + fprintf(stderr, "base64_decode() failed\n"); + exit(1); + } + // prepend randomly initialized 'prefix_len' bytes + // and append 'secret' to end of input + size_t new_in_len = prefix_len + in_len + secret_len; + unsigned char *new_in = malloc(new_in_len); + if (new_in == NULL) + return -1; + memcpy(new_in, prefix, prefix_len); + memcpy(new_in + prefix_len, in, in_len); + memcpy(new_in + prefix_len + in_len, secret, secret_len); + // encrypt with ECB + EVP_CIPHER_CTX *ctx = evp_init(); + int encrypted_len = evp_ecb_encrypt(ctx, new_in, new_in_len, key, out); + evp_cleanup(ctx); + free(new_in); + return encrypted_len; +} + +static ssize_t detect_block_size() { + size_t first_increase = 0, second_increase = 0; + size_t prev_out_size, curr_out_size; + unsigned char *in, *out; + int res = -1; + in = calloc(MAX_BLOCK_SIZE, 1); + out = calloc(2 * MAX_BLOCK_SIZE + MAX_SECRET_SIZE, 1); + if (in == NULL || out == NULL) + goto error; + prev_out_size = oracle(in, 1, out); + if (prev_out_size < 0) + goto error; + for (size_t i = 2; i <= MAX_BLOCK_SIZE; i++) { + curr_out_size = oracle(in, i, out); + if (curr_out_size < 0) + goto error; + else { + if ((size_t)curr_out_size != prev_out_size) { + prev_out_size = curr_out_size; + if (first_increase == 0) + first_increase = i; + else + second_increase = i; + break; + } + } + } + res = first_increase == 0 || second_increase == 0 + ? -1 + : second_increase - first_increase; +error: + free(in); + free(out); + return res; +} + +static ssize_t detect_total_len(size_t block_size) { + ssize_t res = -1; + unsigned char *out = + malloc(MAX_SECRET_SIZE + 2 * block_size + MAX_PREFIX_SIZE); + unsigned char *in = malloc(block_size); + int enc_size = oracle(in, 0, out); + if (enc_size == -1) + goto early_exit; + for (size_t i = 1; i <= block_size; i++) { + if (oracle(in, i, out) != enc_size) { + res = (enc_size / block_size - 1) * block_size + (block_size - i); + break; + } + } +early_exit: + free(out); + return res; +} + +static ssize_t detect_prefix_len(ssize_t block_size) { + ssize_t res = -1; + size_t out_size = MAX_SECRET_SIZE + 2 * block_size + MAX_PREFIX_SIZE; + unsigned char *out1 = malloc(out_size), *out2 = malloc(out_size); + unsigned char *one = calloc(block_size, 1), *two = calloc(block_size, 1); + one[block_size - 1] = 0x01; + two[block_size - 1] = 0x02; + int enc_size = oracle(&one[block_size - 1], 1, out1); + if (enc_size == -1) + goto early_exit; + oracle(&two[block_size - 1], 1, out2); + size_t lower_limit; + for (size_t i = block_size; i <= (size_t)enc_size; i += block_size) { + if (memcmp(out1, out2, i) == 0) + lower_limit = i; + else + break; + } + size_t upper_limit = lower_limit + block_size - 1; + for (size_t i = upper_limit; i >= lower_limit; i--) { + size_t pad_size = block_size - (i % block_size); + enc_size = oracle(&one[block_size - pad_size - 1], pad_size + 1, out1); + oracle(&two[block_size - pad_size - 1], pad_size + 1, out2); + if (memcmp(out1, out2, i) == 0) + res = i; + break; + } +early_exit: + free(out1); + free(out2); + free(one); + free(two); + return res; +} + +static int decrypt_next_char(size_t block_size, size_t prefix_len, + unsigned char *known_chars, size_t known_len) { + size_t blocks_needed = 1 + (known_len + prefix_len) / block_size; + size_t total_size = block_size * blocks_needed; + size_t padding_needed = total_size - prefix_len - known_len - 1; + int res = -1; + unsigned char *in = calloc(block_size, block_size); + unsigned char *out = malloc(total_size + block_size + MAX_SECRET_SIZE); + unsigned char *c_block = NULL; + if (oracle(in, padding_needed, out) < 0) + goto early_exit; + // save last relevant block of output for comparison + unsigned char *last_relevant_block = out + (blocks_needed - 1) * block_size; + c_block = malloc(block_size); + memcpy(c_block, last_relevant_block, block_size); + // copy 'known_chars' into input for checking + memcpy(in + padding_needed, known_chars, known_len); + for (uint i = 0; i < 256; i++) { + in[total_size - prefix_len - 1] = (unsigned char)i; + if (oracle(in, total_size - prefix_len, out) < 0) + goto early_exit; + if (memcmp(last_relevant_block, c_block, block_size) == 0) + res = i; + break; + } +early_exit: + free(in); + free(out); + free(c_block); + return res; +} + +int main() { + init_with_random_bytes(key, 16); + // initialize random prefix + prefix_len = (size_t)arc4random_uniform(MAX_PREFIX_SIZE + 1); + prefix = malloc(prefix_len); + init_with_random_bytes(prefix, prefix_len); + ssize_t block_size = detect_block_size(); + if (block_size == -1) { + printf("cannot determine block size\n"); + exit(1); + } + ssize_t total_len = detect_total_len((size_t)block_size); + if (total_len == -1) { + printf("cannot determine total len\n"); + exit(1); + } + ssize_t guessed_prefix_len = detect_prefix_len((size_t)block_size); + if (guessed_prefix_len == -1) { + printf("cannot determine prefix len\n"); + exit(1); + } + ssize_t secret_len = total_len - guessed_prefix_len; + assert(secret_len >= 0); + printf("block_size=%zd, total_len=%zd, prefix_len=%zd, " + "guessed_prefix_len=%zd, secret_len=%zd", + block_size, total_len, prefix_len, guessed_prefix_len, secret_len); + // decrypt message byte-by-byte using oracle + unsigned char *secret = calloc(MAX_SECRET_SIZE, 1); + for (size_t i = 0; i < (size_t)secret_len; i++) { + int next_char = + decrypt_next_char(block_size, guessed_prefix_len, secret, i); + if (next_char < 0) { + printf("cannot decrypt next char D:\n"); + exit(1); + } + secret[i] = (unsigned char)next_char; + } + // write message!!! + fwrite(secret, 1, secret_len, stdout); + free(secret); +} diff --git a/single_byte_ecb_decryption_s2.c b/single_byte_ecb_decryption_s2.c new file mode 100644 index 0000000..f4b5def --- /dev/null +++ b/single_byte_ecb_decryption_s2.c @@ -0,0 +1,150 @@ +// https://cryptopals.com/sets/2/challenges/12 +#include "util/aes.h" +#include "util/base64.h" +#include "util/random.h" +#include <string.h> + +#define MAX_BLOCK_SIZE 128 +#define MAX_SECRET_SIZE 512 + +static unsigned char key[16]; + +static int oracle(unsigned char *in, size_t in_len, unsigned char *out) { + size_t secret_len; + char *secret_str = + "Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg" + "aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq" + "dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg" + "YnKK"; + uint8_t *secret = base64_decode(secret_str, &secret_len); + if (secret == NULL) { + fprintf(stderr, "base64_decode() failed\n"); + exit(1); + } + // append 'secret' to end of input + size_t new_in_len = in_len + secret_len; + unsigned char *new_in = malloc(new_in_len); + if (new_in == NULL) + return -1; + memcpy(new_in, in, in_len); + memcpy(new_in + in_len, secret, secret_len); + // encrypt with ECB + EVP_CIPHER_CTX *ctx = evp_init(); + int encrypted_len = evp_ecb_encrypt(ctx, new_in, new_in_len, key, out); + evp_cleanup(ctx); + free(new_in); + return encrypted_len; +} + +static ssize_t detect_block_size() { + size_t first_increase = 0, second_increase = 0, prev_out_size, curr_out_size; + unsigned char *in, *out; + int res = -1; + in = calloc(MAX_BLOCK_SIZE, 1); + out = calloc(2 * MAX_BLOCK_SIZE + MAX_SECRET_SIZE, 1); + if (in == NULL || out == NULL) + goto error; + prev_out_size = oracle(in, 1, out); + if (prev_out_size < 0) + goto error; + for (size_t i = 2; i <= MAX_BLOCK_SIZE; i++) { + curr_out_size = oracle(in, i, out); + if (curr_out_size < 0) + goto error; + else { + if ((size_t)curr_out_size != prev_out_size) { + prev_out_size = curr_out_size; + if (first_increase == 0) + first_increase = i; + else { + second_increase = i; + break; + } + } + } + } + res = first_increase == 0 || second_increase == 0 + ? -1 + : second_increase - first_increase; +error: + free(in); + free(out); + return res; +} + +static int detect_ecb(size_t block_size) { + unsigned char *two_blocks = calloc(2 * block_size, 1); + unsigned char *encrypted = malloc(3 * block_size + MAX_SECRET_SIZE); + int res = -1; + if (oracle(two_blocks, 2 * block_size, encrypted) < 0) + goto error; + res = (0 == memcmp(encrypted, encrypted + block_size, block_size)); +error: + free(two_blocks); + free(encrypted); + return res; +} + +static int decrypt_next_char(size_t block_size, unsigned char *known_chars, + size_t known_len) { + size_t blocks_needed = 1 + known_len / block_size; + size_t padding_needed = block_size - 1 - (known_len % block_size); + size_t total_size = block_size * blocks_needed; + int res = -1; + unsigned char *in = calloc(block_size, blocks_needed); + unsigned char *out = + malloc((blocks_needed + 1) * block_size + MAX_SECRET_SIZE); + if (oracle(in, padding_needed, out) < 0) { + free(in); + free(out); + return -1; + } + // save last relevant block of output for later comparison + unsigned char *last_relevant_block = out + (blocks_needed - 1) * block_size; + unsigned char *char_block = malloc(block_size); + memcpy(char_block, last_relevant_block, block_size); + // setup 'blocks_needed' of input for checking + memcpy(in + padding_needed, known_chars, known_len); + for (uint i = 0; i < 256; i++) { + in[total_size - 1] = (unsigned char)i; + if (oracle(in, total_size, out) < 0) + goto early_exit; + if (memcmp(last_relevant_block, char_block, block_size) == 0) { + res = i; + break; + } + } +early_exit: + free(in); + free(out); + free(char_block); + return res; +} + +int main() { + init_with_random_bytes(key, 16); + ssize_t s_block_size = detect_block_size(); + if (s_block_size < 0) { + fprintf(stderr, "unable to detect block_size\n"); + exit(1); + } + size_t block_size = (size_t)s_block_size; + int is_ecb = detect_ecb(block_size); + printf("detected block size: %zu, ecb: %d\n", block_size, is_ecb); + // decrypt message byte-by-byte using oracle + ssize_t i = 0; + unsigned char *decrypted = calloc(MAX_SECRET_SIZE, 1); + for (;;) { + int next_char = decrypt_next_char(block_size, decrypted, i); + if (next_char == -1) + break; + else { + decrypted[i] = (unsigned char)next_char; + i++; + } + } + // write message! + fwrite(decrypted, 1, i - 1, stdout); + free(decrypted); + return 0; +} diff --git a/single_byte_xor_cipher_s1.c b/single_byte_xor_cipher_s1.c new file mode 100644 index 0000000..5a318b8 --- /dev/null +++ b/single_byte_xor_cipher_s1.c @@ -0,0 +1,22 @@ +// https://cryptopals.com/sets/1/challenges/3 +#include "util/hex.h" +#include "util/score.h" +#include "util/xor.h" +#include <string.h> + +int main() { + char *str = + "1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736"; + char *actual; + char *expect = "Cooking MC's like a pound of bacon"; + (void)best_score_hex(str, &actual); + if (strcmp(actual, expect) != 0) { + printf("does not match!!!\n" + "expected: %s\n" + "received: %s\n", + expect, actual); + return 1; + } else + printf("passed :^)\n%s\n", expect); + return 0; +} diff --git a/util/aes.c b/util/aes.c new file mode 100644 index 0000000..85269ae --- /dev/null +++ b/util/aes.c @@ -0,0 +1,150 @@ +#include <assert.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <openssl/conf.h> +#include <openssl/err.h> +#include <openssl/aes.h> +#include "aes.h" +#include "xor.h" +#include "endian.h" +#include "pkcs7_padding.h" + +/*** utilitary stuff for EVP interface ***/ + +EVP_CIPHER_CTX *evp_init() { + return EVP_CIPHER_CTX_new(); +} + +void evp_cleanup(EVP_CIPHER_CTX *ctx) { + EVP_CIPHER_CTX_free(ctx); +} + +void handle_evp_errors() { + ERR_print_errors_fp(stderr); +} + +/* + * ECB mode (electronic code book) implemented with EVP + * high-level OpenSSL interface. in its simplest definition: + * the message is divided into blocks, padded, then each + * block is encrypted separately. +*/ + +int evp_ecb_encrypt(EVP_CIPHER_CTX *ctx, unsigned char *in, int in_len, unsigned char *key, unsigned char *out) { + int curr_out_len = 0, total_len = 0; + if (1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) + return -1; + if (1 != EVP_EncryptUpdate(ctx, out, &curr_out_len, in, in_len)) + return -1; + total_len = curr_out_len; + if (1 != EVP_EncryptFinal_ex(ctx, out + curr_out_len, &curr_out_len)) + return -1; + total_len += curr_out_len; + return total_len; +} + +int evp_ecb_decrypt(EVP_CIPHER_CTX *ctx, unsigned char *in, int in_len, unsigned char *key, unsigned char *out) { + int curr_out_len = 0, total_len = 0; + if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) + return -1; + if (1 != EVP_DecryptUpdate(ctx, out, &curr_out_len, in, in_len)) + return -1; + total_len = curr_out_len; + if (1 != EVP_DecryptFinal_ex(ctx, out + curr_out_len, &curr_out_len)) + return -1; + total_len += curr_out_len; + return total_len; +} + +/* + * CBC mode (cipher block chaining). in CBC mode, each block of + * plaintext is XOR'd with the previous ciphertext block before + * being encrypted. this way, each ciphertext block depends on all + * plaintext blocks processed up to that point. + */ + +int cbc_encrypt(unsigned char *in, int in_len, unsigned char *key, unsigned char *iv, unsigned char *out) { + unsigned char *padded; + ssize_t padded_len = add_padding((uint8_t*) in, in_len, AES_BLOCK_SIZE, &padded); + if (padded_len == -1) + return -1; + assert(padded_len % AES_BLOCK_SIZE == 0); + int num_of_blocks = padded_len / AES_BLOCK_SIZE; + unsigned char *curr_out = out, *curr_in = padded; + unsigned char *prev_out = iv, *xor_d; + AES_KEY ak; + AES_set_encrypt_key(key, 128, &ak); + for (int i = 0; i < num_of_blocks; i++) { + xor_d = xor(curr_in, prev_out, AES_BLOCK_SIZE); + AES_encrypt(xor_d, curr_out, &ak); + prev_out = curr_out; + curr_in += AES_BLOCK_SIZE; + curr_out += AES_BLOCK_SIZE; + free(xor_d); + } + free(padded); + return curr_out - out; +} + +int cbc_decrypt(unsigned char *in, int in_len, unsigned char *key, unsigned char *iv, unsigned char *out) { + //full number of blocks is expected + assert(in_len % AES_BLOCK_SIZE == 0); + int num_of_blocks = in_len / AES_BLOCK_SIZE; + unsigned char *curr_out = out, *curr_in = in; + unsigned char *prev_in = iv, *xor_d; + size_t bytes_decrypted = 0; + AES_KEY ak; + AES_set_decrypt_key(key, 128, &ak); + for (int i = 0; i < num_of_blocks; i++) { + AES_decrypt(curr_in, curr_out, &ak); + xor_d = xor(curr_out, prev_in, AES_BLOCK_SIZE); + memcpy(curr_out, xor_d, AES_BLOCK_SIZE); + prev_in = curr_in; + curr_in += AES_BLOCK_SIZE; + curr_out += AES_BLOCK_SIZE; + bytes_decrypted += AES_BLOCK_SIZE; + free(xor_d); + } + ssize_t pad_len = padding_len(out, bytes_decrypted); + if (pad_len == -1) + return -1; + return bytes_decrypted - (size_t) pad_len; +} + +int ctr_encrypt(unsigned char *in, int in_len, unsigned char *key, uint64_t nonce, unsigned char *out) { + AES_KEY ak; + AES_set_encrypt_key(key, 128, &ak); + //prepare staring 'in' block + //first nonce, second little endian, third goes the counter + unsigned char *ctr_in_block = malloc(16); + if (ctr_in_block == NULL) + return 0; + uint8_t *nonce_bytes = bytes_le_order(nonce); + memcpy(ctr_in_block, nonce_bytes, 8); + uint64_t counter = 0; + unsigned char *counter_bytes, *ctr_out_block = malloc(16); + if (ctr_out_block == NULL) { + free(ctr_in_block); + return 0; + } + for (int i = 0; i < in_len; i++) { + if (i % 16 == 0) { + //encrypt new counter block + counter_bytes = bytes_le_order(counter++); + memcpy(ctr_in_block + 8, counter_bytes, 8); + AES_encrypt(ctr_in_block, ctr_out_block, &ak); + free(counter_bytes); + } + out[i] = in[i] ^ ctr_out_block[i % 16]; + } + free(ctr_in_block); + free(ctr_out_block); + return 1; +} + +int ctr_decrypt(unsigned char *in, int in_len, unsigned char *key, uint64_t nonce, unsigned char *out) { + return ctr_encrypt(in, in_len, key, nonce, out); +} diff --git a/util/aes.h b/util/aes.h new file mode 100644 index 0000000..b37dd46 --- /dev/null +++ b/util/aes.h @@ -0,0 +1,40 @@ +#include <openssl/evp.h> + +//initializes EVP context +EVP_CIPHER_CTX *evp_init(); + +//cleans up EVP context +void evp_cleanup(EVP_CIPHER_CTX *ctx); + +//error handling function +void handle_evp_errors(); + +/* + * encrypts/decrypts AES-encoded data in ECB mode. takes byte string 'in', + * its length, & the key (128bit). writes output to 'out' and + * returns its size or -1 if anything goes wrong. + * handle_evp_errors() can be used to print error messages. + */ +int evp_ecb_encrypt(EVP_CIPHER_CTX *ctx, unsigned char *in, int in_len, unsigned char *key, unsigned char *out); +int evp_ecb_decrypt(EVP_CIPHER_CTX *ctx, unsigned char *in, int in_len, unsigned char *key, unsigned char *out); + +/* + * encrypts/decrypts AES-encoded data in CBC mode using low-level OpenSSL + * interface. takes byte string 'in', its length, the key, & 'iv' + * (128bit). writes output to 'out' and returns its size of -1 + * if anything goes wrong. handle_evp_errors() can be used + * to pint error messages. CBC also handles padding + */ +int cbc_encrypt(unsigned char *in, int in_len, unsigned char *key, unsigned char *iv, unsigned char *out); +int cbc_decrypt(unsigned char *in, int in_len, unsigned char *key, unsigned char *iv, unsigned char *out); + +/* + * encrypts/decrypts AES-encoded data in CTR mode using low-level + * OpenSSL interface. takes byte string 'in' its length, the key + * & the nonce. writes output to 'out' (its length should be the + * same as 'in' length). returns 0 on error, 1 on success. + * encryption and decryption methods are identical. + * no padding is made, as it is not needed. + */ +int ctr_encrypt(unsigned char *in, int in_len, unsigned char *key, uint64_t nonce, unsigned char *out); +int ctr_decrypt(unsigned char *in, int in_len, unsigned char *key, uint64_t nonce, unsigned char *out); diff --git a/util/base64.c b/util/base64.c new file mode 100644 index 0000000..7406aa5 --- /dev/null +++ b/util/base64.c @@ -0,0 +1,194 @@ +#include <ctype.h> +#include <math.h> //not part of c stdlib, so compile with -lm to link +#include <assert.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include "base64.h" + +//*** encoding ***// +static size_t base64_encoded_length(size_t bytes_len) { + return (size_t)(4 * ceil(bytes_len / 3.0)); +} + +static char encode_char(sextet s) { + char res; + assert(s < 64); + if (s < 26) + //0..25 -> 'A'...'Z' + res = 'A' + s; + else if (s < 52) + //26...51 -> 'a'...'z' + res = 'a' + (s - 26); + else if (s < 62) + //52...61 -> '0'...'9' + res = '0' + (s - 52); + else if (s == 62) + res = '+'; + else + res = '/'; + return res; +} + +static void encode_1byte(uint8_t byte1, char *encoded) { + sextet s[2]; + s[0] = byte1 >> 2; + s[1] = (byte1 & 0x03) << 4; + *encoded++ = encode_char(s[0]); + *encoded++ = encode_char(s[1]); + *encoded++ = '='; + *encoded = '='; +} + +static void encode_2byte(uint8_t byte1, uint8_t byte2, char *encoded) { + sextet s[3]; + s[0] = byte1 >> 2; + s[1] = ((byte1 & 0x03) << 4) | (byte2 >> 4); + s[2] = (byte2 & 0x0F) << 2; + for (int i = 0; i < 3; i++) + *encoded++ = encode_char(s[i]); + *encoded = '='; +} + +static void encode_3byte(uint8_t byte1, uint8_t byte2, uint8_t byte3, char *encoded) { + sextet s[4]; + s[0] = byte1 >> 2; + s[1] = ((byte1 & 0x03) << 4) | (byte2 >> 4); + s[2] = ((byte2 & 0x0F) << 2) | (byte3 >> 6); + s[3] = byte3 & 0x3F; + for (int i = 0; i < 4; i++) + *encoded++ = encode_char(s[i]); +} + +char *base64_encode(uint8_t *from, size_t len) { + long padded_bytes; + size_t to_len; + char *to, *curr; + //first determine size of required result buffer + //then malloc (with 1 extra byte for '\0') + to_len = base64_encoded_length(len); + to = (char*) malloc(to_len + 1); + if (to == NULL) + return NULL; + //handle 3 bytes at a time, + //converting into 4 sextets + curr = to; + for (unsigned long i = 0; i < len / 3; i++) { + encode_3byte(*from, *(from + 1), *(from + 2), curr); + from += 3; + curr += 4; + } + //convert leftover bytes (if any) + padded_bytes = len - 3 * (len / 3); + if (padded_bytes == 1) + encode_1byte(*from, curr); + else if (padded_bytes == 2) + encode_2byte(*from, *(from + 1), curr); + //aformentioned zero-terminate + to[to_len] = '\0'; + return to; +} + +//*** decoding ***// +#define WRONG_CHAR 64 + +static uint8_t decode_char(char c) { + uint8_t res; + assert(isalnum(c) || c == '+' || c == '/'); + if (isupper(c)) + res = c - 'A'; + else if (islower(c)) + res = c - 'a' + 26; + else if (isdigit(c)) + res = c - '0' + 52; + else if (c == '+') + res = 62; + else if (c == '/') + res = 63; + else { + fprintf(stderr, "unsupported char: %c\n", c); + res = WRONG_CHAR; + } + return res; +} + +static void decode_1byte(char *from, uint8_t *to) { + //read/decode 2 sextets + uint8_t s1 = decode_char(*from); + uint8_t s2 = decode_char(*(from + 1)); + //check for error + assert(s1 != WRONG_CHAR); + assert(s2 != WRONG_CHAR); + //combine 2 sextets (12 bits) into single byte + uint8_t byte1 = (s1 << 2) | (s2 >> 4); + *to++ = byte1; +} + +static void decode_2byte(char *from, uint8_t *to) { + //read/decode 3 sextets + uint8_t s1 = decode_char(*from); + uint8_t s2 = decode_char(*(from + 1)); + uint8_t s3 = decode_char(*(from + 2)); + //check for error + assert(s1 != WRONG_CHAR); + assert(s2 != WRONG_CHAR); + assert(s3 != WRONG_CHAR); + //combine 3 sextets (18 bits) into single byte + uint8_t byte1 = (s1 << 2) | (s2 >> 4); + uint8_t byte2 = (s2 << 4) | (s3 >> 2); + *to++ = byte1; + *to++ = byte1; +} + +static void decode_3byte(char *from, uint8_t *to) { + //read/decode 4 sextets + uint8_t s1 = decode_char(*from); + uint8_t s2 = decode_char(*(from + 1)); + uint8_t s3 = decode_char(*(from + 2)); + uint8_t s4 = decode_char(*(from + 3)); + //check for error + assert(s1 != WRONG_CHAR); + assert(s2 != WRONG_CHAR); + assert(s3 != WRONG_CHAR); + assert(s4 != WRONG_CHAR); + //combine 4 sextets (24 bits) into single byte + uint8_t byte1 = (s1 << 2) | (s2 >> 4); + uint8_t byte2 = (s2 << 4) | (s3 >> 2); + uint8_t byte3 = (s3 << 6) | s4; + *to++ = byte1; + *to++ = byte1; + *to++ = byte3; +} + +uint8_t *base64_decode(char *from, size_t *len) { + assert(from != NULL && len != NULL); + size_t text_len = strlen(from); + //risky assumption data is properly padded... + //so always use 4-character blocks + assert(text_len % 4 == 0); + //analyze padding... + char last = from[text_len - 1], pre_last = from[text_len - 2]; + int last_block_len = 3; + if (last == '=') + last_block_len = (pre_last == '=') ? 1 : 2; + //determine size of decoded bytes + size_t bytes_len = (text_len / 4 - 1) * 3 + last_block_len; + uint8_t *to = malloc(bytes_len); + if (to == NULL) { + perror("malloc"); + return NULL; + } + //read 4-bytes and write 3-bytes on each loop + unsigned long i; + for (i = 0; i < text_len / 4 - 1; i++) + decode_3byte(&from[4 * i], &to[3 * i]); + //handle last block (ideally with padding) + if (last_block_len == 3) + decode_3byte(&from[4 * i], &to[3 * i]); + else if (last_block_len == 1) + decode_1byte(&from[4 * i], &to[3 * i]); + else if (last_block_len == 2) + decode_2byte(&from[4 * i], &to[3 * i]); + *len = bytes_len; + return to; +} diff --git a/util/base64.h b/util/base64.h new file mode 100644 index 0000000..85279de --- /dev/null +++ b/util/base64.h @@ -0,0 +1,25 @@ +#include <stdint.h> +#include <stddef.h> + +typedef uint8_t sextet; +static size_t base64_encoded_length(size_t bytes_len); +static char encode_char(sextet s); +static void encode_1byte(uint8_t byte1, char *encoded); +static void encode_2byte(uint8_t byte1, uint8_t byte2, char *encoded); +static void encode_3byte(uint8_t byte1, uint8_t byte2, uint8_t byte3, char *encoded); +/* + * encodes to BASE64, returns pointer to newly allocated + * zero-terminated string or NULL if error. + * make sure to free() string!! + */ +char *base64_encode(uint8_t *from, size_t len); +static uint8_t decode_char(char c); +static void decode_1byte(char *from, uint8_t *to); +static void decode_2byte(char *from, uint8_t *to); +static void decode_3byte(char *from, uint8_t *to); +/* + * accept a NULL-terminated string, decode it as BASE64, + * returns a pointer to newly allocated byte array or NULL + * if error. writes the size of array to *len. + */ +uint8_t *base64_decode(char *from, size_t *len); diff --git a/util/endian.c b/util/endian.c new file mode 100644 index 0000000..ea3549a --- /dev/null +++ b/util/endian.c @@ -0,0 +1,10 @@ +#include <stdlib.h> +#include "endian.h" + +uint8_t *bytes_le_order(uint64_t in) { + uint8_t *res_ptr = malloc(8); + uint8_t *in_ptr = (uint8_t *) ∈ + for (int i = 0; i < 8; i ++) + res_ptr[i] = is_big_endian() ? in_ptr[8 - i] : in_ptr[i]; + return res_ptr; +} diff --git a/util/endian.h b/util/endian.h new file mode 100644 index 0000000..014703f --- /dev/null +++ b/util/endian.h @@ -0,0 +1,7 @@ +#include <stdint.h> + +static const uint16_t be_test = 1; +#define is_big_endian() ((* (char *) &be_test) == 0) + +//returns bytes in little endian order (smallest first) +uint8_t *bytes_le_order(uint64_t in); diff --git a/util/hamming.c b/util/hamming.c new file mode 100644 index 0000000..d9a1aad --- /dev/null +++ b/util/hamming.c @@ -0,0 +1,49 @@ +#include <assert.h> +#include <string.h> +#include "hamming.h" + +/* + * precalculated number 1-bits in corresponding bytes from 0 to 255 or + * (00000000 -> 0) to (11111111 -> 8). formatted as a readable 16x16 table. + * see htpps://en.wikipedia.org/wiki/Hamming_weight for more. + * precalculated table version is not most efficient approach, + * but simplest by use of 'precalculate_pop_count'. + */ +static size_t table[] = { +//0 1 2 3 4 5 6 7 8 9 A B C D E F + 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, //0x0 + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, //0x1 + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, //0x2 + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, //0x3 + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, //0x4 + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, //0x5 + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, //0x6 + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, //0x7 + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, //0x8 + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, //0x9 + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, //0xA + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, //0xB + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, //0xC + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, //0xD + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, //0xE + 4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8 //0xF +}; + +size_t pop_count(uint8_t byte) { + return table[byte]; +} + +size_t hamming_distance(char *s1, char *s2) { + size_t len1 = strlen(s1), len2 = strlen(s2), distance = 0; + assert(len1 == len2); + for (unsigned long i = 0; i < len1; i++) + distance += pop_count(s1[i] ^ s2[i]); + return distance; +} + +size_t hamming_distance_bytes(uint8_t *b1, uint8_t *b2, size_t len) { + size_t distance = 0; + for (unsigned long i = 0; i < len; i++) + distance += pop_count(b1[i] ^ b2[i]); + return distance; +} diff --git a/util/hamming.h b/util/hamming.h new file mode 100644 index 0000000..f9e382a --- /dev/null +++ b/util/hamming.h @@ -0,0 +1,14 @@ +#include <stdint.h> +#include <stddef.h> + +//returns number of 1-bits set in a byte. +//(so called "population count".. see wikipedia) +size_t pop_count(uint8_t byte); + +//takes two NULL-terminated strings of same length. +//returns number of differing bits. +size_t hamming_distance(char *s1, char *s2); + +//takes two byte arrays of same length & their length +//returns number of differing bits. +size_t hamming_distance_bytes(uint8_t *b1, uint8_t *b2, size_t len); diff --git a/util/hex.c b/util/hex.c new file mode 100644 index 0000000..dc446c8 --- /dev/null +++ b/util/hex.c @@ -0,0 +1,57 @@ +#include "hex.h" +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +static uint8_t char_to_four_bits(char c) { + uint8_t res; + if (c >= '0' && c <= '9') + res = c - '0'; + else if (c >= 'a' && c <= 'f') + res = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + res = c - 'A' + 10; + else { + fprintf(stderr, "to_byte: unrecognised hex char: %c\n", c); + res = 16; + } + return res; +} + +static char four_bits_to_char(uint8_t b) { + assert(b < 16); + return "0123456789abcdef"[b]; +} + +uint8_t *read_hex(char *hex_str, size_t hex_len) { + assert(hex_len % 2 == 0); + size_t bytes_len = hex_len / 2; + uint8_t *bytes = malloc(bytes_len); + if (bytes == NULL) + return NULL; + for (unsigned long i = 0; i < bytes_len; i++) { + char c1 = hex_str[2 * i], c2 = hex_str[2 * i + 1]; + uint8_t high = char_to_four_bits(c1), low = char_to_four_bits(c2); + if (high == 16 || low == 16) { //this is an error + free(bytes); + return NULL; + } + bytes[i] = high << 4 | low; + } + return bytes; +} + +char *write_hex(uint8_t *bytes, size_t bytes_len) { + size_t hex_len = bytes_len * 2 + 1; // +1 because '\0' + char *hex = malloc(hex_len); + if (hex == NULL) + return NULL; + for (unsigned long i = 0; i < bytes_len; i++) { + uint8_t high = bytes[i] >> 4, low = bytes[i] & 0x0F; + hex[2 * i] = four_bits_to_char(high); + hex[2 * i + 1] = four_bits_to_char(low); + } + hex[hex_len - 1] = '\0'; + return hex; +} diff --git a/util/hex.h b/util/hex.h new file mode 100644 index 0000000..ba91103 --- /dev/null +++ b/util/hex.h @@ -0,0 +1,12 @@ +#include <stdint.h> +#include <stdio.h> +/* + * accepts null-terminated hex-string (ie. "4AE6" or "4ae6") + * which should contain even number of hex digits. + * return corresponding malloc'd byte buffer + * whose length is half of the input string's + */ +static uint8_t char_to_four_bits(char c); +static char four_bits_to_char(uint8_t b); +uint8_t *read_hex(char *hex_str, size_t hex_len); +char *write_hex(uint8_t *bytes, size_t bytes_len); diff --git a/util/io.c b/util/io.c new file mode 100644 index 0000000..09a8aa0 --- /dev/null +++ b/util/io.c @@ -0,0 +1,76 @@ +#include <assert.h> +#include <ctype.h> +#include <stdlib.h> +#include "io.h" + +uint8_t *get_file_contents(FILE *stream, size_t *len) { + assert(len != NULL && stream != NULL); + size_t num_bytes; + uint8_t *buffer; + //jump to eof to determine size + if (fseek(stream, 0, SEEK_END) == -1) { + perror("fseek"); + return NULL; + } + long pos = ftell(stream); + if (pos == -1) { + perror("ftell"); + return NULL; + } else + num_bytes = (size_t) pos; + //jump back to beginning + if (fseek(stream, 0, SEEK_SET) == -1) { + perror("fseek"); + return NULL; + } + buffer = malloc(num_bytes); + if (buffer == NULL) { + perror("malloc"); + return NULL; + } + if (fread(buffer, 1, num_bytes, stream) != num_bytes) { + fprintf(stderr, "fread() failed\n"); + free(buffer); + return NULL; + } + *len = num_bytes; + return buffer; +} + +char *read_non_space(FILE *stream) { + int c, ix = 0; + char *str = malloc(START_SZ); //this means we have START_SZ - 1 chars available + size_t remaining_chars = START_SZ - 1, buf_sz = START_SZ; + for (;;) { + c = fgetc(stream); + if (feof(stream)) + break; + if (ferror(stream)) { + perror("fgetc"); + return NULL; + } + if (isspace(c)) + continue; + if (remaining_chars == 0) { + remaining_chars = buf_sz - 1; + if (buf_sz < MAX_SZ) + buf_sz *= 2; + else { + fprintf(stderr, "input larger than %ld bytes (too large)\n", MAX_SZ); + return NULL; + } + char *str_new = realloc(str, buf_sz); + if (str_new == NULL) { + free(str); + return NULL; + } else + str = str_new; + } + //append char to buffer + str[ix++] = c; + remaining_chars--; + } + //hopefully we've allocated enough space to append NULL-byte... + str[ix] = '\0'; + return str; +} diff --git a/util/io.h b/util/io.h new file mode 100644 index 0000000..893e56a --- /dev/null +++ b/util/io.h @@ -0,0 +1,23 @@ +#include <stdint.h> +#include <stdio.h> + +#define START_SZ 512 +#define MAX_SZ (1UL << 24) +//^ 16MB buffer is largest we allow + +/* + * reads whole contents of a file as byte buffer. + * allocates new buffer with size equal to file length in bytes. + * writes a size of that buffer in '*len', then returns a pointer + * to that new buffer. fseek() is used to determine file size, + * so this won't work on streams lacking support for seek(), eg. pipes. + * returns NULL and prints an error message if anything goes wrong!! + */ +uint8_t *get_file_contents(FILE *stream, size_t *len); + +/* + * reads all non-spaces characters from the stream (although + * skips SPC, TAB, EOL) and returns a newly allocated + * NULL-terminated string, or NULL if something goes wrong. + */ +char *read_non_space(FILE *stream); diff --git a/util/mersenne_twister.c b/util/mersenne_twister.c new file mode 100644 index 0000000..2d3059f --- /dev/null +++ b/util/mersenne_twister.c @@ -0,0 +1,68 @@ +/* + * base on pseudocode from + * https://en.wikipedia.org/wiki/Mersenne_Twister + * MT19937 version (32-bit word) + */ + +#include "mersenne_twister.h" + +//MT-32 algorithm coefficients +#define W 32UL +#define N 624UL +#define M 397UL +#define R 31UL +#define A 0x9908B0DFUL +#define U 11UL +#define D 0xFFFFFFFFUL +#define S 7UL +#define B 0x9D2C5680UL +#define T 15UL +#define C 0xEFC60000UL +#define L 18UL +#define F 1812433253UL //initialization constant + +//derived constants +#define LOWER_MASK ((1UL << R) - 1) +#define UPPER_MASK (~LOWER_MASK) + +//global state +static uint32_t MT[N], idx = N + 1; //essentially unseeded + +//generate next 'N' values +static void twist() { + for (uint32_t i = 0; i < N; i++) { + uint32_t x = (MT[i] & UPPER_MASK) + (MT[(i + 1) % N] & LOWER_MASK); + uint32_t x_a = x >> 1; + if ((x % 2) != 0) + x_a ^= A; + MT[i] = MT[(i + M) % N] ^ x_a; + } + idx = 0; +} + +void set_state(uint32_t *new_state) { + idx = N; + for (uint32_t i = 0; i < N; i++) + MT[i] = *new_state++; +} + +void seed(uint32_t seed) { + idx = N; + MT[0] = seed; + for (uint32_t i = 1; i < N; i++) + MT[i] = F * (MT[i - 1] ^ (MT[i - 1] >> (W - 2))) + i; +} + +uint32_t extract() { + if (idx >= N) { + if (idx > N) + seed(5489); //default seed + twist(); + } + uint32_t y = MT[idx++]; + y ^= (y >> U) & D; + y ^= (y << S) & B; + y ^= (y << T) & C; + y ^= (y >> L); + return y; +} diff --git a/util/mersenne_twister.h b/util/mersenne_twister.h new file mode 100644 index 0000000..dc2d3cf --- /dev/null +++ b/util/mersenne_twister.h @@ -0,0 +1,5 @@ +#include <stdint.h> + +void seed(uint32_t seed); +uint32_t extract(); +void set_state(uint32_t *new_state); diff --git a/util/obj/aes.o b/util/obj/aes.o Binary files differnew file mode 100644 index 0000000..bc408f1 --- /dev/null +++ b/util/obj/aes.o diff --git a/util/obj/base64.o b/util/obj/base64.o Binary files differnew file mode 100644 index 0000000..33e8b8e --- /dev/null +++ b/util/obj/base64.o diff --git a/util/obj/endian.o b/util/obj/endian.o Binary files differnew file mode 100644 index 0000000..8a1a4d4 --- /dev/null +++ b/util/obj/endian.o diff --git a/util/obj/hamming.o b/util/obj/hamming.o Binary files differnew file mode 100644 index 0000000..305be89 --- /dev/null +++ b/util/obj/hamming.o diff --git a/util/obj/hex.o b/util/obj/hex.o Binary files differnew file mode 100644 index 0000000..702cbbe --- /dev/null +++ b/util/obj/hex.o diff --git a/util/obj/io.o b/util/obj/io.o Binary files differnew file mode 100644 index 0000000..251d67d --- /dev/null +++ b/util/obj/io.o diff --git a/util/obj/pkcs7_padding.o b/util/obj/pkcs7_padding.o Binary files differnew file mode 100644 index 0000000..018a155 --- /dev/null +++ b/util/obj/pkcs7_padding.o diff --git a/util/obj/score.o b/util/obj/score.o Binary files differnew file mode 100644 index 0000000..474d034 --- /dev/null +++ b/util/obj/score.o diff --git a/util/obj/xor.o b/util/obj/xor.o Binary files differnew file mode 100644 index 0000000..3971fc6 --- /dev/null +++ b/util/obj/xor.o diff --git a/util/pkcs7_padding.c b/util/pkcs7_padding.c new file mode 100644 index 0000000..99f79d6 --- /dev/null +++ b/util/pkcs7_padding.c @@ -0,0 +1,40 @@ +#include <assert.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <inttypes.h> +#include "pkcs7_padding.h" + +size_t calc_padding_len(size_t data_len, size_t block_len) { + return block_len - (data_len % block_len); +} + +ssize_t add_padding(uint8_t *data, size_t data_len, size_t block_len, uint8_t **padded_data) { + size_t padding_len = calc_padding_len(data_len, block_len); + size_t padded_len = data_len + padding_len; + uint8_t *new_data; + assert(data && padded_data); + assert(data_len >= 0); + assert(block_len > 1 && block_len < 256); //refer to RFC-2315 + new_data = malloc(padded_len); + if (new_data == NULL) + return -1; + (void) memcpy(new_data, data, data_len); + for (size_t i = 0; i < padded_len; i++) + new_data[i] = padding_len; + *padded_data = new_data; + return padded_len; +} + +ssize_t padding_len(uint8_t *padded_data, size_t data_len) { + assert(padded_data); + if (data_len < 1) + return -1; + uint8_t n = padded_data[data_len - 1]; + if (n > data_len || n == 0) + return -1; + for (unsigned int i = 2; i <= n; i++) + if (padded_data[data_len - i] != n) + return -1; //padding error... abort + return (ssize_t) n; +} diff --git a/util/pkcs7_padding.h b/util/pkcs7_padding.h new file mode 100644 index 0000000..7677606 --- /dev/null +++ b/util/pkcs7_padding.h @@ -0,0 +1,20 @@ +#include <stdlib.h> +#include <inttypes.h> + +size_t calc_padding_len(size_t data_len, size_t block_len); + +/* + * accepts a byte string, its length and 'block_len'. + * pads the byte string, allocates a new buffer to contain + * the string & the padding, copies data, appends padding + * bytes, and writes the result to '*padded_data'. + * returns size of the padded block or -1 if error. +*/ +ssize_t add_padding(uint8_t *data, size_t data_len, size_t block_len, uint8_t **padded_data); + +/* + * accepts a byte string and its length. determines how much padding + * should be added to the string and returns number of bytes to be + * trimmed from the end of string, or -1 if error. +*/ +ssize_t padding_len(uint8_t *padded_data, size_t data_len); diff --git a/util/random.c b/util/random.c new file mode 100644 index 0000000..b1a9081 --- /dev/null +++ b/util/random.c @@ -0,0 +1,6 @@ +#include <stdlib.h> +#include "random.h" + +void init_with_random_bytes(unsigned char *data, size_t data_len) { + arc4random_buf(data, data_len); +} diff --git a/util/random.h b/util/random.h new file mode 100644 index 0000000..096a60b --- /dev/null +++ b/util/random.h @@ -0,0 +1,3 @@ +#include <stddef.h> + +void init_with_random_bytes(unsigned char *data, size_t data_len); diff --git a/util/score.c b/util/score.c new file mode 100644 index 0000000..ff5777c --- /dev/null +++ b/util/score.c @@ -0,0 +1,116 @@ +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <limits.h> +#include "score.h" +#include "hex.h" +#include "xor.h" + +static int cmp_by_freq(const void *x, const void *y) { + const struct freq *sx = x; + const struct freq *sy = y; + return sy->freq - sx->freq; //descending order +} + +static int char_canonical_order(char c) { + assert((c >= 'a' && c <= 'z') || c == ' '); + char canonical_order[] = FREQ_ORDER; + char *cp = strchr(canonical_order, c); + return cp - canonical_order; +} + +/* + * this scoring function is based on frequency of english letters. + * we count occurences of all letters and space and sort them + * based on frequencies, producing a permutation of 26 letters (and Space character). + * then add up distances between positions of top N letters (20, for example (SIGNIFICANT_LETTERS constant)) of our orders + * with the canonical order. + */ +static int score_step(uint8_t *str, size_t len, int n, size_t start, size_t step) { + assert(str != NULL); + assert(n >= 1 && n <= ALPHABET_SZ + 1); //+1 for space character + struct freq f[ALPHABET_SZ + 1]; + for (int i = 0; i < ALPHABET_SZ + 1; i++) { + struct freq j; + if (i == ALPHABET_SZ) + j.c = ' '; + else + j.c = 'a' + i; + j.freq = 0; + f[i] = j; + } + //count frequencies + int score = 0; + for (size_t i = start; i < len; i += step) { + char c = str[i]; + if (c >= 'a' && c <= 'z') + f[c - 'a'].freq++; + else if (c >= 'A' && c <= 'Z') + f[c - 'A'].freq++; + else if (c == ' ') + f[ALPHABET_SZ].freq++; + else if (!ispunct(c)) + score += n; //"penalty" for non-punctuation characters + } + //sort & calculate final score! + qsort(f, ALPHABET_SZ + 1, sizeof(struct freq), cmp_by_freq); + for (int i = 0; i < n; i++) + score += abs(char_canonical_order(f[i].c) - i); + return score; +} + +static int score(uint8_t *str, size_t len, int n) { + return score_step(str, len, n, 0, 1); +} + +int best_score_hex(char *hex_str, char **res_str) { + size_t hex_len = strlen(hex_str); + size_t bytes_len = hex_len / 2; + uint8_t *bytes = read_hex(hex_str, hex_len); + return best_score_raw(bytes, bytes_len, res_str); +} + +int best_score_raw(uint8_t *bytes, size_t bytes_len, char **res_str) { + int min_score = INT_MAX; + uint8_t *best_string; + uint8_t cypher, key = 0; + for (;;) { + //xor them with key + uint8_t *xor_d = xor_with_byte_key(bytes, key, bytes_len); + //score string & check for improvement + int curr_score = score(xor_d, bytes_len, SIGNIFICANT_LETTERS); + if (curr_score < min_score) { + min_score = curr_score; + cypher = key; + best_string = xor_d; + } else + free(xor_d); + key++; + if (key == 0) + break; //pass complete + } + char *best_str_terminated = malloc(bytes_len + 1); + memcpy(best_str_terminated, best_string, bytes_len); + best_str_terminated[bytes_len] = '\0'; + free(best_string); + *res_str = best_str_terminated; + return min_score; +} + +uint8_t best_key_step(uint8_t *bytes, size_t bytes_len, size_t start, size_t step) { + int min_score = INT_MAX; + uint8_t best_key = 0, key = 0; + for (;;) { + uint8_t *xor_d = xor_with_byte_key(bytes, key, bytes_len); + int curr_score = score_step(xor_d, bytes_len, SIGNIFICANT_LETTERS, start, step); + if (curr_score < min_score) { + min_score = curr_score; + best_key = key; + } + free(xor_d); + if (++key == 0) + break; //pass complete + } + return best_key; +} diff --git a/util/score.h b/util/score.h new file mode 100644 index 0000000..ce0eb31 --- /dev/null +++ b/util/score.h @@ -0,0 +1,39 @@ +#include <stdio.h> +#include <stdint.h> + +#define ALPHABET_SZ 26 +#define SIGNIFICANT_LETTERS 20 +#define FREQ_ORDER " etaoinshrdlcumwfgypbvkjxqz" +//^ see https://en.wikipedia.org/wiki/Letter_frequency + +struct freq { + char c; + int freq; +}; + +static int cmp_by_freq(const void *x, const void *y); +static int char_canonical_order(char c); +static int score_step(uint8_t *str, size_t len, int n, size_t start, size_t step); +static int score(uint8_t *str, size_t len, int n); + +/* + * returns a best score for the encoded string in hex and inits + * res_str, which points to the best string. + * it is malloc'd (so free() it!!!) + * "score" is a measurement of how likely the input string is + * to be human readable. lower score == better + */ +int best_score_hex(char *hex_str, char **res_str); + +/* + * returns a best score for byte buffer and inits res_str, + * which points to the best string. + * it is malloc'd (so free() it!!!) + */ +int best_score_raw(uint8_t *bytes, size_t bytes_len, char **res_str); + +/* + * returns the key corresponding to the best score for byte buffer when + * only every step'th byte starting from byte 'start' is taken into accont + */ +uint8_t best_key_step(uint8_t *bytes, size_t bytes_len, size_t start, size_t step); diff --git a/util/xor.c b/util/xor.c new file mode 100644 index 0000000..231c943 --- /dev/null +++ b/util/xor.c @@ -0,0 +1,43 @@ +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include "hex.h" +#include "xor.h" + +uint8_t *xor(uint8_t *b1, uint8_t *b2, size_t len) { + uint8_t *res = malloc(len); + if (res == NULL) { + printf("out of bounds\n"); + return NULL; + } + for (uint i = 0; i < len; i++) + res[i] = b1[i] ^ b2[i]; + return res; +} + +char *xor_hex(char *s1, char *s2) { + assert(strlen(s1) == strlen(s2)); + size_t hex_len = strlen(s1), bytes_len = hex_len / 2; + uint8_t *b1 = read_hex(s1, hex_len); + uint8_t *b2 = read_hex(s2, hex_len); + uint8_t *res = xor(b1, b2, bytes_len); + return write_hex(res, bytes_len); +} + +uint8_t *xor_with_byte_key(uint8_t *msg, uint8_t key, size_t len) { + return xor_with_repeating_key(msg, len, &key, 1); +} + +uint8_t *xor_with_repeating_key(uint8_t *msg, size_t msg_len, uint8_t *key, size_t key_len) { + uint8_t *res = malloc(msg_len); + if (res == NULL) { + printf("out of bounds\n"); + return NULL; + } + uint j = 0; + for (uint i = 0; i < msg_len; i++) { + res[i] = msg[i] ^ key[j]; + j = (j + 1) % key_len; + } + return res; +} diff --git a/util/xor.h b/util/xor.h new file mode 100644 index 0000000..fdf6a15 --- /dev/null +++ b/util/xor.h @@ -0,0 +1,27 @@ +#include <stdio.h> +#include <inttypes.h> + +/* + * return bitwise xor of two byte buffers of the same length + * the resulting byte buffer is malloc'd (so free() it!!!) + */ +uint8_t *xor(uint8_t *b1, uint8_t *b2, size_t len); + +/* + * return bitwise xor of two buffers represented as hex strings + * result will be encoded as hex string. the resuling string + * is malloc'd (so free() it!!!) + */ +char *xor_hex(char *s1, char *s2); + +/* + * returns bitwise xor of a single byte key and provided byte message + * the resuling byte buffer is malloc'd (so free() it!!!) + */ +uint8_t *xor_with_byte_key(uint8_t *msg, uint8_t key, size_t len); + +/* + * returns bitwise xor of a single byte key and provided byte message + * the resuling byte buffer is malloc'd (so free() it!!!) + */ +uint8_t *xor_with_repeating_key(uint8_t *msg, size_t msg_len, uint8_t *key, size_t key_len); |