SHA256/SHA512 crypt_hash bug
From the Pike developers mailinglist/LysLysKOM 26091551:
26091551 2023-11-23 13:38 /146 rader/ Ricard Garra Oronich <rgarra@lleida.net>
Sänt av: SRS0=5fN1=HE=lists.lysator.liu.se=pike-devel-bounces@lysator.liu.se
Importerad: 2023-11-23 13:38 av Brevbäraren
Extern mottagare: pike-devel@lists.lysator.liu.se <pike-devel@lists.lysator.liu.se>
Mottagare: Pike (-) developers forum <21443>
Ärende: SHA256/SHA512 crypt_hash bug
------------------------------------------------------------
Hello,
I have been looking into using the SHA256
/SHA512
crypt_hash
functions, and during my testing, comparing the results with other implementations (Python's passlib, Openssl, MySQL and C), I found that for some passwords used, the resulting hash is different that the ones generated by other languages/implementations, and therefore would fail if a hash of these passwords done with Pike was then computed/verified with another system.
I don't know the reason of this, but it seems to fail with some passwords, regardless of the salt or number of rounds, and both SHA256
and SHA512
"fail", while for some other passwords it works well. A couple of values that fail are simply "pass" and "password".
Below I attach some examples, using the mentioned failing passwords, first in Pike, and then comparing the computation of the same hash, using the same password/salt/rounds in other implementations, and you can see that all the rest are the same among them, but different from Pike. This fails also using Crypto.Password.hash()
, and letting it compute a random salt. I also tested the reference base C implementation cited in your documentation (https://akkadia.org/drepper/SHA-crypt.txt), and it gives the same result as the others (and thus, different than Pike's).
In addition, I also found that in some cases, the random salt contains the character "+"
, which the Python library I tested doesn't 'allow' it seems, this is probably not a bug itself, but could be taken into account for future implementations when generating a salt, I attach here their explanation:
From Python's passlib docs: "Restricted salt string character set:
"The underlying algorithm can unambiguously handle salt strings which contain any possible byte value besides \x00
and $
. However, Passlib strictly limits salts to the hash64
character set, as nearly all implementations of sha256-crypt generate and expect salts containing those characters, but may have unexpected behaviors for other character values."
The hash64
character set is:
HASH64_CHARS = u("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
I would like to know if this is really a bug, and if it is, if you would be able to maybe fix it in some revision, if that's the case, I would be interested in using the updated/corrected code ASAP, implementing it in a module of my own perhaps.
Code examples:
Pike v8.0 release 1116 running Hilfe v3.5 (Incremental Pike Frontend):
> Crypto.SHA512.crypt_hash("pass","salt",10000);
(1) Result: "gJkLS5cThyks4JXgQ8x3UNvwYLNW22AHZdM70xoRvEma//RJfeBOC/Ik0EddD7DLI9Sau9pYCV29OLzxxVFph1"
> Crypto.Password.hash("pass","6",10000);
(2) Result: "$6$rounds=10000$UrtM9r9XUEqEp1H3$X.OaL5Jp8PNtjkgKPUdGv7hxFg.jjF0U0dqLnscH3F45socUKOP6jWK2hDtvZAj2f1/I1u7Sgc3n3U7MMJDIi1"
> Crypto.SHA512.crypt_hash("password","salt",10000);
(3) Result: "aaSSVStpZx9F9OYvj2130N4GR9uJcP/A10FvDYjBQWbs2RAYN.9iCJb9GvREy9Huz7H6Bp6u5SlHEEcrxaDPi0"
> Crypto.Password.hash("password","6",10000);
(4) Result: "$6$rounds=10000$CfcWNrK0SB8eROnk$a/agNPy2AKOVf4qNTHRXw16nUSjHeINZCTxgw5PjLlRBFnxJ6DDI/rebM3I8LD8pubgOji7ps70JddbFUWo190"
> Crypto.SHA256.crypt_hash("pass","salt",10000);
(5) Result: "s2.jgIPueybkP3hvZqjt3ql5emK9LDBUGvrNc8Cfq3B"
> Crypto.Password.hash("pass","5",10000);
(6) Result: "$5$rounds=10000$Pjj07C7RqLD+ydig$Heqa8mVcO6ttpcXxxtmYoRF6kuPDbieJgy8YPkNNpl."
> Crypto.Password.hash("pass","5",10000);
(7) Result: "$5$rounds=10000$PKoyOiOCTDQTwjBS$jgKXglMIaloY8JPEz/Zp14q1S6qrG2DShYeEUuO4YC4"
Python:
>>> from passlib.hash import sha512_crypt
>>> sha512_crypt.hash("pass", salt="salt",rounds=10000)
'$6$rounds=10000$salt$OSEwMhCIwtjyui53YYIYdyKYKKMcnmS2EbioMYM3/7ya4jWlyYim8VJvMW4cVEgVkO.a.YBgUKiMtpAGUQSXf.'
>>> sha512_crypt.hash("pass", salt="UrtM9r9XUEqEp1H3",rounds=10000)
'$6$rounds=10000$UrtM9r9XUEqEp1H3$IazyuGXF.YEWCnYtarD5BGYduUW2zzydYZXN3xa5QfHx5wvE.lQyU2rPnqMSiSLMvXR0En2Suo/2805nGp1FU.'
>>> sha512_crypt.hash("password", salt="salt",rounds=10000)
'$6$rounds=10000$salt$dE5fLfpn2uXfkz.eouwYK/BjrHRu.piovQPjwlE06fDJHwMlg2l.IqEBUIfWBzf7YPXOAddB3FM7rnXHHKVNt.'
>>> sha512_crypt.hash("password", salt="CfcWNrK0SB8eROnk",rounds=10000)
'$6$rounds=10000$CfcWNrK0SB8eROnk$wuB7fFzyIokmn5WLk2theKXOpBbIkuyRqtUNTtwPWdEI7eKi.dnvcPtrM337BUXvgUXwrBPYWjKFl.r0i39Yz0'
>>> pike_hash = "$6$rounds=10000$UrtM9r9XUEqEp1H3$X.OaL5Jp8PNtjkgKPUdGv7hxFg.jjF0U0dqLnscH3F45socUKOP6jWK2hDtvZAj2f1/I1u7Sgc3n3U7MMJDIi1"
>>> sha512_crypt.verify("pass",pike_hash)
False
>>> pike_hash2="$6$rounds=10000$CfcWNrK0SB8eROnk$a/agNPy2AKOVf4qNTHRXw16nUSjHeINZCTxgw5PjLlRBFnxJ6DDI/rebM3I8LD8pubgOji7ps70JddbFUWo190"
>>> sha512_crypt.verify("password",pike_hash2)
False
>>> sha256_crypt.hash("pass", salt="salt",rounds=10000)
'$5$rounds=10000$salt$Lffn09CeAx2gukDdao1thbgNhgpn41BG4JJNh0Nk6C/'
>>> pike_hash3 = '$5$rounds=10000$Pjj07C7RqLD+ydig$Heqa8mVcO6ttpcXxxtmYoRF6kuPDbieJgy8YPkNNpl.'
>>> sha256_crypt.verify("pass",pike_hash3)
Traceback (most recent call last):
(...)
raise ValueError("invalid characters in %s salt" % cls.name)
ValueError: invalid characters in sha256_crypt salt
>>> sha256_crypt.hash("pass", salt="Pjj07C7RqLD+ydig",rounds=10000)
Traceback (most recent call last):
(...)
raise ValueError("invalid characters in %s salt" % cls.name)
ValueError: invalid characters in sha256_crypt salt
>>> pike_hash4 = "$5$rounds=10000$PKoyOiOCTDQTwjBS$jgKXglMIaloY8JPEz/Zp14q1S6qrG2DShYeEUuO4YC4"
>>> sha256_crypt.verify("pass",pike_hash4)
False
Openssl:
$ openssl passwd -6 -salt 'rounds=10000$salt' 'pass'
$6$rounds=10000$salt$OSEwMhCIwtjyui53YYIYdyKYKKMcnmS2EbioMYM3/7ya4jWlyYim8VJvMW4cVEgVkO.a.YBgUKiMtpAGUQSXf.
$ openssl passwd -6 -salt 'rounds=10000$salt' 'password'
$6$rounds=10000$salt$dE5fLfpn2uXfkz.eouwYK/BjrHRu.piovQPjwlE06fDJHwMlg2l.IqEBUIfWBzf7YPXOAddB3FM7rnXHHKVNt.
$ openssl passwd -5 -salt 'rounds=10000$salt' 'pass'
$5$rounds=10000$salt$Lffn09CeAx2gukDdao1thbgNhgpn41BG4JJNh0Nk6C/
$ openssl passwd -5 -salt 'rounds=10000$Pjj07C7RqLD+ydig' 'pass'
$5$rounds=10000$Pjj07C7RqLD+ydig$56R7PRLYPMhN07ZhnoGFCKkNK5.N0ZDY0hViH4yG19/
MySQL:
SELECT ENCRYPT('pass', '$6$rounds=10000$salt');
$6$rounds=10000$salt$OSEwMhCIwtjyui53YYIYdyKYKKMcnmS2EbioMYM3/7ya4jWlyYim8VJvMW4cVEgVkO.a.YBgUKiMtpAGUQSXf.
SELECT ENCRYPT('password', '$6$rounds=10000$salt');
$6$rounds=10000$salt$dE5fLfpn2uXfkz.eouwYK/BjrHRu.piovQPjwlE06fDJHwMlg2l.IqEBUIfWBzf7YPXOAddB3FM7rnXHHKVNt.
SELECT ENCRYPT('pass', '$5$rounds=10000$salt');
$5$rounds=10000$salt$Lffn09CeAx2gukDdao1thbgNhgpn41BG4JJNh0Nk6C/
SELECT ENCRYPT('pass', '$5$rounds=10000$Pjj07C7RqLD+ydig');
$5$rounds=10000$Pjj07C7RqLD+ydig$56R7PRLYPMhN07ZhnoGFCKkNK5.N0ZDY0hViH4yG19/
I await your response, thank you for your time. Yours truthfully,
Ricard Garra Oronich Desarollador de Núcleo | Core Developer rgarra@lleida.net (+34) 680 423 701 [http://www.lleida.net/mailing/fires/signatura.png] Parc Científic i Tecnològic Agroalimentari de Lleida Edifici H1 2a planta B | 25003 Lleida (Spain) Tel. (+34) 973 282 300 Fax (+34) 973 282 195 www.lleida.nethttp://www.lleida.net Política de privacidadhttp://www.lleida.net/es/privacy.html
(26091551) /Ricard Garra Oronich <rgarra@lleida.net>/