Observable Timing Discrepancy in totp-rs
Description
totp-rs is a Rust library that permits the creation of 2FA authentification tokens per time-based one-time password (TOTP). Prior to version 1.1.0, token comparison was not constant time, and could theorically be used to guess value of an TOTP token, and thus reuse it in the same time window. The attacker would have to know the password beforehand nonetheless. Starting with patched version 1.1.0, the library uses constant-time comparison. There are currently no known workarounds.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
totp-rs Rust library before 1.1.0 used non-constant-time token comparison, enabling potential token reuse via timing side-channel.
Vulnerability
CVE-2022-29185 affects the totp-rs Rust library, versions prior to 1.1.0. The library implements time-based one-time password (TOTP) authentication as per RFC 6238. The vulnerability is that token comparison inside the check() function was not performed in constant time, meaning the comparison could leak timing information. An attacker could theorically use this side-channel to guess a valid TOTP token and reuse it within the same time window [1][2]. The fix was introduced in version 1.1.0, where the library uses constant_time_eq for comparisons [3][4].
Exploitation
To exploit this timing side-channel, an attacker would need to know the user's password beforehand (since TOTP is a second factor) and would also need to be able to observe the response times of token verification requests. The attacker would then send crafted TOTP tokens and measure the response timing to narrow down the correct value. This requires network proximity or the ability to make many time-precise requests, and assumes the attacker can intercept or observe the verification process [1].
Impact
Successful exploitation could allow an attacker to obtain a valid TOTP token for the current time window and thus bypass the second-factor authentication. This would compromise the confidentiality and integrity of the authentication mechanism, allowing session hijacking or unauthorized access to accounts protected by TOTP [1].
Mitigation
The vulnerability is fixed in totp-rs version 1.1.0, released on 2022-05-20. Users should update to this version or later [3][4]. The library now uses the constant_time_eq crate to ensure all token comparisons are performed in constant time, eliminating the timing side-channel. No workarounds are currently known [1].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
totp-rscrates.io | < 1.1.0 | 1.1.0 |
Affected products
3<1.1.0+ 1 more
- (no CPE)range: <1.1.0
- (no CPE)range: <1.1.0
Patches
11f1e1a6fe722Add constant-time token comparison and partialEq trait
3 files changed · +70 −6
Cargo.toml+2 −1 modified@@ -1,6 +1,6 @@ [package] name = "totp-rs" -version = "1.0.0" +version = "1.1.0" authors = ["Cleo Rebert <cleo.rebert@gmail.com>"] edition = "2021" readme = "README.md" @@ -25,6 +25,7 @@ sha2 = "~0.10.2" sha-1 = "~0.10.0" hmac = "~0.12.1" base32 = "~0.4" +constant_time_eq = "~0.2.1" qrcode = { version = "~0.12", optional = true } image = { version = "~0.23.14", optional = true} base64 = { version = "~0.13", optional = true }
README.md+3 −3 modified@@ -17,7 +17,7 @@ With optional feature "serde_support", library-defined types will be Deserialize Add it to your `Cargo.toml`: ```toml [dependencies] -totp-rs = "~1.0" +totp-rs = "~1.1" ``` You can then do something like: ```Rust @@ -45,7 +45,7 @@ println!("{}", token); Add it to your `Cargo.toml`: ```toml [dependencies.totp-rs] -version = "~1.0" +version = "~1.1" features = ["qr"] ``` You can then do something like: @@ -67,6 +67,6 @@ println!("{}", code); Add it to your `Cargo.toml`: ```toml [dependencies.totp-rs] -version = "~1.0" +version = "~1.1" features = ["serde_support"] ```
src/lib.rs+65 −2 modified@@ -44,6 +44,8 @@ //! # } //! ``` +use constant_time_eq::constant_time_eq; + #[cfg(feature = "serde_support")] use serde::{Deserialize, Serialize}; @@ -59,7 +61,7 @@ type HmacSha256 = hmac::Hmac<sha2::Sha256>; type HmacSha512 = hmac::Hmac<sha2::Sha512>; /// Algorithm enum holds the three standards algorithms for TOTP as per the [reference implementation](https://tools.ietf.org/html/rfc6238#appendix-A) -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] pub enum Algorithm { SHA1, @@ -117,6 +119,24 @@ pub struct TOTP<T = Vec<u8>> { pub secret: T, } +impl <T: AsRef<[u8]>> PartialEq for TOTP<T> { + fn eq(&self, other: &Self) -> bool { + if self.algorithm != other.algorithm { + return false; + } + if self.digits != other.digits { + return false; + } + if self.skew != other.skew { + return false; + } + if self.step != other.step { + return false; + } + constant_time_eq(self.secret.as_ref(), other.secret.as_ref()) + } +} + impl<T: AsRef<[u8]>> TOTP<T> { /// Will create a new instance of TOTP with given parameters. See [the doc](struct.TOTP.html#fields) for reference as to how to choose those values pub fn new(algorithm: Algorithm, digits: usize, skew: u8, step: u64, secret: T) -> TOTP<T> { @@ -154,7 +174,8 @@ impl<T: AsRef<[u8]>> TOTP<T> { let basestep = time / self.step - (self.skew as u64); for i in 0..self.skew * 2 + 1 { let step_time = (basestep + (i as u64)) * (self.step as u64); - if self.generate(step_time) == token { + + if constant_time_eq(self.generate(step_time).as_bytes(), token.as_bytes()) { return true; } } @@ -208,6 +229,48 @@ impl<T: AsRef<[u8]>> TOTP<T> { mod tests { use super::*; + #[test] + fn comparison_ok() { + let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret"); + let test = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret"); + assert_eq!(reference, test); + } + + #[test] + fn comparison_different_algo() { + let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret"); + let test = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecret"); + assert_ne!(reference, test); + } + + #[test] + fn comparison_different_digits() { + let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret"); + let test = TOTP::new(Algorithm::SHA1, 8, 1, 1, "TestSecret"); + assert_ne!(reference, test); + } + + #[test] + fn comparison_different_skew() { + let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret"); + let test = TOTP::new(Algorithm::SHA1, 6, 0, 1, "TestSecret"); + assert_ne!(reference, test); + } + + #[test] + fn comparison_different_step() { + let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret"); + let test = TOTP::new(Algorithm::SHA1, 6, 1, 30, "TestSecret"); + assert_ne!(reference, test); + } + + #[test] + fn comparison_different_secret() { + let reference = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret"); + let test = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecretL"); + assert_ne!(reference, test); + } + #[test] fn url_for_secret_matches_sha1() { let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret");
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-8vxv-2g8p-2249ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-29185ghsaADVISORY
- github.com/constantoine/totp-rs/commit/1f1e1a6fe722deb1656f483b1367ea4be978db5bghsaWEB
- github.com/constantoine/totp-rs/compare/v1.0...v1.1.0ghsaWEB
- github.com/constantoine/totp-rs/issues/13ghsax_refsource_MISCWEB
- github.com/constantoine/totp-rs/releases/tag/v1.1.0ghsax_refsource_MISCWEB
- github.com/constantoine/totp-rs/security/advisories/GHSA-8vxv-2g8p-2249ghsax_refsource_CONFIRMWEB
- rustsec.org/advisories/RUSTSEC-2022-0018.htmlghsaWEB
News mentions
0No linked articles in our index yet.