|
I think this is catastrophically broken because of a known issue of how SHA-2 breaks its input into blocks: when A is aligned to a block boundary, sha(A || B) = f(sha(A), g(B)). http://en.wikipedia.org/wiki/Merkle%E2%80%93Damg%C3%A5rd_con... In your system, the derived password is a sha digest of a concatentation of other sha digests. It almost has the form sha(sha(SALT) || sha(RESOURCE) || sha(MASTER)), with sha() returning the ascii encoding of the base-16 representation. (The difference is three extra newline characters "\n", which makes things slightly messier). This string representation for sha-512 digests is 128 characters or 1028 bits, and sha-512 uses 1028-bit input chunks, so the final sha() call reads the other three digests aligned on chunk boundaries. (Again, few bits off because of newlines, but this is not fatal). So DERIVED(RESOURCE) has a form DERIVED = sha(sha(SALT) || sha(RESOURCE) || sha(MASTER))
= f(sha(sha(SALT) || sha(RESOURCE)), g(sha(MASTER)))
where f,g, are known functions. Moreover, f is reversible -- in SHA-512 it is just addition.So for an attack: given the salt, along with one resource/derived password pair, every other password can be computed. If you know SALT, RESOURCE_1, and DERIVED_1, you know these: sha(sha(SALT) || sha(RESOURCE_1))
DERIVED_1 = f(sha(sha(SALT) || sha(RESOURCE_1)), g(sha(MASTER)))
Because f is reversible, and you know its first argument, you also learn its other argument, g(sha(MASTER))
and can calculate any other password, given the resource name. Note you don't learn the master password, nor its sha digest -- you can't reverse the block function g(), not without breaking SHA-2 itself. But that's not necessary. |
If that's the case, wouldn't it suffice to mix A and B, for example with byte-wise XOR?