From 38c6a5c0a4df938a94452ac632da7969b629f14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ma=C4=87kowski?= Date: Sun, 1 Mar 2026 18:38:01 +0100 Subject: [PATCH] chore(crypto): use blake3 instead of sha2 This uses blake3 for static file content hashing as well as calculating the session auth hash (using blake3's native keyed mode instead of HMAC). This is done to improve the performance without sacrificing security and to reduce the number of deps when we merge #443. --- Cargo.lock | 36 +++++++++++++++++++++++++++++++++--- Cargo.toml | 4 +--- cot/Cargo.toml | 4 +--- cot/src/auth.rs | 28 ++++++++++------------------ cot/src/auth/db.rs | 14 +++++--------- cot/src/lib.rs | 3 ++- cot/src/static_files.rs | 3 +-- deny.toml | 1 + 8 files changed, 54 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96d504f2a..7050f248c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -166,6 +166,18 @@ dependencies = [ "password-hash", ] +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "askama" version = "0.15.4" @@ -523,6 +535,20 @@ dependencies = [ "digest", ] +[[package]] +name = "blake3" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.2.17", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -807,6 +833,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "convert_case" version = "0.10.0" @@ -871,6 +903,7 @@ dependencies = [ "askama", "async-trait", "axum", + "blake3", "bytes", "chrono", "chrono-tz", @@ -882,7 +915,6 @@ dependencies = [ "deadpool-redis", "derive_builder", "derive_more", - "digest", "email_address", "fake", "fantoccini", @@ -891,7 +923,6 @@ dependencies = [ "futures-util", "grass", "hex", - "hmac", "http 1.4.0", "http-body-util", "humantime", @@ -913,7 +944,6 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sha2", "sqlx", "subtle", "swagger-ui-redist", diff --git a/Cargo.toml b/Cargo.toml index b445f0bb1..7d3193af3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ async-stream = "0.3" async-trait = "0.1" axum = { version = "0.8", default-features = false } backtrace = "0.3.76" +blake3 = "1.8.3" bytes = "1.11" cargo_toml = "0.22" chrono = { version = "0.4.44", default-features = false } @@ -85,7 +86,6 @@ darling = "0.23" deadpool-redis = { version = "0.23", default-features = false } derive_builder = "0.20" derive_more = "2" -digest = "0.10" email_address = "0.2.9" fake = "4" fantoccini = "0.22" @@ -97,7 +97,6 @@ glob = "0.3" grass = { version = "0.13.4", default-features = false } heck = "0.5" hex = "0.4" -hmac = "0.12" http = "1.4" http-body = "1" http-body-util = "0.1.3" @@ -130,7 +129,6 @@ serde_html_form = { version = "0.4", default-features = false } serde_json = "1" serde_path_to_error = "0.1.20" serde_urlencoded = "0.7" -sha2 = "0.10" sqlx = { version = "0.8", default-features = false } subtle = { version = "2", default-features = false } swagger-ui-redist = { version = "0.1" } diff --git a/cot/Cargo.toml b/cot/Cargo.toml index 9e83834b1..4900c27b7 100644 --- a/cot/Cargo.toml +++ b/cot/Cargo.toml @@ -20,6 +20,7 @@ aide = { workspace = true, optional = true } askama = { workspace = true, features = ["std"] } async-trait.workspace = true axum = { workspace = true, features = ["http1", "tokio"] } +blake3.workspace = true bytes.workspace = true chrono = { workspace = true, features = ["alloc", "serde", "clock"] } chrono-tz.workspace = true @@ -30,14 +31,12 @@ cot_macros.workspace = true deadpool-redis = { workspace = true, features = ["tokio-comp", "rt_tokio_1"], optional = true } derive_builder.workspace = true derive_more = { workspace = true, features = ["debug", "deref", "display", "from"] } -digest.workspace = true email_address.workspace = true fake = { workspace = true, optional = true, features = ["derive", "chrono"] } form_urlencoded.workspace = true futures-core.workspace = true futures-util.workspace = true hex.workspace = true -hmac.workspace = true http-body-util.workspace = true http.workspace = true humantime.workspace = true @@ -55,7 +54,6 @@ sea-query = { workspace = true, optional = true } sea-query-binder = { workspace = true, features = ["with-chrono", "runtime-tokio"], optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, optional = true } -sha2.workspace = true sqlx = { workspace = true, features = ["runtime-tokio", "chrono"], optional = true } subtle = { workspace = true, features = ["std"] } swagger-ui-redist = { workspace = true, optional = true } diff --git a/cot/src/auth.rs b/cot/src/auth.rs index e6bc9accf..0ee95c4ff 100644 --- a/cot/src/auth.rs +++ b/cot/src/auth.rs @@ -180,16 +180,12 @@ pub trait User { /// use cot::auth::{SessionAuthHash, User, UserId}; /// use cot::common_types::Password; /// use cot::config::SecretKey; - /// use hmac::{Hmac, Mac}; - /// use sha2::Sha512; /// /// struct MyUser { /// id: i64, /// password: Password, /// } /// - /// type SessionAuthHmac = Hmac; - /// /// impl User for MyUser { /// fn id(&self) -> Option { /// Some(UserId::Int(self.id)) @@ -211,12 +207,12 @@ pub trait User { /// // thanks to this, the session hash is invalidated when the user changes their password /// // and the user is automatically logged out /// - /// let mut mac = SessionAuthHmac::new_from_slice(secret_key.as_bytes()) - /// .expect("HMAC can take key of any size"); - /// mac.update(self.password.as_str().as_bytes()); - /// let hmac_data = mac.finalize().into_bytes(); + /// const SESSION_AUTH_HASH_CONTEXT: &'static str = "cot.rs session auth hash v1"; + /// + /// let key = blake3::derive_key(SESSION_AUTH_HASH_CONTEXT, secret_key.as_bytes()); + /// let hash = blake3::keyed_hash(&key, self.password.as_str().as_bytes()); /// - /// Some(SessionAuthHash::new(&hmac_data)) + /// Some(SessionAuthHash::new(hash.as_slice())) /// } /// } /// ``` @@ -350,16 +346,12 @@ impl User for AnonymousUser {} /// use cot::auth::{SessionAuthHash, User, UserId}; /// use cot::common_types::Password; /// use cot::config::SecretKey; -/// use hmac::{Hmac, Mac}; -/// use sha2::Sha512; /// /// struct MyUser { /// id: i64, /// password: Password, /// } /// -/// type SessionAuthHmac = Hmac; -/// /// impl User for MyUser { /// fn id(&self) -> Option { /// Some(UserId::Int(self.id)) @@ -381,12 +373,12 @@ impl User for AnonymousUser {} /// // thanks to this, the session hash is invalidated when the user changes their password /// // and the user is automatically logged out /// -/// let mut mac = SessionAuthHmac::new_from_slice(secret_key.as_bytes()) -/// .expect("HMAC can take key of any size"); -/// mac.update(self.password.as_str().as_bytes()); -/// let hmac_data = mac.finalize().into_bytes(); +/// const SESSION_AUTH_HASH_CONTEXT: &'static str = "cot.rs session auth hash v1"; +/// +/// let key = blake3::derive_key(SESSION_AUTH_HASH_CONTEXT, secret_key.as_bytes()); +/// let hash = blake3::keyed_hash(&key, self.password.as_str().as_bytes()); /// -/// Some(SessionAuthHash::new(&hmac_data)) +/// Some(SessionAuthHash::new(hash.as_slice())) /// } /// } /// ``` diff --git a/cot/src/auth/db.rs b/cot/src/auth/db.rs index b7cf90b20..8eb6190f9 100644 --- a/cot/src/auth/db.rs +++ b/cot/src/auth/db.rs @@ -12,8 +12,6 @@ use async_trait::async_trait; // can figure out it's an autogenerated field use cot::db::Auto; use cot_macros::AdminModel; -use hmac::{Hmac, Mac}; -use sha2::Sha512; use thiserror::Error; use crate::App; @@ -345,8 +343,6 @@ impl DatabaseUser { } } -type SessionAuthHmac = Hmac; - impl User for DatabaseUser { fn id(&self) -> Option { Some(UserId::Int(self.id())) @@ -365,12 +361,12 @@ impl User for DatabaseUser { } fn session_auth_hash(&self, secret_key: &SecretKey) -> Option { - let mut mac = SessionAuthHmac::new_from_slice(secret_key.as_bytes()) - .expect("HMAC can take key of any size"); - mac.update(self.password.as_str().as_bytes()); - let hmac_data = mac.finalize().into_bytes(); + const SESSION_AUTH_HASH_CONTEXT: &str = "cot.rs session auth hash v1"; + + let key = blake3::derive_key(SESSION_AUTH_HASH_CONTEXT, secret_key.as_bytes()); + let hash = blake3::keyed_hash(&key, self.password.as_str().as_bytes()); - Some(SessionAuthHash::new(&hmac_data)) + Some(SessionAuthHash::new(hash.as_slice())) } } diff --git a/cot/src/lib.rs b/cot/src/lib.rs index cc11ba1af..22ea64d58 100644 --- a/cot/src/lib.rs +++ b/cot/src/lib.rs @@ -84,6 +84,7 @@ pub(crate) mod utils; #[cfg(feature = "openapi")] pub use aide; +pub use bytes; /// A wrapper around a handler that's used in [`Bootstrapper`]. /// /// It is returned by [`Bootstrapper::finish`]. Typically, you don't need to @@ -181,9 +182,9 @@ pub use cot_macros::e2e_test; /// ``` pub use cot_macros::main; pub use cot_macros::test; +pub use http; #[cfg(feature = "openapi")] pub use schemars; -pub use {bytes, http}; pub use crate::__private::askama::{Template, filter_fn}; pub use crate::project::{ diff --git a/cot/src/static_files.rs b/cot/src/static_files.rs index f15041061..720bc4cd3 100644 --- a/cot/src/static_files.rs +++ b/cot/src/static_files.rs @@ -13,7 +13,6 @@ use std::time::Duration; use bytes::Bytes; use cot_core::error::impl_into_cot_error; -use digest::Digest; use futures_core::ready; use http::{Request, header}; use pin_project_lite::pin_project; @@ -128,7 +127,7 @@ impl StaticFiles { #[must_use] fn file_hash(file: &StaticFile) -> String { - hex::encode(&sha2::Sha256::digest(&file.content).as_slice()[0..6]) + hex::encode(&blake3::hash(file.content.as_ref()).as_slice()[0..6]) } #[must_use] diff --git a/deny.toml b/deny.toml index 9ebe4dfe6..44e84bbbd 100644 --- a/deny.toml +++ b/deny.toml @@ -17,6 +17,7 @@ allow = [ "0BSD", "Apache-2.0 WITH LLVM-exception", "Apache-2.0", + "BSD-2-Clause", "BSD-3-Clause", "BSL-1.0", "CDLA-Permissive-2.0",