fix(accounts): compare salt and digest lengths in NeedsRehash
HashMetadataValues carries SaltLength and HashLength alongside the type and the cost parameters, and NeedsRehash ignored both. A hash with a 16-byte digest or an 8-byte salt verified fine and was judged current forever -- exactly the drift this method exists to catch. Unreachable today, since every hash ModernUO has written uses the library defaults of 32 and 16, but that is a property of the current configuration rather than of the check. The two lengths are the decoded sizes of the base64 segments rather than entries in the m/t/p list, so they cannot be varied through the existing theory template; they get their own rows with pinned literals. The "current defaults" row of that theory is the negative control. Also name the benchmark behind the 8.5 ms / 10.1 ms figures in the parameter comment: ModernUO-Benchmarks/Benchmarks/Argon2Hashing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dce677b8c3
commit
ccfbc1878a
2 changed files with 26 additions and 4 deletions
|
|
@ -105,6 +105,21 @@ public class PasswordProtectionTest
|
|||
Assert.Equal(expected, Argon2PasswordProtection.Instance.NeedsRehash(hash));
|
||||
}
|
||||
|
||||
// The digest and salt lengths are not in the parameter list -- they are the decoded sizes of the
|
||||
// two base64 segments -- so they cannot be varied through the theory template above. Both hashes
|
||||
// here carry the current type and cost; only a segment length differs from the library defaults
|
||||
// (32-byte digest, 16-byte salt). The "current defaults" row of the theory above is the negative
|
||||
// control: it uses those default lengths and must stay false.
|
||||
[Theory]
|
||||
// 16-byte digest: 22 base64 chars instead of the 43 a 32-byte digest encodes to.
|
||||
[InlineData("$argon2id$v=19$m=16384,t=1,p=1$LD1XJz7P3wQmIJ+Tu6ScgA$NO5hBABsHQ172C5nDO2X4g")]
|
||||
// 8-byte salt: 11 base64 chars instead of the 22 a 16-byte salt encodes to.
|
||||
[InlineData("$argon2id$v=19$m=16384,t=1,p=1$LD1XJz7P3wQ$NO5hBABsHQ172C5nDO2X4gWnB4jDef3x6WhLdVE2LFw")]
|
||||
public void Argon2_NeedsRehash_ComparesSaltAndDigestLengths(string hash)
|
||||
{
|
||||
Assert.True(Argon2PasswordProtection.Instance.NeedsRehash(hash));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("not-a-hash")]
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ public class Argon2PasswordProtection : IPasswordProtection
|
|||
|
||||
// Argon2id over Argon2i: RFC 9106 recommends Argon2i only where side-channel resistance is
|
||||
// required and memory is scarce. 16 MiB at t=1 measures cheaper than the old 8 MiB at t=3
|
||||
// (8.5 ms vs 10.1 ms) while doubling memory-hardness, which is the property that resists GPU
|
||||
// and ASIC cracking; iterations mostly buy wall-clock. p=1 because native argon2 spawns a
|
||||
// thread per lane, which is oversubscription on the 1-2 core hosts this path exists to serve.
|
||||
// (8.5 ms vs 10.1 ms, measured by ModernUO-Benchmarks/Benchmarks/Argon2Hashing) while doubling
|
||||
// memory-hardness, which is the property that resists GPU and ASIC cracking; iterations mostly
|
||||
// buy wall-clock. p=1 because native argon2 spawns a thread per lane, which is oversubscription
|
||||
// on the 1-2 core hosts this path exists to serve.
|
||||
private readonly Argon2PasswordHasher _passwordHasher = new(
|
||||
time: 1,
|
||||
memory: 16384,
|
||||
|
|
@ -52,9 +53,15 @@ public class Argon2PasswordProtection : IPasswordProtection
|
|||
return true;
|
||||
}
|
||||
|
||||
// The digest and salt lengths live in the base64 segments rather than the parameter list,
|
||||
// but they are just as much a part of "was this produced with the parameters we configure
|
||||
// today". Casting the hasher's uint properties keeps the comparison signed-vs-signed; both
|
||||
// are small byte counts, so the narrowing cannot lose anything.
|
||||
return values.ArgonType != _passwordHasher.ArgonType
|
||||
|| values.MemoryCost != _passwordHasher.MemoryCost
|
||||
|| values.TimeCost != _passwordHasher.TimeCost
|
||||
|| values.Parallelism != _passwordHasher.Parallelism;
|
||||
|| values.Parallelism != _passwordHasher.Parallelism
|
||||
|| values.HashLength != (int)_passwordHasher.HashLength
|
||||
|| values.SaltLength != (int)_passwordHasher.SaltLength;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue