I have been writing a class that encrypts and decrypts with block ciphers. I want to use Counter Mode(CTR/CM). We know that Counter Mode generates a keystream basing on counters, then XOR the keystream with plaintext to produce ciphertext. So, one can decrypt as long as he can reproduce the keystream, that is, to reproduce the counter and get the right key.
I observed that the decrypt method of a cipher is not necessary in CTR mode, since to reproduce the keystream we only need to encrypt counter blocks again.
So why can't I replace the ordinary cipher with a hash function(thus make key part of the counter) or a HMAC function(which accepts a key)? Are hash functions not-so-pseudorandom when compared to ciphers in keystream generation, or there's more reason?
p.s.: This question also works with OFB mode -- hashing an IV with or without a key again and again also produce something looks like a keystream.
p.s.2: an example:
1)Let's construct a counter of 16 bytes:
aPADDING00000000
aPADDING00000001
aPADDING00000002
Here we get 3 blocks.
2)Then use HMAC of MD5 to generate keystream:
key = 'This is a key.'
stream[0] = HMAC('This is a key.','aPADDING00000000').hexdigest()
stream[1] = HMAC('This is a key.','aPADDING00000001').hexdigest()
stream[2] = HMAC('This is a key.','aPADDING00000002').hexdigest()
# now stream is:
# ['73f2e665c30aaec5bbead51166aaa85f',
# '91392d0755638fbce5c689f96b02494f',
# '1d18a81751179858d151dd86179385f9']
stream_str = "".join(stream).decode('hex') # stream_str = '73f2 ... 85f9'.decode('hex')
3) Now stream_str looks very much like a keystream. To encrypt:
plaintext = 'This is a plaintext that has length of 48 bytes.'
ciphertext = stream_xor(plaintext,stream_str) # stream_xor XORs the two inputs bit by bit.
or, to decrypt:
decrypted = stream_xor(ciphertext,stream_str) # ciphertext produced before.