Skip to content

jedisct1/go-progressive-hash

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Progressive hashing

Traditional password stretching functions such as PBKDF2, Scrypt, Bcrypt and Argon2 compute a fixed-length digest from a variable length, low-entropy input. These functions are intentionally slow, possibly with large memory requirements, in order to mitigate dictionary attacks.

The flipside is that in order to verify that an input matches a previously computed hash, the whole computation has to be performed again. This requires significant resources, even if the input doesn't happen to match the expected hash.

This package implements a proof of concept of a progressive hash function, a PBKDF2 variant that can detect a mismatching hash for a given input while doing the computation, and thus return early instead of performing the full computation.

The first output bits of this function are computed as follows:

H(x): BLAKE2B-512(x)
G(r, x): r recursive iterations of H(x)
o: output
N: number of progressive bits in the output
M: total output length
seed: application-specific seed
B(b, x): bit b of x
R0: initial number of iterations
C <- 2

h <- H(seed || pad || in)

r <- R0
h <- G(r, h)
B(0, o) <- B(0, h)

r <- r * C
h <- G(r, h)
B(1, o) <- B(0, h)

...

r <- r * C
h <- G(r, h)
B(n, o) <- B(0, h)

Individual bits of the prefix are computed sequentially and the work factor doubles after every bit.

Finally, the remaining M-N bits are copied from bits of h at the same position.

Computing a full new hash requires a full computation.

However, the verification function can compute the first bit, stop early if it doesn't match, compute the next one only if it does and repeat the process until N bits have been accumulated.

M should be large enough to avoid collisions. The maximum output length is 512 bits.

On the other hand, N should be short enough to produce collisions during the fast part of the computation.

Usage

seed := []byte("test application")
in := []byte("input string")

h, err := progessiveHash.Hash(in, seed, 50000, 8, 256)
if err != nil {
	panic(err)
}

err = progessiveHash.Verify(in, seed, 50000, 8, 256, h)
if err != nil {
	panic(err)
}

// This is unlikely to require a full computation
badIn := []byte("wrong input")
err = progessiveHash.Verify(badIn, seed, 50000, 8, 256, h)
if err != nil {
	panic("verification shouldn't have passed")
}