fluent_hash/
lib.rs

1// Copyright 2023 Web3 Developer @ Web3Developer.io
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A lightweight library which provides a fluent interface for generating SHA-1 and SHA-2 digests.
16
17use std::fs::File;
18use std::io::{BufRead, BufReader, Error};
19use ring::digest as r_digest;
20
21
22/// The hashing algorithm. SHA-1 and SHA-2 algorithms are supported.
23#[derive(Debug, Eq, PartialEq)]
24pub enum Hashing {
25    /// The SHA-1 hash algorithm. Should generally be avoided unless working with legacy software.
26    Sha1,
27    /// The SHA-2 256 bit hash algorithm.
28    Sha256,
29    /// The SHA-2 384 bit hash algorithm.
30    Sha384,
31    /// The SHA-2 512 bit hash algorithm.
32    Sha512,
33    /// The SHA-2 512_256 bit hash algorithm. Uses SHA-512 but returns only 256 bits.
34    Sha512_256,
35}
36
37impl Hashing {
38
39    /// Creates a new instance of a `HashContext` to be used with the selected `Hashing` algorithm.
40    pub fn new_context(&self) -> HashContext {
41        match self {
42            Self::Sha1 => HashContext(r_digest::Context::new(
43                &r_digest::SHA1_FOR_LEGACY_USE_ONLY)),
44            Self::Sha256 => HashContext(r_digest::Context::new(
45                &r_digest::SHA256)),
46            Self::Sha384 => HashContext(r_digest::Context::new(
47                &r_digest::SHA384)),
48            Self::Sha512 => HashContext(r_digest::Context::new(
49                &r_digest::SHA512)),
50            Self::Sha512_256 => HashContext(r_digest::Context::new(
51                &r_digest::SHA512_256)),
52        }
53    }
54
55    /// Returns a `Hash` of the given byte array `data`.
56    pub fn hash(&self, data: &[u8]) -> Hash {
57        let mut ctx = self.new_context();
58        ctx.update(data);
59        ctx.finish()
60    }
61
62    /// Returns a `Hash` of the given byte vector `data`.
63    #[inline]
64    pub fn hash_vec(&self, data: Vec<u8>) -> Hash {
65        self.hash(data.as_ref())
66    }
67
68    /// Returns a `Hash` of the given string `data`.
69    #[inline]
70    pub fn hash_str(&self, data: &str) -> Hash {
71        self.hash(data.as_ref())
72    }
73
74    /// Returns a `Result<Hash, Error>` containing the `Hash` of the file located at the given path on success,
75    /// otherwise returns a `std::io::Error` if the file doesn't exist or can't be opened in read-only mode.
76    pub fn hash_file(&self, path: &str) -> Result<Hash, Error> {
77        let file = File::open(path)?;
78        let reader = BufReader::new(file);
79
80        let mut ctx = self.new_context();
81        for line in reader.lines() {
82            ctx.update(line.unwrap().as_bytes());
83        }
84
85        Ok(ctx.finish())
86    }
87
88}
89
90
91
92/// A context to be used for multi-step hash calculations.
93/// Useful when hashing a data structure with multiple fields or when hashing larger inputs.
94#[derive(Clone)]
95pub struct HashContext(r_digest::Context);
96
97impl HashContext {
98
99    /// Updates the `HashContext` with the given byte array `data`.
100    #[inline]
101    pub fn update(&mut self, data: &[u8]) {
102        self.0.update(data);
103    }
104
105    /// Returns the `Hash` from the data in the `HashContext`.
106    /// Consumes the `HashContext` so it cannot reused after calling finish.
107    #[inline]
108    pub fn finish(self) -> Hash {
109        Hash(self.0.finish())
110    }
111
112}
113
114
115/// A hash value which holds the message digest produced by one of the `Hashing` algorithms.
116/// Supports formatting as a byte array, byte vector or a hexadecimal string.
117#[derive(Clone, Copy)]
118pub struct Hash(r_digest::Digest);
119
120impl Hash {
121
122    /// Returns a reference to the hash value bytes.
123    #[inline]
124    pub fn as_bytes(&self) -> &[u8] {
125        self.0.as_ref()
126    }
127
128    /// Returns the hash value as a vector of bytes.
129    #[inline]
130    pub fn to_vec(&self) -> Vec<u8> {
131        self.as_bytes().to_vec()
132    }
133
134    /// Returns the hash value as a hexadecimal string.
135    #[inline]
136    pub fn to_hex(&self) -> String {
137        hex::encode(self.as_bytes())
138    }
139
140}
141
142
143// Tests
144#[cfg(test)]
145mod tests {
146
147    use super::*;
148    use crate::Hashing::{Sha1, Sha256, Sha384, Sha512, Sha512_256};
149    use std::fs::File;
150    use std::io::Write;
151    use hex::ToHex;
152
153    const DATA_TO_DIGEST: &[u8] = b"Hello, World!";
154    const FILE_NAME: &str = "testfile.txt";
155
156    fn create_test_file() {
157        // prepare test file
158        let mut file = File::create("testfile.txt").unwrap();
159        file.write_all(DATA_TO_DIGEST).unwrap();
160        file.sync_all().unwrap();
161    }
162
163    #[test]
164    fn sha256_context() {
165        let mut r_ctx = r_digest::Context::new(&r_digest::SHA256);
166        r_ctx.update(DATA_TO_DIGEST);
167        let expected = r_ctx.finish();
168
169        let mut ctx = Sha256.new_context();
170        ctx.update(DATA_TO_DIGEST);
171        let result = ctx.finish();
172
173        assert_eq!(result.as_bytes(), expected.as_ref());
174        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
175        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
176    }
177
178    #[test]
179    fn sha1_digest() {
180        let expected = r_digest::digest(&r_digest::SHA1_FOR_LEGACY_USE_ONLY, DATA_TO_DIGEST);
181        let result = Sha1.hash(DATA_TO_DIGEST);
182
183        assert_eq!(result.as_bytes(), expected.as_ref());
184        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
185        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
186    }
187
188    #[test]
189    fn sha384_digest() {
190        let expected = r_digest::digest(&r_digest::SHA384, DATA_TO_DIGEST);
191        let result = Sha384.hash(DATA_TO_DIGEST);
192
193        assert_eq!(result.as_bytes(), expected.as_ref());
194        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
195        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
196    }
197
198    #[test]
199    fn sha512_digest() {
200        let expected = r_digest::digest(&r_digest::SHA512, DATA_TO_DIGEST);
201        let result = Sha512.hash(DATA_TO_DIGEST);
202
203        assert_eq!(result.as_bytes(), expected.as_ref());
204        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
205        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
206    }
207
208    #[test]
209    fn sha512_256_digest() {
210        let expected = r_digest::digest(&r_digest::SHA512_256, DATA_TO_DIGEST);
211        let result = Sha512_256.hash(DATA_TO_DIGEST);
212
213        assert_eq!(result.as_bytes(), expected.as_ref());
214        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
215        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
216    }
217
218    #[test]
219    fn sha256_digest() {
220        let expected = r_digest::digest(&r_digest::SHA256, DATA_TO_DIGEST);
221        let result = Sha256.hash(DATA_TO_DIGEST);
222
223        assert_eq!(result.as_bytes(), expected.as_ref());
224        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
225        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
226    }
227
228    #[test]
229    fn sha256_digest_vec() {
230        let expected = r_digest::digest(&r_digest::SHA256, DATA_TO_DIGEST);
231        let result = Sha256.hash_vec(DATA_TO_DIGEST.to_vec());
232
233        assert_eq!(result.as_bytes(), expected.as_ref());
234        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
235        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
236    }
237
238    #[test]
239    fn sha256_digest_str() {
240        let expected = r_digest::digest(&r_digest::SHA256, DATA_TO_DIGEST);
241        let result = Sha256.hash_str(&String::from_utf8(DATA_TO_DIGEST.to_vec()).unwrap());
242
243        assert_eq!(result.as_bytes(), expected.as_ref());
244        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
245        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
246    }
247
248    #[test]
249    fn sha256_digest_file() {
250        create_test_file();
251
252        let expected = r_digest::digest(&r_digest::SHA256, DATA_TO_DIGEST);
253        let result = Sha256.hash_file(FILE_NAME).unwrap();
254
255        assert_eq!(result.as_bytes(), expected.as_ref());
256        assert_eq!(result.to_vec(), expected.as_ref().to_vec());
257        assert_eq!(result.to_hex(), expected.encode_hex::<String>());
258    }
259
260    #[test]
261    //#[should_panic]
262    fn sha256_digest_file_error() {
263        let result = Sha256.hash_file("notfound.txt");
264        let e = match result {
265            Ok(_) => panic!("Test should fail"),
266            Err(e) => e
267        };
268        assert_eq!(e.to_string(), "No such file or directory (os error 2)");
269    }
270
271}