#include <gcrypt.h>
#include <stdio.h>
#include <stdlib.h>

static gcry_error_t setiv(gcry_cipher_hd_t cipherCtx, char *passphrase,
			  int algorithm)
{
  size_t ivLength = 0;
  gcry_error_t error = 0;

  if((ivLength = gcry_cipher_get_algo_blklen(algorithm)) != 0)
    {
      char *iv = (char *) gcry_calloc_secure(ivLength, sizeof(char));

      if(iv)
	{
	  memcpy((void *) iv, (const void *) passphrase, ivLength);
	  error = gcry_cipher_setiv(cipherCtx, (const void *) iv, ivLength);
	  gcry_free(iv);
	}
      else
	error = GPG_ERR_ENOMEM;
    }
  else
    error = GPG_ERR_INV_LENGTH;

  return error;
}

int main(void)
{
  int rc = EXIT_SUCCESS;
  int algorithm = 0;
  size_t blockLength = 0;
  gcry_error_t error = 0;
  gcry_cipher_hd_t cipherCtx;

  if(!gcry_check_version(GCRYPT_VERSION))
    {
      rc = EXIT_FAILURE;
      printf("gcry_check_version() failure.\n");
    }

  gcry_control(GCRYCTL_ENABLE_M_GUARD);
  gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN);

  if((error = gcry_control(GCRYCTL_INIT_SECMEM, 16384, 0)) != 0)
    {
      rc = EXIT_FAILURE;
      printf("gcry_control() failure (%s).\n", gcry_strerror(error));
    }

  gcry_control(GCRYCTL_RESUME_SECMEM_WARN);
  gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
  algorithm = gcry_cipher_map_name("aes256");

  if((error = gcry_cipher_open(&cipherCtx, algorithm,
			       GCRY_CIPHER_MODE_CBC,
			       GCRY_CIPHER_SECURE |
			       GCRY_CIPHER_CBC_CTS)) != 0)
    {
      rc = EXIT_FAILURE;
      printf("gcry_cipher_open() failure (%s).\n", gcry_strerror(error));
    }

  char *buffer = 0;
  char *passphrase = (char *) gcry_calloc_secure(256, sizeof(char));

  if(passphrase)
    {
      blockLength = gcry_cipher_get_algo_blklen(algorithm);
      buffer = (char *) gcry_calloc_secure(blockLength, sizeof(char));

      if(buffer)
	{
	  memset(buffer, 0, blockLength);
	  memcpy(buffer, "Hello!", 6);
	  gcry_cipher_reset(cipherCtx);
	  error = setiv(cipherCtx, passphrase, algorithm);

	  if(error)
	    {
	      rc = EXIT_FAILURE;
	      printf("setiv() failure (%s).\n", gcry_strerror(error));
	    }

	  if((error = gcry_cipher_encrypt(cipherCtx,
					  (void *) buffer,
					  blockLength,
					  (const void *) 0,
					  (size_t) 0)) != 0)
	    {
	      rc = EXIT_FAILURE;
	      printf("gcry_cipher_encrypt() failure (%s).\n",
		     gcry_strerror(error));
	    }

	  gcry_cipher_reset(cipherCtx);
	  error = setiv(cipherCtx, passphrase, algorithm);

	  if(error)
	    {
	      rc = EXIT_FAILURE;
	      printf("setiv() failure (%s).\n", gcry_strerror(error));
	    }

	  if((error = gcry_cipher_decrypt(cipherCtx,
					  (void *) buffer,
					  blockLength,
					  (const void *) 0,
					  (size_t) 0)) != 0)
	    {
	      rc = EXIT_FAILURE;
	      printf("gcry_cipher_decrypt() failure (%s).\n",
		     gcry_strerror(error));
	    }
	}

      gcry_free(buffer);
    }

  gcry_free(passphrase);
  gcry_cipher_close(cipherCtx);
  return rc;
}
