assemble_core/
cryptography.rs

1//! Cryptography functionality to aid with hashing and comparison
2
3use generic_array::GenericArray;
4use serde::de::Error;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use sha2::digest::{OutputSizeUser, Update};
7use sha2::Digest;
8use sha2::Sha256 as Sha2_Sha256;
9use std::fmt::{Display, Formatter};
10use std::io;
11use std::num::ParseIntError;
12use std::path::Path;
13use std::str::FromStr;
14use thiserror::Error;
15
16type Sha256Length = <Sha2_Sha256 as OutputSizeUser>::OutputSize;
17
18/// Number of bytes in the SHA-256 output
19pub const SHA256_BYTES: usize = 256 / 8;
20
21/// Output of sha256 hashing
22#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
23pub struct Sha256([u8; SHA256_BYTES]);
24impl Sha256 {
25    fn from(array: &GenericArray<u8, Sha256Length>) -> Self {
26        let slice = array.as_slice();
27        assert_eq!(slice.len(), SHA256_BYTES);
28        let mut output_array = [0_u8; SHA256_BYTES];
29        output_array.clone_from_slice(slice);
30        Self(output_array)
31    }
32}
33
34#[derive(Debug, Error)]
35pub enum ParseSha256Error {
36    #[error("Expected a string of 64 chars (len = {0})")]
37    WrongSize(usize),
38    #[error(transparent)]
39    ParseIntError(#[from] ParseIntError),
40}
41
42impl FromStr for Sha256 {
43    type Err = ParseSha256Error;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        if s.chars().count() != 64 {
47            return Err(ParseSha256Error::WrongSize(s.chars().count()));
48        }
49
50        let mut bytes = [0_u8; SHA256_BYTES];
51        for index in 0..SHA256_BYTES {
52            let byte_str = &s[(index * 2)..][..2];
53            let byte = u8::from_str_radix(byte_str, 16)?;
54            bytes[index] = byte;
55        }
56        Ok(Self(bytes))
57    }
58}
59
60impl Display for Sha256 {
61    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
62        write!(
63            f,
64            "{}",
65            self.0
66                .iter()
67                .map(|b| format!("{:02x}", b))
68                .collect::<String>()
69        )
70    }
71}
72
73impl Serialize for Sha256 {
74    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75    where
76        S: Serializer,
77    {
78        self.to_string().serialize(serializer)
79    }
80}
81
82impl<'de> Deserialize<'de> for Sha256 {
83    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
84    where
85        D: Deserializer<'de>,
86    {
87        let string = String::deserialize(deserializer)?;
88        Sha256::from_str(&string).map_err(D::Error::custom)
89    }
90}
91
92/// A hasher for creating Sha256 values
93pub struct Sha256Hasher {
94    hasher: Sha2_Sha256,
95}
96
97impl Sha256Hasher {
98    /// Create a new hasher
99    pub fn new() -> Self {
100        Self {
101            hasher: Sha2_Sha256::new(),
102        }
103    }
104
105    /// Finalize the hasher
106    pub fn finalize(self) -> Sha256 {
107        Sha256::from(&self.hasher.finalize())
108    }
109
110    /// Update the hasher
111    pub fn update<B: Sha256Hashable + ?Sized>(&mut self, value: &B) {
112        value.hash(self);
113    }
114}
115
116/// Types that can be hashed by a hasher
117pub trait Sha256Hashable {
118    fn hash(&self, hasher: &mut Sha256Hasher);
119}
120
121impl<B: AsRef<[u8]> + ?Sized> Sha256Hashable for B {
122    fn hash(&self, hasher: &mut Sha256Hasher) {
123        Digest::update(&mut hasher.hasher, self)
124    }
125}
126
127/// Convenience method for hashing a value into a [`Sha256`](Sha256) value
128pub fn hash_sha256<S: Sha256Hashable + ?Sized>(value: &S) -> Sha256 {
129    let mut hasher = Sha256Hasher::new();
130    hasher.update(value);
131    hasher.finalize()
132}
133
134/// Convenience method for hashing a file into a [`Sha256`](Sha256) value
135pub fn hash_file_sha256<P: AsRef<Path> + ?Sized>(value: &P) -> io::Result<Sha256> {
136    let read = std::fs::read(value)?;
137    Ok(hash_sha256(&read))
138}
139
140#[cfg(test)]
141mod tests {
142    use crate::cryptography::{hash_sha256, Sha256};
143    use std::str::FromStr;
144
145    #[test]
146    fn parse_string() {
147        let string = "design patterns is a really long and boring book";
148        let value = hash_sha256(string);
149        let value_as_string = value.to_string();
150        let str_hash = Sha256::from_str(&value_as_string).unwrap();
151        assert_eq!(value, str_hash, "value was parsed incorrectly");
152    }
153
154    #[test]
155    fn hash_string() {
156        let string1 = "Hello, World!";
157        let string2 = "Hello, World!".to_string();
158        let value1 = hash_sha256(string1);
159        let value2 = hash_sha256(&string2);
160        println!("{}", value1);
161        assert_eq!(
162            value1,
163            Sha256::from_str("dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f")
164                .unwrap()
165        );
166        assert_eq!(
167            value1, value2,
168            "Hashing of equivalent bytes should be equal"
169        );
170    }
171}