encrypt_decrypt.c 12.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
#include <stdio.h>
#include <stdlib.h>   // size_t
#include <gcrypt.h>

#define AES256_KEY_SIZE     32
#define AES256_BLOCK_SIZE   16
#define HMAC_KEY_SIZE       64
#define KDF_ITERATIONS      50000
#define KDF_SALT_SIZE       128
#define KDF_KEY_SIZE        AES256_KEY_SIZE + HMAC_KEY_SIZE
//#define MAX_PASSWORD_LENGTH 512

void cleanup (gcry_cipher_hd_t cipher, gcry_mac_hd_t mac, char *str, char *str2, char *str3) {
  if (cipher != NULL) gcry_cipher_close(cipher);
  if (mac != NULL)    gcry_mac_close(mac);

  if (str != NULL)  free(str);
  if (str2 != NULL) free(str2);
  if (str3 != NULL) free(str3);
}

// Returns 0 on error and returns the number of bytes read on success
//   WARNING: data must be free()'d manually
size_t read_file_into_buf (char *filepath, unsigned char **data) {
  long file_size;
  size_t bytes_read;
  FILE *f = fopen(filepath, "rb");

  if (f == NULL) {
    fprintf(stderr, "Error: unable to open file %s\n", filepath);
    return 0;
  }

  // Get file size
  fseek(f, 0, SEEK_END);
  file_size = ftell(f);
  fseek(f, 0, SEEK_SET);
  
  *data = malloc(file_size + 1);
Tyler Nichols's avatar
Tyler Nichols committed
40
  if (*data == NULL) {
41 42 43 44 45 46
    fprintf(stderr, "Error: file is too large to fit in memory\n");
    fclose(f);
    return 0;
  }

  bytes_read = fread(*data, 1, file_size, f);
Tyler Nichols's avatar
Tyler Nichols committed
47 48 49
  if (bytes_read == 0) {
    free(*data);
  }
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184

  fclose(f);

  return bytes_read;
}

// Returns 0 on error and returns the number of bytes written on success
size_t write_buf_to_file (char *filepath, unsigned char *data, unsigned int data_length) {
  size_t bytes_written;
  FILE *f = fopen(filepath, "wb");

  if (f == NULL) {
    fprintf(stderr, "Error: unable to open file %s\n", filepath);
    return 0;
  }

  bytes_written = fwrite(data, 1, data_length, f);

  fclose(f);

  return bytes_written;
}

int init_cipher (gcry_cipher_hd_t *handle, unsigned char *key, unsigned char *init_vector) {
  gcry_error_t err;

  // 256-bit AES using cipher-block chaining; with ciphertext stealing, no manual padding is required
  err = gcry_cipher_open(handle,
                         GCRY_CIPHER_AES256,
                         GCRY_CIPHER_MODE_CBC,
                         GCRY_CIPHER_CBC_CTS);
  if (err) {
    fprintf(stderr, "cipher_open: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    return 1;
  }

  err = gcry_cipher_setkey(*handle, key, AES256_KEY_SIZE);
  if (err) {
    fprintf(stderr, "cipher_setkey: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    gcry_cipher_close(*handle);
    return 1;
  }

  err = gcry_cipher_setiv(*handle, init_vector, AES256_BLOCK_SIZE);
  if (err) {
    fprintf(stderr, "cipher_setiv: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    gcry_cipher_close(*handle);
    return 1;
  }

  return 0;
}

int encrypt_file (char *infile, char *outfile, char *password) {
  unsigned char init_vector[AES256_BLOCK_SIZE],
                kdf_salt[KDF_SALT_SIZE],
                kdf_key[KDF_KEY_SIZE],
                aes_key[AES256_KEY_SIZE],
                hmac_key[HMAC_KEY_SIZE],
                *hmac,
                *packed_data,
                *ciphertext,
                *text;
  size_t blocks_required, text_len, packed_data_len, hmac_len;
  gcry_cipher_hd_t handle;
  gcry_mac_hd_t mac;
  gcry_error_t err;

  // Fetch text to be encrypted
  if (!(text_len = read_file_into_buf(infile, &text))) {
    return 1;
  }

  // Find number of blocks required for data
  blocks_required = text_len / AES256_BLOCK_SIZE;
  if (text_len % AES256_BLOCK_SIZE != 0) {
    blocks_required++;
  }

  // Generate 128 byte salt in preparation for key derivation
  gcry_create_nonce(kdf_salt, KDF_SALT_SIZE);

  // Key derivation: PBKDF2 using SHA512 w/ 128 byte salt over 10 iterations into a 64 byte key
  err = gcry_kdf_derive(password,
                        strlen(password),
                        GCRY_KDF_PBKDF2,
                        GCRY_MD_SHA512,
                        kdf_salt,
                        KDF_SALT_SIZE,
                        KDF_ITERATIONS,
                        KDF_KEY_SIZE,
                        kdf_key);
  if (err) {
    fprintf(stderr, "kdf_derive: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    free(text);
    return 1;
  }

  // Copy the first 32 bytes of kdf_key into aes_key
  memcpy(aes_key, kdf_key, AES256_KEY_SIZE);

  // Copy the last 32 bytes of kdf_key into hmac_key
  memcpy(hmac_key, &(kdf_key[AES256_KEY_SIZE]), HMAC_KEY_SIZE);

  // Generate the initialization vector
  gcry_create_nonce(init_vector, AES256_BLOCK_SIZE);

  // Begin encryption
  if (init_cipher(&handle, aes_key, init_vector)) {
    free(text);
    return 1;
  }

  // Make new buffer of size blocks_required * AES256_BLOCK_SIZE for in-place encryption
  ciphertext = malloc(blocks_required * AES256_BLOCK_SIZE);
  if (ciphertext == NULL) {
    fprintf(stderr, "Error: unable to allocate memory for the ciphertext\n");
    cleanup(handle, NULL, text, NULL, NULL);
    return 1;
  }
  memcpy(ciphertext, text, blocks_required * AES256_BLOCK_SIZE);
  free(text);

  // Encryption is performed in-place
  err = gcry_cipher_encrypt(handle, ciphertext, AES256_BLOCK_SIZE * blocks_required, NULL, 0);
  if (err) {
    fprintf(stderr, "cipher_encrypt: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, NULL, ciphertext, NULL, NULL);
    return 1;
  }

  // Compute and allocate space required for packed data
  hmac_len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA512);
  packed_data_len = KDF_SALT_SIZE + AES256_BLOCK_SIZE + (AES256_BLOCK_SIZE * blocks_required) + hmac_len;
  packed_data = malloc(packed_data_len);
Tyler Nichols's avatar
Tyler Nichols committed
185 186 187 188 189
  if (packed_data == NULL) {
    fprintf(stderr, "Unable to allocate memory for packed data\n");
    cleanup(handle, NULL, ciphertext, NULL, NULL);
    return 1;
  }
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390

  // Pack data before writing: salt::IV::ciphertext::HMAC where "::" denotes concatenation
  memcpy(packed_data, kdf_salt, KDF_SALT_SIZE);
  memcpy(&(packed_data[KDF_SALT_SIZE]), init_vector, AES256_BLOCK_SIZE);
  memcpy(&(packed_data[KDF_SALT_SIZE + AES256_BLOCK_SIZE]), ciphertext, AES256_BLOCK_SIZE * blocks_required);

  // Begin HMAC computation on encrypted/packed data
  hmac = malloc(hmac_len);
  if (hmac == NULL) {
    fprintf(stderr, "Error: unable to allocate enough memory for the HMAC\n");
    cleanup(handle, NULL, ciphertext, packed_data, NULL);
    return 1;
  }

  err = gcry_mac_open(&mac, GCRY_MAC_HMAC_SHA512, 0, NULL);
  if (err) {
    fprintf(stderr, "mac_open during encryption: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, NULL, ciphertext, packed_data, hmac);
    return 1;
  }

  err = gcry_mac_setkey(mac, hmac_key, HMAC_KEY_SIZE);
  if (err) {
    fprintf(stderr, "mac_setkey during encryption: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  // Add packed_data to the MAC computation
  err = gcry_mac_write(mac, packed_data, packed_data_len - hmac_len);
  if (err) {
    fprintf(stderr, "mac_write during encryption: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  // Finalize MAC and save it in the hmac buffer
  err = gcry_mac_read(mac, hmac, &hmac_len);
  if (err) {
    fprintf(stderr, "mac_read during encryption: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  // Append the computed HMAC to packed_data
  memcpy(&(packed_data[KDF_SALT_SIZE + AES256_BLOCK_SIZE + (AES256_BLOCK_SIZE * blocks_required)]), hmac, hmac_len);

  // Write packed data to file
  if (!write_buf_to_file(outfile, packed_data, packed_data_len)) {
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  cleanup(handle, mac, ciphertext, packed_data, hmac);

  return 0;
}

int decrypt_file (char *infile, char *outfile, char *password) {
  unsigned char init_vector[AES256_BLOCK_SIZE],
                kdf_salt[KDF_SALT_SIZE],
                kdf_key[KDF_KEY_SIZE],
                aes_key[AES256_KEY_SIZE],
                hmac_key[HMAC_KEY_SIZE],
                *hmac,
                *packed_data,
                *ciphertext;
  size_t blocks_required, packed_data_len, ciphertext_len, hmac_len;
  gcry_cipher_hd_t handle;
  gcry_mac_hd_t mac;
  gcry_error_t err;

  // Read in file contents
  if (!(packed_data_len = read_file_into_buf(infile, &packed_data))) {
    return 1;
  }

  // Compute necessary lengths
  hmac_len = gcry_mac_get_algo_maclen(GCRY_MAC_HMAC_SHA512);
  ciphertext_len = packed_data_len - KDF_SALT_SIZE - AES256_BLOCK_SIZE - hmac_len;

  ciphertext = malloc(ciphertext_len);
  if (ciphertext == NULL) {
    fprintf(stderr, "Error: ciphertext is too large to fit in memory\n");
    free(packed_data);
    return 1;
  }

  hmac = malloc(hmac_len);
  if (hmac == NULL) {
    fprintf(stderr, "Error: could not allocate memory for HMAC\n");
    cleanup(NULL, NULL, ciphertext, packed_data, NULL);
    return 1;
  }

  // Unpack data
  memcpy(kdf_salt, packed_data, KDF_SALT_SIZE);
  memcpy(init_vector, &(packed_data[KDF_SALT_SIZE]), AES256_BLOCK_SIZE);
  memcpy(ciphertext, &(packed_data[KDF_SALT_SIZE + AES256_BLOCK_SIZE]), ciphertext_len);
  memcpy(hmac, &(packed_data[KDF_SALT_SIZE + AES256_BLOCK_SIZE + ciphertext_len]), hmac_len);

  // Key derivation: PBKDF2 using SHA512 w/ 128 byte salt over 10 iterations into a 64 byte key
  err = gcry_kdf_derive(password,
                        strlen(password),
                        GCRY_KDF_PBKDF2,
                        GCRY_MD_SHA512,
                        kdf_salt,
                        KDF_SALT_SIZE,
                        KDF_ITERATIONS,
                        KDF_KEY_SIZE,
                        kdf_key);
  if (err) {
    fprintf(stderr, "kdf_derive: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(NULL, NULL, ciphertext, packed_data, hmac);
    return 1;
  }

  // Copy the first 32 bytes of kdf_key into aes_key
  memcpy(aes_key, kdf_key, AES256_KEY_SIZE);

  // Copy the last 32 bytes of kdf_key into hmac_key
  memcpy(hmac_key, &(kdf_key[AES256_KEY_SIZE]), HMAC_KEY_SIZE);

  // Begin HMAC verification
  err = gcry_mac_open(&mac, GCRY_MAC_HMAC_SHA512, 0, NULL);
  if (err) {
    fprintf(stderr, "mac_open during decryption: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, NULL, ciphertext, packed_data, hmac);
    return 1;
  }

  err = gcry_mac_setkey(mac, hmac_key, HMAC_KEY_SIZE);
  if (err) {
    fprintf(stderr, "mac_setkey during decryption: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  err = gcry_mac_write(mac, packed_data, KDF_SALT_SIZE + AES256_BLOCK_SIZE + ciphertext_len);
  if (err) {
    fprintf(stderr, "mac_write during decryption: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  // Verify HMAC
  err = gcry_mac_verify(mac, hmac, hmac_len);
  if (err) {
    fprintf(stderr, "HMAC verification failed: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  } else {
    printf("Valid HMAC found\n");
  }

  // Begin decryption
  if (init_cipher(&handle, aes_key, init_vector)) {
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  err = gcry_cipher_decrypt(handle, ciphertext, ciphertext_len, NULL, 0);
  if (err) {
    fprintf(stderr, "cipher_decrypt: %s/%s\n", gcry_strsource(err), gcry_strerror(err));
    cleanup(handle, mac, ciphertext, packed_data, hmac);
    return 1;
  }

  // Write plaintext to the output file
  if (!write_buf_to_file(outfile, ciphertext, ciphertext_len)) {
    fprintf(stderr, "0 bytes written.\n");
  }

  cleanup(handle, mac, ciphertext, packed_data, hmac);

  return 0;
}

void display_usage () {
  puts("Usage: ./encrypt_decrypt [encrypt|decrypt] <input file path> <output file path> <password>");
}

int main (int argc, const char **argv) {
  if (argc < 5) {
    fprintf(stderr, "Error: not enough arguments.\n");
    display_usage();
    return 1;
  }

  if (strncmp(argv[1], "encrypt", 7) == 0) {
    encrypt_file((char *)argv[2], (char *)argv[3], (char *)argv[4]);
  } else if (strncmp(argv[1], "decrypt", 7) == 0) {
    decrypt_file((char *)argv[2], (char *)argv[3], (char *)argv[4]);
  } else {
    fprintf(stderr, "Error: invalid action.\n");
    display_usage();
    return 1;
  }

  return 0;
}