#include #include #include #include #include #include #include #include #include #include #include // Adjusting this will render the file format incompatible // KDF_WORKFACTOR must be a power of two between 1 and 2^32 #define KDF_BLOCKSIZE 1024 #define KDF_WORKFACTOR (16 * 1024) unsigned char workbuf[KDF_WORKFACTOR * KDF_BLOCKSIZE]; void xxtea128(uint32_t const key[4], uint32_t block[4]) { // Encryption half of the XXTEA algorithm, with block size limited // to 128 bits or 4 words. This avoids all the weaknesses that // Wikipedia knows of, since both depend on only running 6 rounds // per block, and we will run 6 + 52//4 = 6 + 13 = 19 uint32_t roundconstant = 0; for (unsigned round = 0; round < 19; round++) { // This took a while to puzzle out since the original // specification is a mess, and the mess is only added to // by needing to support custom blockwidths. // // The algorithm is as follows: // // 1. Set the round constant (sum) to round * 0x9e3779b9 // (implemented by addition) // // 2. Create a reduced version of the round constant (e) // which is the bits 3…2 of the round constant. The // reduced version is needed for changing the pattern of // key accesses, since key is only 4 words long // // 3. Go through each word in the block and derive its new // value based on its current value (v[p]), the next (y) // and the previous word (z), wrapping around the ends // of the block as needed. // // The function for deriving the new value of a word is a // xor of sums of xors, followed by an in-place addition. // The first sum adds together combinations of the next and // previous word, and the second sum adds together // previous/next combined with a value dependant on the // round constant. The key is also mixed into the word in // the first xor of second sum. After this the result is // added back into the original word. // // I have changed the operand order in the second xor of // first add and in the second add. This is to keep the // part dependant of previous word on the left and the part // dependant on the next word on the right. roundconstant += 0x9e3779b9; uint32_t reduced = (roundconstant >> 2) & 3; block[0] += ((block[3]>>5 ^ block[1]<<2) + (block[3]<<4 ^ block[1]>>3)) ^ ((key[reduced ^ 0] ^ block[3]) + (roundconstant ^ block[1])); block[1] += ((block[0]>>5 ^ block[2]<<2) + (block[0]<<4 ^ block[2]>>3)) ^ ((key[reduced ^ 1] ^ block[0]) + (roundconstant ^ block[2])); block[2] += ((block[1]>>5 ^ block[3]<<2) + (block[1]<<4 ^ block[3]>>3)) ^ ((key[reduced ^ 2] ^ block[1]) + (roundconstant ^ block[3])); block[3] += ((block[2]>>5 ^ block[0]<<2) + (block[2]<<4 ^ block[0]>>3)) ^ ((key[reduced ^ 3] ^ block[2]) + (roundconstant ^ block[0])); } } uint32_t bytes2word(unsigned char const bytes[4]) { return (uint32_t)bytes[0] | (uint32_t)bytes[1]<<8 | (uint32_t)bytes[2]<<16 | (uint32_t)bytes[3]<<24; } void word2bytes(unsigned char *bytes, uint32_t word) { bytes[0] = word; bytes[1] = word>>8; bytes[2] = word>>16; bytes[3] = word>>24; } void block2words(uint32_t words[4], unsigned char const bytes[16]) { words[0] = bytes2word(&bytes[0]); words[1] = bytes2word(&bytes[4]); words[2] = bytes2word(&bytes[8]); words[3] = bytes2word(&bytes[12]); } void words2block(unsigned char bytes[16], uint32_t const words[4]) { word2bytes(&bytes[0], words[0]); word2bytes(&bytes[4], words[1]); word2bytes(&bytes[8], words[2]); word2bytes(&bytes[12], words[3]); } struct hashstate { // A_n and B_n of the MDC-2 algorithm uint32_t a[4]; uint32_t b[4]; // Buffer to hold data until next full block unsigned char buffer[16]; size_t length; // Counter that keeps tracks of how much data we've hashed uint64_t totalbits; }; void initialize_hash(struct hashstate *state) { // Hash function is MDC-2 with xxtea128, which is nice since it // gives us a 256 bit hash. The constants are based on binary // expansion of the square root of two, A1 being the first 128 bits // and B1 the next 128. // // If we treat A1 and B1 as 128bit little endian integers, they // have the values: // // A1 = 6a09e667 f3bcc908 b2fb1366 ea957d3e // A2 = 3adec175 12775099 da2f590b 0667322a state->a[0] = 0xea957d3eUL; state->a[1] = 0xb2fb1366UL; state->a[2] = 0xf3bcc908UL; state->a[3] = 0x6a09e667UL; state->b[0] = 0x0667322aUL; state->b[1] = 0xda2f590bUL; state->b[2] = 0x12775099UL; state->b[3] = 0x3adec175UL; memset(state->buffer, 0, sizeof(state->buffer)); state->length = 0; state->totalbits = 0; } void compress_hash(struct hashstate *state) { assert(state->length == 16); // M_i uint32_t message[4]; block2words(message, state->buffer); // V_i = M_i ^ E(M_i, A_i) // Note: In this description A_i is the *key*, not the plaintext uint32_t v[4]; memcpy(v, message, sizeof(v)); xxtea128(state->a, v); v[0] ^= message[0]; v[1] ^= message[1]; v[2] ^= message[2]; v[3] ^= message[3]; // W_i = M_i ^ E(M_i, B_i); uint32_t w[4]; memcpy(w, message, sizeof(w)); xxtea128(state->b, w); w[0] ^= message[0]; w[1] ^= message[1]; w[2] ^= message[2]; w[3] ^= message[3]; // A_{i+1} = Vwi^L || W_i^R state->a[0] = v[0]; state->a[1] = v[1]; state->a[2] = w[2]; state->a[3] = w[3]; // B_{i+1} = W_i^L || V_i^R state->b[0] = w[0]; state->b[1] = w[1]; state->b[2] = v[2]; state->b[3] = v[3]; // Mark that we have consumed the buffer state->length = 0; } void feed_hash(struct hashstate *state, unsigned char input[], size_t length) { // Invariant: The buffer will be filled somewhere between 0 and 15 // when we enter this loop. This is because once it reaches 16, the // hash compression function is executed. for (size_t i = 0; i < length; i++) { // Must not overflow the internat counter. In practice we will not // hit this. assert(state->totalbits <= UINT64_MAX - 8); state->buffer[state->length++] = input[i]; state->totalbits += 8; if (state->length == 16) { compress_hash(state); } } } void finalize_hash(struct hashstate *state, unsigned char hash[32]) { // Feed the padding. It consists of one-bit, followed by zero-bits, // followed by the number of bits in the message as big-endian // uint64. This is the same padding as in SHA-2. // We can assume that this works due to the invariant that buffer // fill when entering this function is between 0 and 15 state->buffer[state->length++] = 0x80; while(state->length != 8) { if (state->length == 16) { compress_hash(state); } state->buffer[state->length++] = 0; } // Add the number of bits, and do one last compression state->buffer[8] = state->totalbits >> 56; state->buffer[9] = state->totalbits >> 48; state->buffer[10] = state->totalbits >> 40; state->buffer[11] = state->totalbits >> 32; state->buffer[12] = state->totalbits >> 24; state->buffer[13] = state->totalbits >> 16; state->buffer[14] = state->totalbits >> 8; state->buffer[15] = state->totalbits; state->length += 8; compress_hash(state); // Extract the hash state for (size_t i = 0; i < 4; i++) { word2bytes(&hash[i*4], state->a[i]); } for (size_t i = 0; i < 4; i++) { word2bytes(&hash[i*4 + 16], state->b[i]); } // Clear all of the hash state, in case there was sth important // there explicit_bzero(state, sizeof(struct hashstate)); } void pbkdf2_1_block(unsigned char output[32], unsigned char passphrase[], size_t passphraselen, unsigned char salt[], size_t saltlen, uint32_t blockindex) { // NOTE: This implementation is hardcoded to one round, as required // by the MFcrypt (see Stronger Key Derivation Via Sequential // Memory-hard Functions by Colin Percival) algorithm. This is not // suitable as a general purpose password-based KDF. // This is equivalent to // F(Password, Salt, 1, i) // = U_1 // = PRF(Password, Salt + INT_32_BE(i)) // We use HMAC-MDC2-XXTEA128 as our PRF // The blocksize of the underlying hash function is 128 bits (16B) // but HMAC is specified assuming that the hash function output (in // our case 256 bits or 32B) fits in one block. As far as I can // tell extending the key to be two blocks long is not a problem. unsigned char padded_key[32]; if (passphraselen > 16) { // We hash it even if it is shorter than our extended key // length to avoid giving attacker any funny surfaces to // play with at the interface of two blocks struct hashstate state; initialize_hash(&state); feed_hash(&state, passphrase, passphraselen); finalize_hash(&state, padded_key); } else { // Copy the key and zero-pad if necessary memset(padded_key, 0, 32); memcpy(padded_key, passphrase, passphraselen); } // Outer and inner key derivation unsigned char outer_key[32], inner_key[32]; for (size_t i = 0; i < 32; i++) { outer_key[i] = padded_key[i] ^ 0x5c; inner_key[i] = padded_key[i] ^ 0x36; } // Inner hash unsigned char inner_hash[32]; struct hashstate state; initialize_hash(&state); feed_hash(&state, inner_key, 32); // Our message is salt plus big endian encoding of blockindex feed_hash(&state, salt, saltlen); unsigned char be_blockindex[4]; be_blockindex[0] = blockindex >> 24; be_blockindex[1] = blockindex >> 16; be_blockindex[2] = blockindex >> 8; be_blockindex[3] = blockindex; feed_hash(&state, be_blockindex, 4); finalize_hash(&state, inner_hash); // Outer hash initialize_hash(&state); feed_hash(&state, outer_key, 32); feed_hash(&state, inner_hash, 32); finalize_hash(&state, output); } void mfcrypt_hash(unsigned char chunk[16]) { uint32_t key[4], words[4]; block2words(key, chunk); block2words(words, chunk); xxtea128(key, words); words2block(chunk, words); } void blockmix(unsigned char block[KDF_BLOCKSIZE]) { // r = KDF_BLOCKSIZE / 32, since block is 2r times the width of our // hash function (xxtea128) const size_t r = KDF_BLOCKSIZE / 32; // accumulator (X) starts off as chunk 2r-1. Chunk k is at memory // location 16*k and is 16 bytes long. Substituting we get: // start = 16*(2*(KDF_BLOCKSIZE / 32) - 1) // start = 16*(KDF_BLOCKSIZE / 16 - 1) // start = KDF_BLOCKSIZE - 16 unsigned char accumulator[16]; memcpy(accumulator, &block[16 * (2*r - 1)], 16); // Chunk i is at memory location 16*i. We go through chunks < 2r unsigned char hashedchunks[KDF_BLOCKSIZE]; for (size_t i = 0; i < 2*r; i++) { // X = H(X xor B_i) for (size_t index = 0; index < 16; index++) { accumulator[index] ^= block[16 * i + index]; } mfcrypt_hash(accumulator); // Y_i = X memcpy(&hashedchunks[16 * i], accumulator, 16); } // Interleave the blocks back into the buffer. We go through B's // chunks < r which corresponds to indices every 16 bytes smaller // than 16*(KDF_BLOCKSIZE / 32) = KDF_BLOCKSIZE / 2 size_t i = 0; for (; i < r; i++) { // B_i = Y_{2*i} memcpy(&block[16*i], &hashedchunks[16*2*i], 16); } // Now we go through B's chunks < 2r but >= r for (; i < 2*r; i++) { // B_i = Y_{2*(i - r) + 1} memcpy(&block[16*i], &hashedchunks[16*(2*(i - r) + 1)], 16); } } void romix(unsigned char block[KDF_BLOCKSIZE]) { // Block i starts at location KDF_BLOCKSIZE * i for (size_t i = 0; i < KDF_WORKFACTOR; i++) { // V_i = X memcpy(&workbuf[KDF_BLOCKSIZE * i], block, KDF_BLOCKSIZE); // X = H(X) blockmix(block); } for (size_t i = 0; i < sizeof(workbuf) / KDF_BLOCKSIZE; i++) { // j = Integrify(X) mod N // N is a power of two uint32_t j = bytes2word(&block[KDF_BLOCKSIZE - 4]) & (KDF_WORKFACTOR - 1); // X = H(X xor V_j) for (size_t index = 0; index < KDF_BLOCKSIZE; index++) { block[index] ^= workbuf[KDF_BLOCKSIZE * j + index]; } blockmix(block); } } void kdf(unsigned char key[16], unsigned char passphrase[], size_t passphraselen, unsigned char salt[32]) { unsigned char block[KDF_BLOCKSIZE]; for (size_t i = 0; i < KDF_BLOCKSIZE / 32; i++) { pbkdf2_1_block(&block[i * 32], passphrase, passphraselen, salt, 32, i); } romix(block); unsigned char result[32]; pbkdf2_1_block(result, passphrase, passphraselen, block, KDF_BLOCKSIZE, 0); memcpy(key, result, 16); } // 16 bit authentication tag const int mprime = (16-2)/2; // 32 bit = 4 byte length field const int lprime = 4-1; void ccm_mac(unsigned char mac[16], uint32_t key[4], uint64_t messageindex, unsigned char message[], uint32_t length) { // CCM specifies that the length field is big endian while we are // natively little endian. Flip it. unsigned char length_bytes[4]; length_bytes[0] = length >> 24; length_bytes[1] = length >> 16; length_bytes[2] = length >> 8; length_bytes[3] = length; uint32_t be_length = bytes2word(length_bytes); // First block is special uint32_t mac_words[4] = {mprime<<3 | lprime, messageindex, messageindex >> 32, be_length}; xxtea128(key, mac_words); // Process all full blocks size_t index = 0; for (; index + 16 <= length; index += 16) { // Xor the plaintext block and previous encrypted block uint32_t block[4]; block2words(block, &message[index]); mac_words[0] ^= block[0]; mac_words[1] ^= block[1]; mac_words[2] ^= block[2]; mac_words[3] ^= block[3]; // Encrypt xxtea128(key, mac_words); } if (index < length) { // Pad with zeros to block width unsigned char fullblock[16]; memset(fullblock, 0, 16); memcpy(fullblock, &message[index], length - index); // Xor the plaintext block and previous encrypted block uint32_t block[4]; block2words(block, fullblock); mac_words[0] ^= block[0]; mac_words[1] ^= block[1]; mac_words[2] ^= block[2]; mac_words[3] ^= block[3]; // Encrypt xxtea128(key, mac_words); } words2block(mac, mac_words); } void ccm_xor_block(unsigned char block[16], uint32_t key[4], uint64_t messageindex, uint32_t counter) { // CCM specifies that the counter field is big endian while we are // natively little endian. Flip it. unsigned char counter_bytes[4]; counter_bytes[0] = counter >> 24; counter_bytes[1] = counter >> 16; counter_bytes[2] = counter >> 8; counter_bytes[3] = counter; uint32_t be_counter = bytes2word(counter_bytes); uint32_t words[4] = {lprime, messageindex, messageindex >> 32, be_counter}; xxtea128(key, words); unsigned char keystream[16]; words2block(keystream, words); for (size_t i = 0; i < 16; i++) { block[i] ^= keystream[i]; } } void ccm_encrypt(unsigned char key[16], uint64_t messageindex, unsigned char message[], uint32_t length, unsigned char mac[16]) { uint32_t key_words[4]; block2words(key_words, key); // Authenticate ccm_mac(mac, key_words, messageindex, message, length); // Encrypt // MAC is xored with first block of keystream ccm_xor_block(mac, key_words, messageindex, 0); // Xor full blocks size_t index = 0; uint32_t counter = 1; for (; index + 16 <= length; index += 16) { ccm_xor_block(&message[index], key_words, messageindex, counter++); } // Xor partial block, if any if (index < length) { unsigned char fullblock[16]; memcpy(fullblock, &message[index], length - index); ccm_xor_block(fullblock, key_words, messageindex, counter++); memcpy(&message[index], fullblock, length - index); } } bool ccm_decrypt(unsigned char key[16], uint64_t messageindex, unsigned char message[], uint32_t length, unsigned char mac[16]) { uint32_t key_words[4]; block2words(key_words, key); // Decrypt // MAC is xored with first block of keystream ccm_xor_block(mac, key_words, messageindex, 0); // Xor full blocks size_t index = 0; uint32_t counter = 1; for (; index + 16 <= length; index += 16) { // Message blocks are numbered from index 1 onwards ccm_xor_block(&message[index], key_words, messageindex, counter++); } // Xor partial block, if any if (index < length) { unsigned char fullblock[16]; memset(fullblock, 0, 16); memcpy(fullblock, &message[index], length - index); ccm_xor_block(fullblock, key_words, messageindex, counter++); memcpy(&message[index], fullblock, length - index); } // Compute the expected authentication tag unsigned char computed_mac[16]; ccm_mac(computed_mac, key_words, messageindex, message, length); // Compare the expected and actual tag in constant time unsigned char different = 0; for (size_t i = 0; i < 16; i++) { different |= computed_mac[i] ^ mac[i]; } // Do the tags match? if (different) { // Nope. Wipe what we decrypted and return false explicit_bzero(message, length); return false; } // They do, return true return true; } ssize_t passphrase_prompt(unsigned char *passphrase, size_t size, const char *prompt) { // Read from controlling TTY, even if stdio has been redirected int tty = open("/dev/tty", O_RDWR); if (tty == -1) { perror("Failed to open controlling tty"); return -1; } if (write(tty, prompt, strlen(prompt)) == -1) { perror("Failed to write to terminal"); close(tty); return -1; } // Turn off echo struct termios saved; if (tcgetattr(tty, &saved) != 0) { perror("Failed to get terminal attributes"); close(tty); return -1; } struct termios altered; altered = saved; altered.c_lflag &= ~ECHO; if (tcsetattr(tty, TCSANOW, &altered) != 0) { perror("Failed to turn echoing off"); close(tty); return -1; } // Read until newline size_t index = 0; for (;;) { if (index >= size) { fprintf(stderr, "Passphrase too long, maximum size is %zu bytes\n", size - 1); // Clean any line buffer char tmp; for (;;) { if (read(tty, &tmp, 1) <= 0) { break; } if (tmp == '\n') { break; } } tcsetattr(tty, TCSANOW, &saved); close(tty); return -1; } ssize_t bytes_read = read(tty, &passphrase[index], size - index); if (bytes_read == -1) { perror("Failed to read passphrase"); tcsetattr(tty, TCSANOW, &saved); close(tty); return -1; } else if (bytes_read == 0) { fprintf(stderr, "Unexpected EOF\n"); tcsetattr(tty, TCSANOW, &saved); close(tty); return -1; } index += bytes_read; if (passphrase[index-1] == '\n') { // Got end of line break; } } // Write a newline (since the user's is not visible) and restore // terminal settings if (write(tty, "\n", 1) == -1) { perror("Failed to write to terminal"); tcsetattr(tty, TCSANOW, &saved); close(tty); return -1; } if (tcsetattr(tty, TCSANOW, &saved) != 0) { perror("Failed to restore terminal state"); close(tty); return -1; } close(tty); return index - 1; } ssize_t passphrase_file(char *passfilepath, unsigned char passphrase[], size_t size) { int file = open(passfilepath, O_RDONLY); // Check permissions struct stat statbuf; if (fstat(file, &statbuf) != 0) { perror("Could not stat passphrase file"); close(file); return -1; } if (statbuf.st_mode & S_IROTH) { fprintf(stderr, "Warning: Passphrase file is world-readable\n"); } // Read until newline size_t index = 0; for (;;) { if (index >= size) { fprintf(stderr, "Passphrase too long, maximum size is %zu bytes\n", size - 1); close(file); return -1; } ssize_t bytes_read = read(file, &passphrase[index], size - index); if (bytes_read == -1) { perror("Failed to read passphrase"); close(file); return -1; } else if (bytes_read == 0) { fprintf(stderr, "Unexpected EOF\n"); close(file); return -1; } index += bytes_read; if (passphrase[index-1] == '\n') { // Got end of line break; } } close(file); return index - 1; } void usage(char *name) { fprintf(stderr, "Usage: %s -d | -e [-f] [-p passfile] [-i infile] [-o outfile]\n\n", name); fprintf(stderr, "-d Decrypt\n"); fprintf(stderr, "-e Encrypt\n"); fprintf(stderr, "-f Force output to terminal\n"); fprintf(stderr, "-p passfile Read passphrase from a file instead of the terminal.\n"); fprintf(stderr, "-i infile Read from a file instead of the terminal.\n"); fprintf(stderr, "-o outfile Write to a file instead of the terminal.\n"); } int main(int argc, char *argv[]) { bool encrypting = false; bool decrypting = false; bool force = false; char *passfilepath = NULL; char *infilepath = NULL; char *outfilepath = NULL; int opt; while ((opt = getopt(argc, argv, "defp:i:o:")) != -1) { switch (opt) { case 'd': decrypting = true; break; case 'e': encrypting = true; break; case 'f': force = true; break; case 'p': passfilepath = optarg; break; case 'i': infilepath = optarg; break; case 'o': outfilepath = optarg; break; default: usage(argv[0]); exit(1); } } if (optind != argc) { usage(argv[0]); exit(1); } if ((!encrypting && !decrypting) || (encrypting && decrypting)) { usage(argv[0]); exit(1); } FILE *infile = stdin; FILE *outfile = stdout; if (infilepath != NULL) { infile = fopen(infilepath, "r"); if (infile == NULL) { perror("Failed to open input file"); exit(1); } } if (outfilepath != NULL) { outfile = fopen(outfilepath, "w"); if (outfile == NULL) { perror("Failed to open output file"); exit(1); } } if (encrypting && !force && isatty(fileno(outfile))) { fprintf(stderr, "Refusing to print encrypted (binary) data to terminal. Use -f to force output.\n"); exit(1); } // Get the salt for key derivation unsigned char salt[32]; if (encrypting) { // Generate salt randomly if (getentropy(salt, 32) != 0) { perror("Could not generate salt (getentropy)"); exit(1); } // Write salt to the beginning of the file if (fwrite(&salt, 32, 1, outfile) != 1) { fprintf(stderr, "Could not write salt\n"); exit(1); } } else { // Read salt from the beginning of the file if (fread(&salt, 32, 1, infile) != 1) { fprintf(stderr, "Could not read salt\n"); exit(1); } } // Read passphrase unsigned char passphrase[128]; ssize_t passphrase_len; if (passfilepath == NULL) { // Read from terminal if no passfile specified passphrase_len = passphrase_prompt(passphrase, sizeof(passphrase), "passphrase: "); if (passphrase_len == -1) { explicit_bzero(passphrase, sizeof(passphrase)); exit(1); } if (encrypting) { // Have the user confirm the passphrase if encrypting, to avoid losing data unsigned char confirm[sizeof(passphrase)]; ssize_t confirm_len = passphrase_prompt(confirm, sizeof(confirm), "confirm passphrase: "); if (confirm_len == -1) { explicit_bzero(passphrase, sizeof(passphrase)); explicit_bzero(confirm, sizeof(confirm)); exit(1); } if (confirm_len != passphrase_len || memcmp(passphrase, confirm, passphrase_len) != 0) { fprintf(stderr, "Passphrases do not match\n"); explicit_bzero(passphrase, sizeof(passphrase)); explicit_bzero(confirm, sizeof(confirm)); exit(1); } explicit_bzero(confirm, sizeof(confirm)); } } else { passphrase_len = passphrase_file(passfilepath, passphrase, sizeof(passphrase)); if (passphrase_len == -1) { explicit_bzero(passphrase, sizeof(passphrase)); exit(1); } } // Derive key unsigned char key[16]; kdf(key, passphrase, passphrase_len, salt); explicit_bzero(passphrase, sizeof(passphrase)); uint64_t messageindex = 0; if (encrypting) { for (;;) { // Leave space for the MAC in the work buffer size_t bytes = fread(workbuf, 1, sizeof(workbuf) - 16, infile); if (bytes == 0 && ferror(infile)) { perror("Failure reading"); explicit_bzero(key, sizeof(key)); exit(1); } // MAC is after the message unsigned char *mac = &workbuf[bytes]; ccm_encrypt(key, messageindex++, workbuf, bytes, mac); size_t written = fwrite(workbuf, 1, bytes + 16, outfile); if (written != bytes + 16) { perror("Failure writing"); explicit_bzero(key, sizeof(key)); exit(1); } // If this chunk was short, that means we're done if (bytes < sizeof(workbuf) - 16) { break; } if (messageindex == 0) { // We will not run into this, but I feel like it's cleaner to check fprintf(stderr, "Chunk counter overflow\n"); explicit_bzero(key, sizeof(key)); exit(1); } } } else { for (;;) { size_t bytes = fread(workbuf, 1, sizeof(workbuf), infile); if (bytes == 0 && ferror(infile)) { perror("Failure reading"); explicit_bzero(key, sizeof(key)); exit(1); } else if (bytes < 16) { fprintf(stderr, "Chunk too short. File likely corrupt.\n"); explicit_bzero(key, sizeof(key)); exit(1); } // MAC is after the message unsigned char *mac = &workbuf[bytes - 16]; bool auth = ccm_decrypt(key, messageindex++, workbuf, bytes - 16, mac); if (!auth) { if (messageindex == 1) { // First chunk fprintf(stderr, "Authentication failed. Either the passphrase is wrong or the file is corrupt.\n"); } else { fprintf(stderr, "Authentication failed. The file is likely corrupt.\n"); } explicit_bzero(key, sizeof(key)); exit(1); } size_t written = fwrite(workbuf, 1, bytes - 16, outfile); if (written != bytes - 16) { perror("Failure writing"); explicit_bzero(key, sizeof(key)); exit(1); } // If this chunk was short, that means we're done if (bytes < sizeof(workbuf)) { break; } if (messageindex == 0) { // We will not run into this, but I feel like it's cleaner to check fprintf(stderr, "Chunk counter overflow\n"); explicit_bzero(key, sizeof(key)); exit(1); } } } // Remove the key from memory explicit_bzero(key, sizeof(key)); if (fflush(stdout) != 0) { perror("Failure writing"); exit(1); } return 0; }