Returning a hash as String rust [duplicate]
Asked Answered
N

2

6

I want to print the output from a hashing function to stdout. I use the groestl hash function but i suppose it works the same as sha or others. Doing it like this prints the hash as it should:

fn create_hash<D: Digest>(msg: &str, mut hasher: D) -> GenericArray<u8, D::OutputSize> {
  hasher.update(msg);
  hasher.finalize()
}

fn main() {
  let hasher = Groestl256::default();
  let res = create_hash("asdfasdf", hasher);
  println!("{:x}", res);
}

Outputs: db102d374ae45b130ae49e91dcc7b648b86ba1a0415a32dcce7806d46f316460

Now i want to do a match to make the other algorithms(Groestl512,...) also usable.

    let dig = match algorithm {
        HashTypes::groestl224 => {
            create_hash(message.as_ref().unwrap(), Groestl224::default())
        }
        HashTypes::groestl256 => {
            create_hash(message.as_ref().unwrap(), Groestl256::default())
        }
        HashTypes::groestl384 => {
            create_hash(message.as_ref().unwrap(), Groestl384::default())
        }
        HashTypes::groestl512 => {
            create_hash(message.as_ref().unwrap(), Groestl512::default())
        }
    };
    let res = HashGroestl::create_hash("asdfasdf", Groestl256::default());
    
    println!("Result: {:x}", res);

This results in the match arms having incompatible types due to different sizes of the returning array. I tried to bypass this by returning a String instead of the GenericArray.

When i want to return a String from create_hash with format!("{:x}", hasher.finalize()) results in following error:

cannot add `<D as groestl::Digest>::OutputSize` to `<D as groestl::Digest>::OutputSize`
the trait `std::ops::Add` is not implemented for `<D as groestl::Digest>::OutputSize`
required because of the requirements on the impl of `std::fmt::LowerHex` for `aes::cipher::generic_array::GenericArray<u8, <D as groestl::Digest>::OutputSize>`
required by `std::fmt::LowerHex::fmt`

I also tried around with converting the array to a Vec or with .as_slice().

So how can i either return a String like above or make the match arms compatible?

EDIT: Okay, just found the solution in Generic function to compute a hash (digest::Digest trait) and get back a String to return as a string. But the other part with making the match arms compatible still interests me, so if someone has an answer for that, you're welcome!

Neglect answered 1/4, 2021 at 13:6 Comment(0)
S
5

When i want to return a String from create_hash with format!("{:x}", hasher.finalize()) results in [...] error

The problem is that create_hash is generic over D, which is only required to implement Digest. While concrete implementation of Digest you are supplying it also satisfy the LowerHex trait needed for format!("{:x}") to accept them, the signature of create_hash doesn't reflect that.

To fix it, you should use an additional constraint on digest's output, like this:

fn create_hash<D>(msg: &str, mut hasher: D) -> String
where
    D: Digest,
    digest::Output<D>: std::fmt::LowerHex,
{
    hasher.update(msg);
    format!("{:x}", hasher.finalize())
}

Playground

To make the different match arms compatible, you must introduce indirection and therefore allocation. The above example already does it with String, so you could have your match arms include format!("{:x}", create_hash(...)) and they'd be compatible automatically. But if you don't like that, you can also make create_hash return Vec<u8> instead:

fn create_hash<D: Digest>(msg: &str, mut hasher: D) -> Vec<u8> {
    hasher.update(msg);
    hasher.finalize().as_slice().to_vec()
}

That will make the match arms compatible, but {:x} will no longer work. You can also make create_hash() return a boxed object that implements LowerHex:

fn create_hash<D>(msg: &str, mut hasher: D) -> Box<dyn LowerHex>
where
    D: Digest,
    digest::Output<D>: LowerHex,
{
    hasher.update(msg);
    Box::new(hasher.finalize())
}

The match arms then work out:

fn main() {
    let res = if true {
        create_hash("asdfasdf", Sha256::default())
    } else {
        create_hash("asdfasdf", Sha512::default())
    };
    println!("{:x}", res.as_ref());
}

Playground

Swell answered 1/4, 2021 at 13:32 Comment(0)
C
0

The problem is that you have different output types in each variant you matching:

let dig = match algorithm {
        HashTypes::groestl224 => {
            create_hash(message.as_ref().unwrap(), Groestl224::default()) // type #1
        }
        HashTypes::groestl256 => {
            create_hash(message.as_ref().unwrap(), Groestl256::default()) // type #2
        }
        // etc
    };

So your dig variable can't have any exact type.

You should somehow convert every matched type to exactly the same type. The simplest way is to use:

create_hash(message.as_ref().unwrap(), Groestl256::default())
   .to_slice()
   .to_owned()

After that you will have a Vec<u8> which you may own and do what you want. But it uses allocation.

Or you can use dynamic dispatching:

fn create_hash<D: Digest + 'static>(msg: &str, mut hasher: D) -> Box<dyn std::ops::Deref<Target = [u8]>> {
  hasher.update(msg);
  let digest = hasher.finalize();
  Box::new(digest)
}

The best, the most complicated and the most Rust-way way is to define some enum which will choose it's variant based on given digest.

Crosscheck answered 1/4, 2021 at 13:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.