Using SHA-512 for encryption/decryption It occurred to me that if SHA2 can be used to derive keys from passwords, then it might as well be good enough to generate random data that can be xored with a plaintext to encrypt and the other way around.

These are my assumptions:


SHA-512 produces a random-looking output that is impossible to guess
The result of SHA-512 can be fed back into it appended to a 256-bit key and produce an output with the same quality as given for a random input


If those assumptions hold, then it should provide privacy. It's much faster than AES-256-CBC.

At first I thought there must be something I'm missing, but no one has pointed out an attack that can be carried out against this construction. So what I would like to know is how secure this algorithm is and specific ways to break it.

Here's the code:

mad.h

#ifndef _MAD_H_
#define _MAD_H_

typedef struct {
  unsigned char state[64];
  unsigned char key[32];
} MadCtx;

void mad_ctx_init(MadCtx* mad, unsigned char const* key,
                  unsigned char const* iv);

void mad_encrypt(MadCtx* mad, unsigned char const* in, unsigned int in_size,
                 unsigned char* out); 

void mad_decrypt(MadCtx* mad, unsigned char const* in, unsigned int in_size,
                 unsigned char* out);

#endif


mad.c

#include "mad.h"
#include &lt;stdint.h&gt;
#include &lt;assert.h&gt;
#include &lt;string.h&gt;
#include &lt;openssl/sha.h&gt;


// Private
static void _xor64(uint64_t* dest, uint64_t const* a, uint64_t* b)
{
  for(int i = 0; i &lt; 8; ++i)
    *dest++ = *a++ ^ *b++;
}

// Public
void mad_ctx_init(MadCtx* mad, unsigned char const* key,
                  unsigned char const* iv)
{
  memcpy(mad-&gt;state, iv, 64);
  memcpy(mad-&gt;key, key, 32);
}

void mad_encrypt(MadCtx* mad, unsigned char const* in, unsigned int in_size,
                 unsigned char* out)
{
  assert(0 == in_size % 64);

  int n = in_size &gt;&gt; 6; // in_size / 64
  while(n){
    uint64_t x[8];
    SHA512((unsigned char const*)mad, 96, (unsigned char*)x);
    _xor64((uint64_t*)out, (uint64_t const*)in, x);
    memcpy(mad-&gt;state, out, 64);
    in += 64;
    out += 64;
    --n;
  }
}

void mad_decrypt(MadCtx* mad, unsigned char const* in, unsigned int in_size,
                 unsigned char* out)
{
  assert(0 == in_size % 64);

  int n = in_size &gt;&gt; 6; // in_size / 64
  while(n){
    uint64_t x[8];
    SHA512((unsigned char const*)mad, 96, (unsigned char*)x);
    memcpy(mad-&gt;state, in, 64);
    _xor64((uint64_t*)out, (uint64_t const*)in, x);
    in += 64;
    out += 64;
    --n;
  }
}


and some test code

#include "mad.h"
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;assert.h&gt;

void phex(void const* data, size_t size)
{
  char const* table = "0123456789abcdef";
  unsigned char const* in = data;
  while(size--){
    int c;
    c = table[*in &gt;&gt; 4];
    putchar(c);

    c = table[*in &amp; 0xf];
    putchar(c);
    ++in;
  }
}

void read_or_die(FILE* file, void* dest, size_t size)
{
  if(size != fread(dest, 1, size, file)){
    perror("fread()");
    exit(EXIT_FAILURE);
  }
}

int main(int argc, char* argv[])
{
  FILE* urandom = fopen("/dev/urandom", "r");
  unsigned char iv[64];
  unsigned char key[32];
  unsigned char plain[128];
  unsigned char cipher[128];
  unsigned char decrypted[128];

  read_or_die(urandom, iv, 64);
  read_or_die(urandom, key, 32);
  memset(plain, 0xdd, 128);

  puts("plain text is:");
  for(int i = 0; i != 128; i += 32){
    phex(plain + i, 32);
    putchar('\n');
  }
  putchar('\n');

  MadCtx ctx;
  mad_ctx_init(&amp;ctx, key, iv);
  mad_encrypt(&amp;ctx, plain, 128, cipher);

  puts("cipher text is:");
  for(int i = 0; i != 128; i += 32){
    phex(cipher + i, 32);
    putchar('\n');
  }
  putchar('\n');

  assert(0 != memcmp(plain, cipher, 128));

  mad_ctx_init(&amp;ctx, key, iv);
  mad_decrypt(&amp;ctx, cipher, 128, decrypted);

  puts("decrypted text is:");
  for(int i = 0; i != 128; i += 32){
    phex(decrypted + i, 32);
    putchar('\n');
  }
  putchar('\n');

  assert(0 == memcmp(plain, decrypted, 128));
}
The above encrypt/decrypt functions can be summarized as follows:

void mad_encrypt(...)
{
  while(n)
  {
    SHA512(mad, 96, x);
    XOR(out, in, x);
    memcpy(mad-&gt;state, out, 64);
    ...
  }
}

void mad_decrypt(...)
{
  while(n)
  {
    SHA512(mad, 96, x);
    XOR(out, in, x);
    memcpy(mad-&gt;state, in, 64);
    ...
  }
}


This is basically the CFB mode. Note that the two functions are almost identical, except a small difference in memcpy. 

AES in CFB mode is done in much the same way, except of course it uses AES block cipher instead of SHA512(...) function above. Also AES uses blocks of 16 bytes, so AES has to run 4 rounds to catch up with a single SHA512 round. Overall, AES is faster. 

To compare the performance you can simply compare 4 rounds of AES to 1 round of SHA512. Watch out for compiler optimizations which may skew the result. Many tests have already been done for this, it shows AES is faster.

AES also uses key expansion which makes it more secure. Key expansion is relatively slow but it's done only once per file/data. This may skew the performance test depending on how the test is done.

typedef struct {
  unsigned char state[64];
  unsigned char key[32];
} MadCtx;


For improvement, don't let the key linger on in MadCtx::key. For example, you can use SHA512 to combine the key with IV (or state as you call it) so it becomes hidden during the operation, then you can drop key out of the structure.

Example:

unsigned char mad_IV[64];

void mad_init(const unsigned char *key, const unsigned char* iv)
{
    unsigned char buf[96];
    memcpy(buf, iv, 64);
    memcpy(buf + 64, key, 32);
    SHA512(buf, 96, mad_IV);
}

void mad_crypt(char const* in, int in_size, char* out, int encrypt)
{
    assert(0 == in_size % 64);

    int n = in_size &gt;&gt; 6; // in_size / 64
    while (n)
    {
        SHA512(mad_IV, 64, mad_IV);
        _xor64((uint64_t*)out, (uint64_t*)in, (uint64_t*)mad_IV);

        if (encrypt)
            memcpy(mad_IV, out, 64);
        else
            memcpy(mad_IV, in, 64);

        in += 64;
        out += 64;
        --n;
    }
}

int main()
{   
    unsigned char iv[64] = { 0 };
    unsigned char key[32] = { 0 };
    memcpy(iv, "iv", 2);
    memcpy(key, "key", 3);

    int size = 640;
    char *plaintext = malloc(size);
    char *decrypted = malloc(size);
    char *encrypted = malloc(size);

    memset(plaintext, 0, size);
    memset(encrypted, 0, size);
    memset(decrypted, 0, size);

    strcpy_s(plaintext, size, "plainxxxxxx.");

    mad_init(key, iv);
    mad_crypt(plaintext, size, encrypted, 1);

    mad_init(key, iv);
    mad_crypt(encrypted, size, decrypted, 0);

    phex(encrypted, 64);
    printf("\n\n");

    printf("plaintext: %s\n", plaintext);
    printf("decrypted: %s\n", decrypted);

    putchar('\n');

    return 0;
}