chuchi_crypto/hash/
mod.rs

1//! Contains structs used for hashing.
2//!
3//! ## Note
4//! **Do not** use this hasher for hashing password
5//! or other sensitive data since this hash does not
6//! use any salt, it is vulnerable to a rainbow table
7//! attack.
8
9#[cfg(feature = "b64")]
10use crate::error::DecodeError;
11use crate::error::TryFromError;
12
13use std::convert::{TryFrom, TryInto};
14use std::mem::ManuallyDrop;
15use std::{fmt, ptr};
16
17use blake2::{Blake2b512, Digest};
18use generic_array::{typenum::U64, GenericArray};
19
20#[cfg(feature = "b64")]
21use base64::engine::{general_purpose::URL_SAFE_NO_PAD, Engine};
22
23pub fn hash(data: impl AsRef<[u8]>) -> Hash {
24	Hasher::hash(data)
25}
26
27pub struct Hasher {
28	inner: Blake2b512,
29}
30
31impl Hasher {
32	pub fn new() -> Self {
33		Self {
34			inner: Blake2b512::new(),
35		}
36	}
37
38	pub fn update(&mut self, data: impl AsRef<[u8]>) {
39		self.inner.update(data);
40	}
41
42	pub fn finalize(self) -> Hash {
43		let arr = self.inner.finalize();
44		Hash {
45			bytes: convert_generic_array(arr),
46		}
47	}
48
49	pub fn hash(data: impl AsRef<[u8]>) -> Hash {
50		let mut hasher = Hasher::new();
51		hasher.update(data);
52		hasher.finalize()
53	}
54}
55
56fn convert_generic_array<T>(arr: GenericArray<T, U64>) -> [T; 64] {
57	// safe because both have the same memory layout
58	// and generic array does it
59	unsafe {
60		// see https://docs.rs/generic-array/0.14.4/src/generic_array/lib.rs.html#636
61		let a = ManuallyDrop::new(arr);
62		ptr::read(&*a as *const GenericArray<T, U64> as *const [T; 64])
63	}
64}
65
66#[derive(Clone, PartialEq, Eq)]
67pub struct Hash {
68	bytes: [u8; 64],
69}
70
71impl Hash {
72	pub const LEN: usize = 64;
73
74	/// ## Panics
75	/// if the slice is not 64 bytes long.
76	pub fn from_slice(slice: &[u8]) -> Self {
77		slice.try_into().unwrap()
78	}
79
80	pub fn to_bytes(&self) -> [u8; 64] {
81		self.bytes
82	}
83}
84
85#[cfg(not(feature = "b64"))]
86impl fmt::Debug for Hash {
87	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88		f.debug_tuple("Hash").field(&self.as_ref()).finish()
89	}
90}
91
92#[cfg(feature = "b64")]
93impl fmt::Debug for Hash {
94	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95		f.debug_tuple("Hash").field(&self.to_string()).finish()
96	}
97}
98
99#[cfg(feature = "b64")]
100impl fmt::Display for Hash {
101	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102		base64::display::Base64Display::new(self.as_ref(), &URL_SAFE_NO_PAD)
103			.fmt(f)
104	}
105}
106
107impl From<[u8; 64]> for Hash {
108	fn from(bytes: [u8; 64]) -> Self {
109		Self { bytes }
110	}
111}
112
113impl TryFrom<&[u8]> for Hash {
114	type Error = TryFromError;
115
116	fn try_from(v: &[u8]) -> Result<Self, Self::Error> {
117		<[u8; 64]>::try_from(v)
118			.map_err(TryFromError::from_any)
119			.map(Self::from)
120	}
121}
122
123#[cfg(feature = "b64")]
124impl crate::FromStr for Hash {
125	type Err = DecodeError;
126
127	fn from_str(s: &str) -> Result<Self, Self::Err> {
128		if s.len() != crate::calculate_b64_len(Self::LEN) {
129			return Err(DecodeError::InvalidLength);
130		}
131
132		let mut bytes = [0u8; Self::LEN];
133		URL_SAFE_NO_PAD
134			.decode_slice_unchecked(s, &mut bytes)
135			.map_err(DecodeError::inv_bytes)
136			.and_then(|_| {
137				Self::try_from(bytes.as_ref()).map_err(DecodeError::inv_bytes)
138			})
139	}
140}
141
142impl AsRef<[u8]> for Hash {
143	fn as_ref(&self) -> &[u8] {
144		&self.bytes
145	}
146}
147
148#[cfg(all(feature = "b64", feature = "serde"))]
149mod impl_serde {
150
151	use super::*;
152
153	use std::borrow::Cow;
154	use std::str::FromStr;
155
156	use _serde::de::Error;
157	use _serde::{Deserialize, Deserializer, Serialize, Serializer};
158
159	impl Serialize for Hash {
160		fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161		where
162			S: Serializer,
163		{
164			serializer.collect_str(&self)
165		}
166	}
167
168	impl<'de> Deserialize<'de> for Hash {
169		fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170		where
171			D: Deserializer<'de>,
172		{
173			let s: Cow<'_, str> = Deserialize::deserialize(deserializer)?;
174			Self::from_str(s.as_ref()).map_err(D::Error::custom)
175		}
176	}
177}
178
179#[cfg(test)]
180mod tests {
181
182	use super::*;
183
184	#[test]
185	fn hash_something() {
186		let bytes: Vec<u8> = (0..=255).collect();
187
188		let hash = Hasher::hash(bytes);
189
190		let hash_bytes = [
191			30, 204, 137, 111, 52, 211, 249, 202, 196, 132, 199, 63, 117, 246,
192			165, 251, 88, 238, 103, 132, 190, 65, 179, 95, 70, 6, 123, 156,
193			101, 198, 58, 103, 148, 211, 215, 68, 17, 44, 101, 63, 115, 221,
194			125, 235, 102, 102, 32, 76, 90, 155, 250, 91, 70, 8, 31, 193, 15,
195			219, 231, 136, 79, 165, 203, 248,
196		];
197		assert_eq!(hash.to_bytes(), hash_bytes);
198	}
199
200	#[test]
201	#[cfg(feature = "b64")]
202	fn hash_b64() {
203		let bytes: Vec<u8> = (0..=255).collect();
204
205		let hash = Hasher::hash(bytes);
206
207		assert_eq!(
208			hash.to_string(),
209			"HsyJbzTT-crEhMc_dfal-1juZ4S-QbNfRgZ7nGXGOme\
210			U09dEESxlP3PdfetmZiBMWpv6W0YIH8EP2-eIT6XL-A"
211		);
212	}
213}