atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
99524cf9f17c87f6898b4227baea78d8cfe28ef9
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/secret.rsRust52 lines1.5 KB
1
use std::{fmt::Write as _, sync::LazyLock};
2
3
use hmac::{Hmac, Mac};
4
use sha2::Sha256;
5
6
static SECRET: LazyLock<Vec<u8>> = LazyLock::new(|| {
7
    std::env::var("JELQD_SECRET")
8
        .map(String::into_bytes)
9
        .unwrap_or_else(|_| rand::random::<[u8; 32]>().to_vec())
10
});
11
12
fn mac(message: &str) -> Hmac<Sha256> {
13
    let mut mac = Hmac::<Sha256>::new_from_slice(&SECRET).expect("hmac accepts any key length");
14
    mac.update(message.as_bytes());
15
    mac
16
}
17
18
fn hex(bytes: &[u8]) -> String {
19
    bytes.iter().fold(String::new(), |mut out, b| {
20
        let _ = write!(out, "{b:02x}");
21
        out
22
    })
23
}
24
25
fn unhex(text: &str) -> Option<Vec<u8>> {
26
    if !text.len().is_multiple_of(2) {
27
        return None;
28
    }
29
    (0..text.len())
30
        .step_by(2)
31
        .map(|i| u8::from_str_radix(text.get(i..i + 2)?, 16).ok())
32
        .collect()
33
}
34
35
pub fn sign(name: &str, value: &str) -> String {
36
    let tag = mac(&format!("{name}={value}")).finalize().into_bytes();
37
    format!("{value}.{}", hex(&tag))
38
}
39
40
pub fn verify<'a>(name: &str, signed: &'a str) -> Option<&'a str> {
41
    let (value, tag) = signed.rsplit_once('.')?;
42
    mac(&format!("{name}={value}"))
43
        .verify_slice(&unhex(tag)?)
44
        .ok()?;
45
    Some(value)
46
}
47
48
/// Mixes the server secret into a seed, so a public seed and public source cannot predict rolls offline.
49
pub fn mix_seed(seed: u64) -> u64 {
50
    let tag = mac(&format!("seed={seed}")).finalize().into_bytes();
51
    u64::from_le_bytes(tag[..8].try_into().expect("sha256 is longer than 8 bytes"))
52
}