Apologies first, newbie here just setting off on my Pythonic journey and rather enjoying it so far, aside from this little issue I'm facing...
I've been trying to find a way to double hash (SHA1) and base64 encode some values, using Python's hashlib and base64 libraries, for a password digest but I'm stuck.
I need to populate a SOAP XML web service header with a digested password that uses the following algorithm; Base64(SHA1(NONCE + TIMESTAMP + SHA1(PASSWORD)))
An extract of the documentation, below, shows the correct result and some common errors:
QUOTE Input parameters:
Plain text password: AMADEUS
Raw Nonce (may be unprintable): secretnonce10111
Base64 encoded Nonce: c2VjcmV0bm9uY2UxMDExMQ==
Timestamp/Created: 2015-09-30T14:12:15Z
Correct result: Password digest: +LzcaRc+ndGAcZIXmq/N7xGes+k= Formula: Base64(SHA1($NONCE + $TIMESTAMP + SHA1($CLEARPASSWORD)))
All parameters correct, except $NONCE which has the same (Base64) format as in Nonce XML element: Password digest: AiRk9oAVpkYDX2MXh+diClQ0Lds= Formula: Base64(SHA1(Base64($NONCE) + $TIMESTAMP + SHA1($CLEARPASSWORD)))
SHA1 in hexadecimal encoding instead of raw SHA1 for initial plain password encryption and for concatenated string: Password digest: NWE1MGRhM2ZmNjFhMDA2ODUyNmIxMGM4MTczODQ0NjE2MWQyM2IxZQ== Formula: Base64(HEX(SHA1($NONCE) + $TIMESTAMP + HEX(SHA1($CLEARPASSWORD))))
SHA1 in hexadecimal encoding instead of raw SHA1 for concatenated string, password not encrypted with SHA1: Password digest: NzU0ZjJlMTc2ZjkxZmM2OTg4N2E0ZDlkMWY2MWE0YWJkOGI0MzYxZA== Formula: Base64(HEX(SHA1($NONCE + $TIMESTAMP + $CLEARPASSWORD)))
Almost everything is incorrect: SHA1 in hexadecimal encoding instead of raw SHA1 for concatenated string, password not encrypted with SHA1, $NONCE has the same (Base64) format as in Nonce XML element: Password digest: NGIzYmNiY2I3Njc2ZjZiNzdmNDMwMGVlMTIwODdhZDE1ZmZlOTEwMA== Formula: Base64(HEX(SHA1(Base64($NONCE) + $TIMESTAMP + $CLEARPASSWORD))) UNQUOTE
Here is what I have so far, using the variables suggested in the documentation so that I can check the results:
import base64
import hashlib
NONCE = "secretnonce10111"
TIMESTAMP = "2015-09-30T14:12:15Z"
PASSWORD = "AMADEUS"
PWSHA1 = hashlib.sha1(PASSWORD.encode('ascii')).hexdigest()
CONCAT = (NONCE + TIMESTAMP + str(PWSHA1)).encode('ascii')
CONCATSHA1 = hashlib.sha1(CONCAT).hexdigest()
PWDIGEST = base64.b64encode(CONCATSHA1.encode('ascii')).decode('ascii')
print(type(PWDIGEST), PWDIGEST)
Result
<class 'str'> NWE1MGRhM2ZmNjFhMDA2ODUyNmIxMGM4MTczODQ0NjE2MWQyM2IxZQ==
Note: I've used encoding in ('utf-8') as well as ('ascii') and just (), and I've also written a more concise version of the code above but to no avail...
PWDIGEST = base64.b64encode(hashlib.sha1((NONCE + TIMESTAMP + str(hashlib.sha1(PASSWORD.encode('ascii')).hexdigest())).encode('ascii')).hexdigest().encode('ascii')).decode('ascii')
So as you can see, according to the documentation, it's not wokring because "SHA1 in hexadecimal encoding instead of raw SHA1 for initial plain password encryption and for concatenated string".
I realise that I'm using hexdigest() in the code above, which is rending the hash to hexadecimal, but it's the closest I can get.
Following the Python hashlib docs I have also tried using .digest() and update(), which gives a completely different result that's not in the docs, as shown below:
PWSHA = hashlib.sha1()
PWSHA.update(PASSWORD.encode('utf-8'))
PWSHA1 = PWSHA.digest()
CONCAT = (NONCE + TIMESTAMP + str(PWSHA1))
CSHA = hashlib.sha1()
CSHA.update(CONCAT.encode("utf-8"))
CSHA1 = CSHA.digest()
PWDIGEST = base64.b64encode(CSHA1).decode('ascii')
print(type(PWDIGEST), PWDIGEST)
Results in
<class 'str'> exB8TjilUE+w8b2SKs+PkOhRjfg=
I've also tried inputting the bytes values directly into base64.b64encode, but no joy...
I have loads of questions, but I guess the most important ones are; what am I missing? Is there an elegant way in which this can be done? Is it possible to concatenate "raw SHA1" or "bytes-like objects" values with strings?
I don't understand what the instructions are telling you to do, but your use of hexdigest()
instead of digest()
is almost certainly a mistake. When you work with crypto, you are almost always dealing with bytes, not strings. Everything that is not a byte string (the text, the nonce, the timestamp) should be converted into a byte string. All calculations and concatenations should be done with this bytestring. And then as the last step, if necessary, you convert it back to a string.