Skip to content

fix: enforce minimum digest length - #280

Merged
Spomky merged 1 commit into
11.5.xfrom
fix/enforce-minimum-digest-length
Jun 6, 2026
Merged

fix: enforce minimum digest length#280
Spomky merged 1 commit into
11.5.xfrom
fix/enforce-minimum-digest-length

Conversation

@Spomky

@Spomky Spomky commented Jun 6, 2026

Copy link
Copy Markdown
Member

What

Reject digest algorithms that cannot satisfy the RFC 4226 dynamic truncation.

The truncation reads four bytes starting at an offset taken from the low nibble of the last digest byte, i.e. an offset in [0, 15], so it reads up to the 19th byte. A digest shorter than 19 bytes (e.g. md5, 16 bytes) makes the truncation read past the end of the hash, collapsing the generated and verified OTP to a small, secret-independent set of values.

This is framed as an enforcement of the spec constraint, not a nominative blacklist: the length rule generically rejects every too-short digest (md4, ripemd128, tiger128, …), md5 being one case.

Changes

  • OTP::getParameterMap() validates the algorithm against hash_hmac_algos() (instead of hash_algos()), which also rejects non-HMAC algorithms (crc32, adler32, …) that previously slipped through and crashed later in hash_hmac().
  • A minimum digest size of 19 bytes is enforced, computed dynamically via strlen(hash($algo, '', true)) — no per-algorithm magic values, robust to future PHP additions.
  • Both entry points are covered: the direct setter (withDigest/setDigest) and the otpauth:// provisioning URI (Factory::loadFromProvisioningUri).
  • Docblock of OTPInterface::getDigest() and doc/Customize.md no longer present md5 as a supported value.
  • Behavioural change documented in doc/UPGRADE_v11-v12.md (ships in v11.5.0).

Compatibility

Breaking for accounts configured with a sub-19-byte digest (md5 & co.) — those were already out of spec and non-interoperable with authenticator apps. They must be re-enrolled with sha1, sha256 or sha512.

Tests / QA

  • New tests: short-digest rejection (direct setter + provisioning URI), spec-compliant digests accepted.
  • Full suite green: phpunit (160 tests / 440 assertions), phpstan (max), ecs, rector, deptrac (0 violations), lint.

The RFC 4226 dynamic truncation reads four bytes starting at an offset in
[0, 15], i.e. up to the 19th byte of the digest. A shorter hash (e.g. md5,
16 bytes) makes the truncation read past the end of the digest, collapsing
the generated/verified OTP to a small, secret-independent set of values.

Enforce the minimum digest size required by the spec instead of accepting any
hash_algos() entry:

- validate against hash_hmac_algos() so non-HMAC algorithms (crc32, adler32)
  are rejected up front instead of crashing later in hash_hmac()
- require the raw digest to be at least 19 bytes, computed dynamically so the
  rule holds for any current or future algorithm without per-algo magic values

Both entry points are covered (direct setter and otpauth:// provisioning URI).
md5 and other sub-19-byte digests are now rejected; sha1/sha256/sha512 remain.

Docblocks and docs updated to drop md5 from the supported values, and the
behavioural change is documented for the v11.5 upgrade.
@Spomky Spomky self-assigned this Jun 6, 2026
@Spomky
Spomky force-pushed the fix/enforce-minimum-digest-length branch from f12fc76 to 7247a77 Compare June 6, 2026 23:37
@Spomky Spomky added this to the 11.5.0 milestone Jun 6, 2026
@Spomky Spomky changed the title fix: enforce minimum digest length (GHSA-6gj7-7cpc-j2xc) fix: enforce minimum digest length Jun 6, 2026
@Spomky
Spomky merged commit 877683d into 11.5.x Jun 6, 2026
11 checks passed
@Spomky
Spomky deleted the fix/enforce-minimum-digest-length branch June 6, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant