obfuscate/
lib.rs

1use std::fmt::Formatter;
2use std::sync::LazyLock;
3
4use derive_more::From;
5use sha2::{Digest, Sha256};
6
7use log::kv::{ToValue, Value};
8
9pub mod impls;
10
11pub trait Obfuscate {
12    fn obfuscate(&self, f: &mut Formatter<'_>) -> core::fmt::Result;
13}
14
15pub fn compute_hash(bytes: &[u8]) -> [u8; 10] {
16    /// Store a per-instantiation salt, so that obfuscated values cannot turn into pseudo-ids.
17    ///
18    /// This will be regenerated each time the library is instantiated. This will be approximately
19    /// once per client instantiation.
20    static SALT: LazyLock<[u8; 32]> = LazyLock::new(|| {
21        use rand::Rng as _;
22        let mut salt = [0; _];
23        rand::thread_rng().fill(&mut salt);
24        salt
25    });
26
27    let mut hasher = Sha256::new();
28    hasher.update(*SALT);
29    hasher.update(bytes);
30
31    let mut output = [0; 10];
32    output.copy_from_slice(&hasher.finalize().as_slice()[0..10]);
33    output
34}
35
36/// We often want logging for some values that we shouldn't know the real value of, for privacy reasons.
37///
38/// `ConversationId` is a canonical example of such an item.
39///
40/// This wrapper lets us log a partial hash of the sensitive item, so we have deterministic loggable non-sensitive
41/// aliases for all our sensitive values.
42#[derive(From)]
43pub struct Obfuscated<'a, T: Obfuscate>(&'a T);
44impl<'a, T: Obfuscate> core::fmt::Debug for Obfuscated<'a, T> {
45    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46        self.0.obfuscate(f)
47    }
48}
49
50impl<'a, T: Obfuscate> ToValue for Obfuscated<'a, T> {
51    fn to_value(&self) -> Value<'_> {
52        Value::from_debug(self)
53    }
54}