7
$\begingroup$

I've come up with this little routine for doing encryption using the SHA-2 (in this case SHA-256) hash function. As such it is a block cipher with a 256 bit (32 byte) block size and an arbitrary key length. It's easy to see that you could replace the hash function and get an entirely new type of algorithm.

I want to know if there are any problems, especially flaws, in this construction, and whether algorithms of this type have been studied.

Initialization with key:

$$ S_0 = H(k) $$

Encryption for plaintext blocks $P_1, \dots P_n$:

$$ S_i = H(S_{i-1})$$ $$ C_i = P_i \oplus S_i $$

Decryption for ciphertext blocks $C_1 \dots C_n$:

$$ S_i = H(S_{i-1})$$ $$ P_i = C_i \oplus S_i $$

Here is some sample C source code using SSL:

#include <openssl/sha.h>

struct sha_crypt_state {
    unsigned char digest[SHA256_DIGEST_LENGTH];
};

void sha_crypt_set_key( sha_crypt_state *state, unsigned char *key, int key_length )
{
    SHA256( key, key_length, state->digest );
}

void sha_crypt( sha_crypt_state *state, unsigned char *block )
{
    sha_crypt_state temp;
    SHA256( state->digest, sizeof(state->digest), temp.digest );
    memcpy( state->digest, temp.digest, sizeof(temp.digest) );

    for ( int i = 0; i < sizeof(state->digest); ++i ) {
        block[i] ^= state->digest[i];
    }
}

void sha_crypt_test()
{
    const char *key = "secret";

    // prepare a test block
    char block[SHA256_DIGEST_LENGTH];
    memset( block, 0, sizeof(block) );
    strcpy( block, "Hello, testing encryption!" );

    // test encrypt
    sha_crypt_state state;
    set_key( &state, (unsigned char *)key, strlen(key) );
    sha_crypt( &state, (unsigned char *)block );

    // test decrypt
    set_key( &state, (unsigned char *)key, strlen(key) );
    sha_crypt( &state, (unsigned char *)block );
}
$\endgroup$
4
  • 1
    $\begingroup$ Why do you call this a block cypher? Looks like a stream cypher to me. $\endgroup$ Commented Jan 20, 2012 at 12:45
  • $\begingroup$ Welcome to cryptography Stack Exchange. We want our questions here a bit more on the theoretical level, i.e. mostly without any actual source code, and mathematical formulas instead. I'll edit your question to be more conform with this idea. $\endgroup$ Commented Jan 20, 2012 at 12:47
  • 2
    $\begingroup$ This is almost the exact same algorithm as listed on this question: crypto.stackexchange.com/questions/48/… The answers there also provide some pretty good insight. $\endgroup$ Commented Jan 20, 2012 at 17:31
  • $\begingroup$ This is a cross-site duplicate of stackoverflow.com/questions/8939848/… $\endgroup$
    – e-sushi
    Commented Dec 4, 2013 at 16:19

3 Answers 3

9
$\begingroup$

Your cipher looks a bit like the output feedback mode of operation for block ciphers.

While OFB for block ciphers is considered safe (as long as it is used right), OFB for a hash function like you are using it has the problem that the key is only used at the start, to generate the "initialization vector", not at each step of the algorithm.

Thus, as CodeInChaos remarked, your code is vulnerable to a known-plaintext attack: Given one block of corresponding plaintext and ciphertext, we can derive the corresponding state of the cipher, and from this calculate all following states (and thus decrypt the rest of the message, though not blocks before the start).

Also, your scheme has no initialization vector, i.e. reusing the same key produces the same key stream.

I think you can get a secure version by including the key at each step of the hashing, and also including a proper initialization vector at the start, using this function for the state update:

$$ S_0 = H(K || IV ) $$ $$ S_i = H(K || S_{i-1})$$

$\endgroup$
2
9
$\begingroup$

First of all, this no block-cipher at all. It's a stream-cipher. Thus you can use every key only once, and you can't use any cipher modes built on block-ciphers.

Your scheme is vulnerable to a known plaintext attack. If the attacker knows 32 aligned(or 63 unaligned) bytes of plaintext, he can calculate the state of your cipher:

$ S_i = P_i \oplus C_i $

The later $ S_j (j>i) $ only depend on the $ S_i $ the attacker recovered, i.e. he can calculate all of them. And thus he can decrypt everything that comes after that known plaintext.

Such a plain text attack is often possible in practice. Files often start with known magic bytes, network protocols often contain known sequences in headers (http headers should be long enough),...

$\endgroup$
1
  • 1
    $\begingroup$ And to say things more generally, that kind of stream cipher is secure only if the stream would be a good PRNG. A PRNG should not allow recomputing its internal state from its output (because knowing the state implies being able to predict subsequent output, and a secure PRNG must offer prediction resistance). It is doable with hash functions, but needs more effort. See NIST SP800-90 for some hash-based PRNG (Hash_DRBG and HMAC_DRBG). $\endgroup$ Commented Jan 20, 2012 at 13:08
4
$\begingroup$

If I understood your code correctly, what you are doing is encrypting a message $m$ with a key $k$ by: $c=m\oplus h(k)$, in an ECB mode where $h$ is some hash function. Take two encrypted blocks $c_1$ and $c_2$ and add them: $c_1\oplus c_2 = m_1 \oplus h(k) \oplus m_2 \oplus h(k)=m_1\oplus m_2$.

Moreover, you may loose entropy if the initial secret is selected in a smaller subset than 2^256 hash output values (in the case of a 256-bit hash function).

$\endgroup$
1
  • $\begingroup$ It's not ECB mode. The state is maintained between successive calls to the encryption routine. In your notation: $c_1\oplus c_2 = m_1 \oplus h(k) \oplus m_2 \oplus h(h(k))$ $\endgroup$
    – user1449
    Commented Jan 20, 2012 at 11:52