I know about PBKDF2, bcrypt, scrypt, etc. and while I'm not a crypt professional, I think I understand why are they better than just "hash(pass+salt)".
But a colleague of mine surprised me with the following password hash function which he claims to be good, because
- It is slow on CPU (takes about 2-3 seconds in i5)
- It will be slower on GPU because needs random access to a lot of memory (why? GPUs have lots of gigabytes)
- It cannot be timed, any time-based attack will fail even if underlying hash-function will be vulnerable (aren't hash calculation time a constant, for SHA512 at least?).
- It cannot be cracked by audio attack because of fake operations (what's that?)
So while I do not like bicycles in cryptography in general, arguments sound reasonable and I just want to know the truth fro myself, in the first place and to argue for rewriting of this code to something standard.
EDIT
So here are some specific questions, as suggested in comment by @B-Con:
- Why this (needing a lot of memory) hash will be slow of GPU?
- Isn't SHA512 and RIPEMD160 calculation time a constant?
Does a fake data processing in parallel of real data really help against audio attack whatever it is?
private static readonly RNGCryptoServiceProvider _random = new RNGCryptoServiceProvider(); private const int BlockCount = 1024; private const int IterationCount = 65536; private static byte[] GetHashInternal(byte[] data, byte[] salt) { var sha512 = new SHA512Managed(); var ripemd = new RIPEMD160Managed(); var hashLength = (sha512.HashSize / 8) + (ripemd.HashSize / 8); var realHash = new byte[BlockCount * hashLength]; var fakeHash = new byte[BlockCount * hashLength]; var realData = new byte[hashLength]; var realSalt = new byte[hashLength]; var fakeData = new byte[hashLength]; var fakeSalt = new byte[hashLength]; for (var index = 0; index < data.Length; index++) { realData[index % (hashLength - 1)] ^= data[index]; } for (var index = 0; index < salt.Length; index++) { realSalt[index % (hashLength - 1)] ^= salt[index]; } _random.GetBytes(fakeData); _random.GetBytes(fakeSalt); var length1 = sha512.HashSize / 8; var length2 = ripemd.HashSize / 8; var offset = 0; for (var step = 0; step < IterationCount; step++) { for (var index = 0; index < hashLength; index++) { realHash[(offset + index) % realHash.Length] ^= realData[index]; } for (var index = 0; index < hashLength; index++) { fakeHash[(offset + index) % realHash.Length] ^= fakeData[index]; } Array.Copy(sha512.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + (hashLength / 2)) % (realHash.Length - length1), length1); Array.Copy(sha512.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + (hashLength / 2)) % (fakeHash.Length - length1), length1); Array.Copy(ripemd.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + length1 + (hashLength / 2)) % (realHash.Length - length2), length2); Array.Copy(ripemd.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + length1 + (hashLength / 2)) % (fakeHash.Length - length2), length2); for (var index = 0; index < hashLength; index++) { realHash[(offset + index) % realHash.Length] ^= realSalt[index]; } for (var index = 0; index < hashLength; index++) { fakeHash[(offset + index) % realHash.Length] ^= fakeSalt[index]; } Array.Copy(ripemd.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + length1 + (hashLength / 2)) % (realHash.Length - length2), length2); Array.Copy(ripemd.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + length1 + (hashLength / 2)) % (fakeHash.Length - length2), length2); Array.Copy(sha512.ComputeHash(realHash, offset, hashLength), 0, realHash, (offset + (hashLength / 2)) % (realHash.Length - length1), length1); Array.Copy(sha512.ComputeHash(fakeHash, offset, hashLength), 0, fakeHash, (offset + (hashLength / 2)) % (fakeHash.Length - length1), length1); offset = (offset + 13) % (realHash.Length - hashLength); } return sha512.ComputeHash(realHash); }