fluent-hash is a new open source Rust library which I recently published to crates.io (see here). I wanted to try creating and publishing a Rust library and so I thought I’d start with something simple and hopefully useful for other developers. It is a simple and lightweight hashing library which provides a fluent interface for creating and formatting hash values. It builds upon the foundations of the trusted cryptography library Ring (which is currently one of the most popular libraries in the Rust cryptography space), by providing a thin wrapper on top of Ring’s digest
module. See the Github link for the new project here.
In this post I will show you how to import the library into your Rust project and how to use the Hashing
, Hash
and HashContext
types for creating hashes from various data types. I also show you how to format Hash
values as bytes or hexadecimal using the provided convenience methods.
Note that this library is new and has not been audited or formally verified so use at your own risk.
What is a Fluent Interface?
From Wikipedia: ‘In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL).’
This basically means we can make a library easier to use and make the code more readible by designing the library in a way that allows the user to chain simple method calls together. As you will see in this post, fluent-hash uses this design pattern.
Summary of Types
The fluent-hash library contains the following types:
enum Hashing – The hashing algorithm. Supports selecting from the following algorithms: Sha1
, Sha256
, Sha384
, Sha512
and Sha512_256
. Implements convenience methods such as hash
, hash_vec
, hash_str
and hash_file
for producing digests from various data types. These convenience methods return an instance of the Hash
struct.
struct Hash – A hash value which holds the digest produced by one of the Hashing
algorithms. Supports formatting as a byte array, byte vector or a hexadecimal string.
struct HashContext – A context to be used for multi-step hash calculations. Useful when hashing a data structure with multiple fields or when hashing larger inputs.
See the full Rust library documentation here.
Getting Started
To get started using fluent-hash, add the library as a dependancy to your Rust Cargo.toml file.
[dependencies]
fluent-hash = "0.2.3"
Rust Imports
Let’s start by importing the required types into our project. In this example we are importing all the fluent-hash types including the Hashing
enum values.
use fluent_hash::Hashing::Sha1;
use fluent_hash::Hashing::Sha256;
use fluent_hash::Hashing::Sha384;
use fluent_hash::Hashing::Sha512;
use fluent_hash::Hashing::Sha512_256;
use fluent_hash::Hashing;
use fluent_hash::HashContext;
use fluent_hash::Hash;
Hashing a Byte Array
In this example we are using the SHA-1 hashing algorithm and hashing the bytes of the string ‘hello, world’.
let hash: Hash = Sha1.hash(b"hello, world");
Hashing a Byte Vector
Here we run the SHA-256 hashing algorithm on a vector of bytes from the string ‘hello, world’.
let hash: Hash = Sha256.hash_vec(b"hello, world".to_vec());
Hashing a String
Here we run the SHA-384 hashing algorithm on the string ‘hello, world’.
let hash: Hash = Sha384.hash_str("hello, world");
Hashing a File
In this example we create a text file containing the string ‘hello, world’ and then run it through the SHA-512 hashing algorithm.
let mut file = File::create("file.txt")?;
file.write_all(b"hello, world")?;
file.sync_all()?;
let hash: Hash = Sha512.hash_file("file.txt")?;
The hash_file
method also supports error handling.
let error: Error = match Sha512.hash_file("notfound.txt") {
Ok(_) => panic!("Expecting std::io::Error"),
Err(e) => e
};
Using the HashContext
Here we use the lower level HashContext
type to manually pass data into the hash function in chunks. This can be useful when hashing a data structure with multiple fields or when hashing larger inputs.
let mut ctx: HashContext = Sha512_256.new_context();
ctx.update(b"hello, world");
ctx.update(b"more data");
let hash = ctx.finish();
Formatting the Hash
fluent-hash supports formatting hash values as bytes or hexadecimal using the convenience methods provided on the Hash
type.
let bytes: &[u8] = hash.as_bytes();
let bytes_vec: Vec<u8> = hash.to_vec();
let hex: String = hash.to_hex();
println!("bytes = {:?}", bytes);
println!("bytes_vec = {:?}", bytes_vec);
println!("hex = {}", hex);
Fluent Interface Method Chaining
The fluent interface allows you to use method chaining to easily calculate a hash for a given algorithm returning it in the desired format. The resulting code is clean and readable.
let result = Hashing::Sha256
.hash(b"hello, world")
.to_hex();
println!("result = {}", result);
Full Sample Code
Here is the full code sample for reference.
use std::fs::File;
use std::io::Write;
use std::io::Error;
use fluent_hash::Hashing::Sha1;
use fluent_hash::Hashing::Sha256;
use fluent_hash::Hashing::Sha384;
use fluent_hash::Hashing::Sha512;
use fluent_hash::Hashing::Sha512_256;
use fluent_hash::Hashing;
use fluent_hash::HashContext;
use fluent_hash::Hash;
fn main() -> Result<(), Error> {
// Hashing a byte array
let hash: Hash = Sha1.hash(b"hello, world");
// Hashing a byte vector
let hash: Hash = Sha256.hash_vec(b"hello, world".to_vec());
// Hashing a string
let hash: Hash = Sha384.hash_str("hello, world");
// Hashing a file
let mut file = File::create("file.txt")?;
file.write_all(b"hello, world")?;
file.sync_all()?;
let hash: Hash = Sha512.hash_file("file.txt")?;
// Hashing a file supports error handling
let error: Error = match Sha512.hash_file("notfound.txt") {
Ok(_) => panic!("Expecting std::io::Error"),
Err(e) => e
};
// Using the HashContext
let mut ctx: HashContext = Sha512_256.new_context();
ctx.update(b"hello, world");
ctx.update(b"more data");
let hash = ctx.finish();
// Format the hash
let bytes: &[u8] = hash.as_bytes();
let bytes_vec: Vec<u8> = hash.to_vec();
let hex: String = hash.to_hex();
println!("bytes = {:?}", bytes);
println!("bytes_vec = {:?}", bytes_vec);
println!("hex = {}", hex);
// Fluent interface supports method chaining
let result = Hashing::Sha256
.hash(b"hello, world")
.to_hex();
println!("result = {}", result);
Ok(())
}
Conclusion
In this post I introduced the fluent-hash library and showed how you can use it to generate SHA-1 and SHA-2 hashes by using the provided Hashing
, Hash
and HashContext
types. I also explained the fluent interface design pattern and showed how fluent-hash uses method chaining to provide an easy to use interface and supports creating clean and readable code.
Here are the links to the library and documentation for reference:
https://github.com/web3-developer/fluent-hash