1#[inline(never)]
26pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
27 let mut diff: u8 = 0;
28 diff |= u8::from(a.len() != b.len());
30 let max_len = a.len().max(b.len());
33 for i in 0..max_len {
34 let av = a.get(i).copied().unwrap_or(0);
35 let bv = b.get(i).copied().unwrap_or(0);
36 diff |= av ^ bv;
37 core::hint::black_box(&mut diff);
39 }
40 diff == 0
41}
42
43#[inline(never)]
52pub fn constant_time_eq_u32(a: u32, b: u32) -> bool {
53 let mut diff = a ^ b;
55 core::hint::black_box(&mut diff);
56 diff == 0
57}
58
59#[inline(never)]
68pub fn constant_time_eq_u64(a: u64, b: u64) -> bool {
69 let mut diff = a ^ b;
70 core::hint::black_box(&mut diff);
71 diff == 0
72}
73
74#[inline(never)]
83pub fn constant_time_eq_u128(a: u128, b: u128) -> bool {
84 let mut diff = a ^ b;
85 core::hint::black_box(&mut diff);
86 diff == 0
87}
88
89#[inline(never)]
100pub fn constant_time_all_pass(checks: &[bool]) -> bool {
101 let mut result: u8 = 0;
102 for &c in checks {
103 result |= u8::from(!c);
105 core::hint::black_box(&mut result);
106 }
107 result == 0
108}
109
110#[inline(never)]
124pub fn constant_time_eq_ascii_lower(a: &[u8], lower_b: &[u8]) -> bool {
125 let mut diff: u8 = 0;
126 diff |= u8::from(a.len() != lower_b.len());
128 let max_len = a.len().max(lower_b.len());
129 for i in 0..max_len {
130 diff |= a
131 .get(i)
132 .copied()
133 .unwrap_or(0)
134 .to_ascii_lowercase()
135 ^ lower_b.get(i).copied().unwrap_or(0);
136 core::hint::black_box(&mut diff);
137 }
138 diff == 0
139}
140
141#[inline(never)]
153pub fn constant_time_eq_case_insensitive(a: &[u8], b: &[u8]) -> bool {
154 let mut diff: u8 = 0;
155 diff |= u8::from(a.len() != b.len());
157 let max_len = a.len().max(b.len());
158 for i in 0..max_len {
159 diff |= a
160 .get(i)
161 .copied()
162 .unwrap_or(0)
163 .to_ascii_lowercase()
164 ^ b.get(i).copied().unwrap_or(0).to_ascii_lowercase();
165 core::hint::black_box(&mut diff);
166 }
167 diff == 0
168}
169
170#[inline(never)]
187pub fn constant_time_contains(haystack: &[u8], needle: &[u8]) -> bool {
188 let last = haystack.len().checked_sub(needle.len()).unwrap_or(0);
193 let too_short = haystack.len() < needle.len();
194 let mut found: u8 = 0;
195 for i in 0..=last {
196 let mut diff: u8 = 0;
198 for j in 0..needle.len() {
199 diff |= haystack.get(i + j).copied().unwrap_or(0)
201 ^ needle.get(j).copied().unwrap_or(0);
202 core::hint::black_box(&mut diff);
203 }
204 found |= u8::from(diff == 0);
205 core::hint::black_box(&mut found);
206 }
207 let result = u8::from(!too_short) & found;
209 result != 0
210}
211
212#[inline(never)]
225pub fn constant_time_starts_with(haystack: &[u8], prefix: &[u8]) -> bool {
226 let len_ok = haystack.len() >= prefix.len();
228 let mut diff: u8 = 0;
229 for (i, &p) in prefix.iter().enumerate() {
230 let b = haystack.get(i).copied().unwrap_or(0);
232 diff |= b ^ p;
233 core::hint::black_box(&mut diff);
234 }
235 let result = u8::from(len_ok) & u8::from(diff == 0);
237 result == 1
238}
239
240#[inline(never)]
255pub fn constant_time_contains_case_insensitive(haystack: &[u8], needle: &[u8]) -> bool {
256 let last = haystack.len().checked_sub(needle.len()).unwrap_or(0);
260 let too_short = haystack.len() < needle.len();
261 let mut found: u8 = 0;
262 for i in 0..=last {
263 let mut diff: u8 = 0;
265 for j in 0..needle.len() {
266 diff |= haystack
268 .get(i + j)
269 .copied()
270 .unwrap_or(0)
271 .to_ascii_lowercase()
272 ^ needle.get(j).copied().unwrap_or(0).to_ascii_lowercase();
273 core::hint::black_box(&mut diff);
274 }
275 found |= u8::from(diff == 0);
276 core::hint::black_box(&mut found);
277 }
278 let result = u8::from(!too_short) & found;
280 result != 0
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_constant_time_starts_with_basic() {
289 assert!(constant_time_starts_with(b"hello world", b"hello"));
290 assert!(constant_time_starts_with(b"hello", b"hello"));
291 assert!(constant_time_starts_with(b"hello", b""));
292 assert!(!constant_time_starts_with(b"hello world", b"world"));
293 assert!(!constant_time_starts_with(b"hi", b"hello"));
294 assert!(!constant_time_starts_with(b"hellx", b"hello"));
295 }
296
297 #[test]
298 fn test_constant_time_contains_case_insensitive_basic() {
299 assert!(constant_time_contains_case_insensitive(b"Hello World", b"world"));
300 assert!(constant_time_contains_case_insensitive(b"HELLO WORLD", b"hello"));
301 assert!(constant_time_contains_case_insensitive(b"abcdef", b""));
302 assert!(!constant_time_contains_case_insensitive(b"Hello", b"word"));
303 assert!(!constant_time_contains_case_insensitive(b"ab", b"ABC"));
304 assert!(constant_time_contains_case_insensitive(b"a\xFFb", b"A\xFF"));
306 }
307
308 #[test]
309 fn test_constant_time_eq_equal() {
310 let a = b"secret_key_123";
311 let b = b"secret_key_123";
312 assert!(constant_time_eq(a, b));
313 }
314
315 #[test]
316 fn test_constant_time_eq_different() {
317 let a = b"secret_key_123";
318 let b = b"secret_key_124";
319 assert!(!constant_time_eq(a, b));
320 }
321
322 #[test]
323 fn test_constant_time_eq_different_length() {
324 let a = b"short";
325 let b = b"longer";
326 assert!(!constant_time_eq(a, b));
327 }
328
329 #[test]
330 fn test_constant_time_eq_empty() {
331 let a: &[u8] = b"";
332 let b: &[u8] = b"";
333 assert!(constant_time_eq(a, b));
334 }
335
336 #[test]
337 fn test_constant_time_eq_u32() {
338 assert!(constant_time_eq_u32(42, 42));
339 assert!(!constant_time_eq_u32(42, 43));
340 assert!(constant_time_eq_u32(0, 0));
341 assert!(constant_time_eq_u32(u32::MAX, u32::MAX));
342 }
343
344 #[test]
345 fn test_constant_time_eq_u64() {
346 assert!(constant_time_eq_u64(123456789, 123456789));
347 assert!(!constant_time_eq_u64(123456789, 123456790));
348 assert!(constant_time_eq_u64(0, 0));
349 assert!(constant_time_eq_u64(u64::MAX, u64::MAX));
350 }
351
352 #[test]
353 fn test_constant_time_eq_u128() {
354 assert!(constant_time_eq_u128(1, 1));
355 assert!(!constant_time_eq_u128(1, 2));
356 assert!(constant_time_eq_u128(u128::MAX, u128::MAX));
357 }
358
359 #[test]
360 fn test_constant_time_all_pass() {
361 assert!(constant_time_all_pass(&[true, true, true]));
362 assert!(!constant_time_all_pass(&[true, false, true]));
363 assert!(!constant_time_all_pass(&[false, false, false]));
364 assert!(constant_time_all_pass(&[]));
365 }
366
367 #[test]
368 fn test_constant_time_eq_all_byte_values() {
369 for byte in 0u16..=255 {
370 let val = byte as u8;
371 let a = [val; 32];
372 let b = [val; 32];
373 assert!(constant_time_eq(&a, &b));
374
375 let mut c = [val; 32];
376 if val < 255 {
377 c[16] = val + 1;
378 assert!(!constant_time_eq(&a, &c));
379 }
380 }
381 }
382
383 #[test]
386 fn test_constant_time_eq_case_insensitive_equal() {
387 assert!(constant_time_eq_case_insensitive(b"Content-Type", b"content-type"));
388 assert!(constant_time_eq_case_insensitive(b"CONTENT-TYPE", b"content-type"));
389 assert!(constant_time_eq_case_insensitive(b"content-type", b"content-type"));
390 assert!(constant_time_eq_case_insensitive(b"AbCdEf", b"aBcDeF"));
391 }
392
393 #[test]
394 fn test_constant_time_eq_case_insensitive_not_equal() {
395 assert!(!constant_time_eq_case_insensitive(b"content-type", b"content-length"));
396 assert!(!constant_time_eq_case_insensitive(b"abc", b"abd"));
397 assert!(!constant_time_eq_case_insensitive(b"abc", b"abcd"));
399 assert!(!constant_time_eq_case_insensitive(b"", b"a"));
400 }
401
402 #[test]
403 fn test_constant_time_eq_case_insensitive_non_ascii() {
404 assert!(constant_time_eq_case_insensitive(b"a\xFFb", b"A\xFFB"));
406 assert!(!constant_time_eq_case_insensitive(b"a\xFFb", b"A\xFEb"));
407 assert!(constant_time_eq_case_insensitive(b"", b""));
409 }
410
411 #[test]
414 fn test_constant_time_contains_basic() {
415 assert!(constant_time_contains(b"hello world", b"world"));
416 assert!(constant_time_contains(b"hello world", b"hello"));
417 assert!(constant_time_contains(b"hello world", b"o w"));
418 assert!(!constant_time_contains(b"hello world", b"word"));
419 }
420
421 #[test]
422 fn test_constant_time_contains_boundaries() {
423 assert!(constant_time_contains(b"abc", b"abc"));
425 assert!(!constant_time_contains(b"abc", b"abd"));
426 assert!(!constant_time_contains(b"ab", b"abc"));
428 assert!(constant_time_contains(b"ab", b""));
430 assert!(constant_time_contains(b"", b""));
431 assert!(!constant_time_contains(b"", b"a"));
432 }
433
434 #[test]
435 fn test_constant_time_contains_all_positions() {
436 assert!(constant_time_contains(b"aaab", b"ab"));
438 assert!(constant_time_contains(b"xyz", b"z"));
440 assert!(!constant_time_contains(b"xyz", b"w"));
441 }
442
443 #[test]
446 fn test_constant_time_eq_different_length_content_related() {
447 assert!(!constant_time_eq(b"secret", b"secret_key"));
449 assert!(!constant_time_eq(b"secret_key", b"secret"));
450 assert!(!constant_time_eq(b"ab", b"abab"));
452 assert!(!constant_time_eq(b"abab", b"ab"));
453 assert!(constant_time_eq(b"", b""));
455 assert!(!constant_time_eq(b"", b"a"));
456 assert!(!constant_time_eq(b"a", b""));
457 }
458
459 #[test]
460 fn test_constant_time_eq_ascii_lower_different_length_content_related() {
461 assert!(!constant_time_eq_ascii_lower(b"ABC", b"abcd"));
462 assert!(!constant_time_eq_ascii_lower(b"abcd", b"ABC"));
463 assert!(!constant_time_eq_ascii_lower(b"abc", b"abcD"));
464 assert!(!constant_time_eq_ascii_lower(b"", b"a"));
465 assert!(!constant_time_eq_ascii_lower(b"a", b""));
466 }
467
468 #[test]
469 fn test_constant_time_eq_case_insensitive_different_length_content_related() {
470 assert!(!constant_time_eq_case_insensitive(b"AbC", b"aBcD"));
471 assert!(!constant_time_eq_case_insensitive(b"aBcD", b"AbC"));
472 assert!(!constant_time_eq_case_insensitive(b"AbC", b"aBcCd"));
473 assert!(!constant_time_eq_case_insensitive(b"", b"a"));
474 assert!(!constant_time_eq_case_insensitive(b"a", b""));
475 }
476
477 #[test]
478 fn test_constant_time_contains_needle_longer_than_haystack() {
479 assert!(!constant_time_contains(b"abc", b"abcd"));
481 assert!(!constant_time_contains(b"abc", b"abcabc"));
482 assert!(!constant_time_contains(b"", b"a"));
483 assert!(!constant_time_contains(b"a", b"ab"));
484 assert!(constant_time_contains(b"", b""));
486 assert!(constant_time_contains(b"abc", b""));
487 }
488
489 #[test]
490 fn test_constant_time_contains_case_insensitive_needle_longer() {
491 assert!(!constant_time_contains_case_insensitive(b"ABC", b"AbCd"));
492 assert!(!constant_time_contains_case_insensitive(b"abc", b"abcabc"));
493 assert!(!constant_time_contains_case_insensitive(b"", b"A"));
494 assert!(!constant_time_contains_case_insensitive(b"a", b"AB"));
495 assert!(constant_time_contains_case_insensitive(b"", b""));
496 assert!(constant_time_contains_case_insensitive(b"ABC", b""));
497 }
498
499 #[test]
502 fn test_timing_constant_eq_different_length() {
503 let a_short = vec![0x5Au8; 64];
506 let b_short = vec![0x5Bu8; 64];
507 let a_long_fixed = vec![0x5Au8; 4096];
508 let b_long_fixed = vec![0x5Bu8; 4096];
509 let long_prefix = {
511 let mut v = a_short.clone();
512 v.resize(4096, 0x5Au8);
513 v
514 };
515
516 let iters = 200_000u32;
517
518 for _ in 0..10_000 {
520 core::hint::black_box(constant_time_eq(&a_short, &b_short));
521 }
522
523 let t0 = std::time::Instant::now();
524 for _ in 0..iters {
525 core::hint::black_box(constant_time_eq(&a_short, &b_short));
526 }
527 let short_elapsed = t0.elapsed();
528
529 let t1 = std::time::Instant::now();
530 for _ in 0..iters {
531 core::hint::black_box(constant_time_eq(&a_long_fixed, &b_long_fixed));
532 }
533 let long_fixed_elapsed = t1.elapsed();
535 assert!(
536 long_fixed_elapsed >= short_elapsed,
537 "较长输入应花费不少于较短输入的耗时"
538 );
539
540 let t2 = std::time::Instant::now();
542 for _ in 0..iters {
543 core::hint::black_box(constant_time_eq(&long_prefix, &a_long_fixed));
544 }
545 let related_elapsed = t2.elapsed();
546
547 let lower = long_fixed_elapsed.as_secs_f64() * 0.7;
550 let upper = long_fixed_elapsed.as_secs_f64() * 1.3;
551 let got = related_elapsed.as_secs_f64();
552 assert!(
553 got >= lower && got <= upper,
554 "相同长度下比较耗时应基本恒定:related={got:.6}s, fixed={:.6}s",
555 long_fixed_elapsed.as_secs_f64()
556 );
557 let _ = short_elapsed;
559 }
560}