SHA-based encryption function in Python
Bryan Olson
fakeaddress at nowhere.org
Thu Apr 25 00:39:38 EDT 2002
Paul Rubin wrote:
> ARGGH! I knew about that before too, but forgot about it while writing
> the code.
I figured you were testing us.
> I will replace SHA(block) with SHA(key + block) and release
> a new version tonight, with the MAC improved too. Sound ok?
Let me see if I understand. We start with a shared secret key called
"key", which may be of any length. Each message requires it's own nonce
of 16 bytes.
From this we form a session encrption key, called "enc_key" as:
enc_key = _hash("enc" + key + nonce)
Now we need to intialize the variable "seed", and we need a new way
to do it. Perhaps:
seed = _hash("seed" + enc_key)
Then we form the running key, called "xkey", which is a list of
20-byte blocks.
xkey = []
for i in range(n_blocks_needed):
seed = _hash(enc_key + seed)
xkey.append(seed)
The ciphertext is the exclusive or of the plaintext and the running key.
The nonce travels with the ciphertext.
Now I have a few suggestions.
The system presents a cipher, a MAC, key derivations, and formats. I
think these should be defined individually. I agree with defining how
to encrypt-and-sign with a master key, but I think the smaller elements
should also be defined and exposed on their own.
I would separate the IV from the ciphertext, and make computing the MAC
a separate function. I see no need to include the nonce in computing
the auth_key. In some protocols, such as SSL/TLS, we would send a stream
in many sections.
Here's a candidate reference implementation. Each function does one
thing that I believe should be individually defined. The code is
written for lucidity and simplicity, not speed. I'm using Paul Rubin's
latest (at the time of this writing) MAC, but really I recommend going
with HMAC.
########################################################
import sha
class CryptError(Exception): pass
MACLEN = 8
IVLEN = 16
def session_cipher(enc_key, text):
seed = _hash("seed" + enc_key)
result_text = []
while text:
(block, text) = (text[:20], text[20:])
seed = _hash(enc_key + seed)
block = _xor_strings(block, seed[:len(block)])
result_text.append(block)
return "".join(result_text)
def session_sign(auth_key, text):
k1 = _hash('auth1' + auth_key)
k2 = _hash('auth2' + auth_key)
return _hash(k1 + _hash(k2 + text))[:MACLEN]
def derive_enc_key(master_key, nonce):
assert len(nonce) == IVLEN
return _hash('enc' + master_key + nonce)
def derive_auth_key(master_key):
return _hash('auth' + master_key)
def encipher(master_key, plaintext):
nonce = _generate_nonce_somehow(master_key + plaintext)
enc_key = derive_enc_key(master_key, nonce)
return (nonce, session_cipher(enc_key, plaintext))
def decipher(master_key, nonce, ciphertext):
enc_key = derive_enc_key(master_key, nonce)
return session_cipher(enc_key, ciphertext)
def sign(master_key, text):
session_key = derive_auth_key(master_key)
return session_sign(session_key, text)
def verify(master_key, text, signature):
if sign(master_key, text) != signature:
raise CryptError, "invalid key or ciphertext"
def encipher_and_sign(master_key, plaintext):
(nonce, ciphertext) = encipher(master_key, plaintext)
signature = sign(master_key, ciphertext)
return (nonce, ciphertext, signature)
def verify_and_decipher(master_key, nonce, ciphertext, signature):
verify(master_key, ciphertext, signature)
return decipher(master_key, nonce, ciphertext)
#############################################################
# Utility functions
def _hash(x):
return sha.new(x).digest()
def _xor_strings(s1, s2):
return "".join(map(lambda x, y: chr(ord(x) ^ ord(y)), s1, s2))
_state = ""
def _generate_nonce_somehow(entropy=""):
global _state
import time
_state = _hash(`time.time()` + _state + entropy)
return _state[:IVLEN]
#############################################################
# Not a great test but....
if __name__ == "__main__":
master_key = "Big time secret key"
plaintext = "No matter what happens, somebody just knew it would."
nonce, ciphertext, sig = encipher_and_sign(master_key, plaintext)
deciphertext = verify_and_decipher(master_key, nonce, ciphertext, sig)
print deciphertext
assert(deciphertext == plaintext), "Darn, it failed."
More information about the Python-list
mailing list