Skip to content

Commit 6267acb

Browse files
feat(api): Add simple HMAC encryption of clear content using SHA256
1 parent bb66979 commit 6267acb

File tree

8 files changed

+108
-0
lines changed

8 files changed

+108
-0
lines changed

Justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ watch:
6262
build:
6363
cargo build
6464

65+
# Build for production (optimized)
66+
gen-key:
67+
cargo run --example gen-key -p web_server
68+
6569
# Build for production (optimized)
6670
build-release:
6771
cargo build --release

crates/lib/lib-core/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ anyhow = { workspace = true }
1212
serde_with = { workspace = true }
1313
lib-util = { path = "../lib-util"}
1414
uuid = {version = "1", features = ["v4","fast-rng",]}
15+
rand = "0.9"
16+
hmac = "0.12"
17+
sha2 = "0.10"
18+
base64-url = "3"
1519

1620
[dev-dependencies]
1721
serial_test = "3"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use serde::Serialize;
2+
3+
pub type Result<T> = core::result::Result<T, Error>;
4+
5+
#[derive(Debug, Serialize)]
6+
pub enum Error {
7+
KeyFailHmac,
8+
}
9+
10+
// region: --- Error Boilerplate
11+
12+
impl core::fmt::Display for Error {
13+
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), core::fmt::Error> {
14+
write!(fmt, "{self:?}")
15+
}
16+
}
17+
18+
impl std::error::Error for Error {}
19+
20+
// endregion: --- Error Boilerplate
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// region: --- Modules
2+
3+
pub mod error;
4+
pub mod password;
5+
pub use error::{Error, Result};
6+
7+
use hmac::{Hmac, Mac};
8+
use sha2::Sha256;
9+
10+
// endregion: --- Modules
11+
12+
pub struct EncryptContent {
13+
pub content: String, // Clear content.
14+
pub salt: String, // Clear salt.
15+
}
16+
17+
// We normalise into b64 url as it is portable and a reliable character set.
18+
pub fn encrypt_into_b64u(key: &[u8], enc_content: &EncryptContent) -> Result<String> {
19+
let mut hmac = Hmac::<Sha256>::new_from_slice(key).map_err(|_| Error::KeyFailHmac)?;
20+
21+
let EncryptContent { content, salt } = enc_content;
22+
23+
hmac.update(content.as_bytes());
24+
hmac.update(salt.as_bytes());
25+
26+
let result = hmac.finalize();
27+
28+
let result_bytes = result.into_bytes();
29+
30+
Ok(base64_url::encode(&result_bytes))
31+
}
32+
33+
// region: --- Tests
34+
35+
#[cfg(test)]
36+
mod tests {
37+
use super::*;
38+
use anyhow::Result;
39+
use rand::RngCore;
40+
41+
#[test]
42+
fn test_encrypt_into_b64u_ok() -> Result<()> {
43+
let mut fx_key = [0u8; 64]; // 512 bits = 64 bytes
44+
rand::rng().fill_bytes(&mut fx_key);
45+
46+
let fx_enc_content = EncryptContent {
47+
content: "Hey there".to_string(),
48+
salt: "don't be salty".to_string(),
49+
};
50+
let result = encrypt_into_b64u(&fx_key, &fx_enc_content)?;
51+
52+
let result2 = encrypt_into_b64u(&fx_key, &fx_enc_content)?;
53+
54+
// Basic indempotency test
55+
assert_eq!(result, result2);
56+
57+
Ok(())
58+
}
59+
}
60+
61+
// endregion: --- Tests
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

crates/lib/lib-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod config;
2+
pub mod crypt;
23
pub mod ctx;
34
pub mod model;
45

crates/lib/services/web_server/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ lib-web = { path = "../../lib-web"}
1818
tokio = { workspace = true }
1919
axum = { workspace = true }
2020

21+
rand = "0.9"
2122
derive_more = { workspace = true }
2223
tracing-subscriber = { workspace = true }
2324
tracing = { workspace = true }
2425
httpc-test = "0.1.10"
26+
base64-url = "3"
2527

2628
[lints]
2729
workspace = true
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
pub type Result<T> = core::result::Result<T, Error>;
2+
pub type Error = Box<dyn std::error::Error>; // Ok for tools.
3+
4+
use rand::RngCore;
5+
6+
fn main() -> Result<()> {
7+
let mut key = [0u8; 64]; // 512 bits = 64 bytes
8+
rand::rng().fill_bytes(&mut key);
9+
println!("\nGenerated key from rand::thread_rng():\n{key:?}");
10+
11+
let b64u = base64_url::encode(&key);
12+
println!("\nKey b64u encoded:\n{}", &b64u);
13+
14+
Ok(())
15+
}

0 commit comments

Comments
 (0)