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 <stdint.h>
#include <assert.h>
#include <string.h>
#include <openssl/sha.h>
// Private
static void _xor64(uint64_t* dest, uint64_t const* a, uint64_t* b)
{
for(int i = 0; i < 8; ++i)
*dest++ = *a++ ^ *b++;
}
// Public
void mad_ctx_init(MadCtx* mad, unsigned char const* key,
unsigned char const* iv)
{
memcpy(mad->state, iv, 64);
memcpy(mad->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 >> 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->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 >> 6; // in_size / 64
while(n){
uint64_t x[8];
SHA512((unsigned char const*)mad, 96, (unsigned char*)x);
memcpy(mad->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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
void phex(void const* data, size_t size)
{
char const* table = "0123456789abcdef";
unsigned char const* in = data;
while(size--){
int c;
c = table[*in >> 4];
putchar(c);
c = table[*in & 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(&ctx, key, iv);
mad_encrypt(&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(&ctx, key, iv);
mad_decrypt(&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));
}