Version: otplib 13.4.1 (also reproduced on earlier 13.x)
v13 changed the HashAlgorithm enum to lowercase ('sha1' | 'sha256' | 'sha512'). Code migrating from v12 conventions (or following RFC nomenclature) passes 'SHA1' / 'SHA-1' — and instead of rejecting the unknown value, the crypto plugin silently computes HMAC-SHA512:
import { NobleCryptoPlugin } from 'otplib';
const c = new NobleCryptoPlugin();
(await c.hmac('sha1', key, data)).length; // 20 bytes — SHA-1 ✓
(await c.hmac('SHA1', key, data)).length; // 64 bytes — silently SHA-512 ✗
(await c.hmac('SHA-1', key, data)).length; // 64 bytes — silently SHA-512 ✗
Consequence: generateSync/verifySync remain perfectly self-consistent, so every app-level test passes, while the RFC 6238 test vectors fail and codes from Google Authenticator / Authy / 1Password never verify. For a TOTP library this is a worst-case failure mode: it ships silently and breaks only against real devices. In plain JavaScript (no TypeScript enum checking) there is no signal at all — we shipped exactly this and every user enrollment failed with "invalid code" until we diffed the library's output against the RFC vectors.
Repro against the RFC 6238 Appendix B vector (secret GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ = base32("12345678901234567890"), T=59s, expected 94287082):
import { generateSync } from 'otplib';
const secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ';
generateSync({ secret, algorithm: 'sha1', digits: 8, period: 30, epoch: 59 }); // '94287082' ✓
generateSync({ secret, algorithm: 'SHA1', digits: 8, period: 30, epoch: 59 }); // '69342147' ✗ (silent SHA-512)
Suggested fix: validate the algorithm string (case-insensitively, or normalize before dispatch) and throw on anything unrecognized. A thrown error surfaces the misuse on the first call; the current silent fallback produces valid-looking but interoperability-breaking codes. Given v12 documented uppercase-style options and v13 is the migration target, case-normalizing (algorithm.toLowerCase(), and perhaps accepting 'SHA-1') would also make migrations safe.
Happy to open a PR for the validation/normalization if useful.
Version: otplib 13.4.1 (also reproduced on earlier 13.x)
v13 changed the
HashAlgorithmenum to lowercase ('sha1' | 'sha256' | 'sha512'). Code migrating from v12 conventions (or following RFC nomenclature) passes'SHA1'/'SHA-1'— and instead of rejecting the unknown value, the crypto plugin silently computes HMAC-SHA512:Consequence:
generateSync/verifySyncremain perfectly self-consistent, so every app-level test passes, while the RFC 6238 test vectors fail and codes from Google Authenticator / Authy / 1Password never verify. For a TOTP library this is a worst-case failure mode: it ships silently and breaks only against real devices. In plain JavaScript (no TypeScript enum checking) there is no signal at all — we shipped exactly this and every user enrollment failed with "invalid code" until we diffed the library's output against the RFC vectors.Repro against the RFC 6238 Appendix B vector (secret
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ= base32("12345678901234567890"), T=59s, expected94287082):Suggested fix: validate the algorithm string (case-insensitively, or normalize before dispatch) and throw on anything unrecognized. A thrown error surfaces the misuse on the first call; the current silent fallback produces valid-looking but interoperability-breaking codes. Given v12 documented uppercase-style options and v13 is the migration target, case-normalizing (
algorithm.toLowerCase(), and perhaps accepting'SHA-1') would also make migrations safe.Happy to open a PR for the validation/normalization if useful.