use std::io::{ Write, Result as IoResult };
use sha1::Sha1;
pub struct Sha1Writer<W> {
writer: W,
hasher: Sha1,
impl<W> Sha1Writer<W> {
fn new(writer: W) -> Self {
Sha1Writer { writer, hasher: Sha1::new() }
fn into_digest(self) -> impl AsRef<[u8]> {
self.hasher.finalize()
impl<W: Write> Write for Sha1Writer<W> {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
self.hasher.update(buf);
self.writer.write(buf)
fn flush(&mut self) -> IoResult<()> {
self.writer.flush()
You might want to implement all the default methods of io::Write
as well, for the sake of performance.
Furthermore, if you can choose the hashing algorithm, don't choose SHA-1 – it's not considered secure anymore. Try at least SHA-256 or some of the SHA-3 candidates instead, such as BLAKE2 or Keccak.
H2CO3:
You might want to implement all the default methods of io::Write
as well, for the sake of performance.
Just to make sure I get this right: In my Sha1Reader
I should implement for example read_vectored()
that calls read_vectored()
of the wrapped reader
. If I don't do that, by implementing Read
for Sha1Reader
I get the default implementation, but that would just call read()
instead of read_vectored()
. Correct?
H2CO3:
Furthermore, if you can choose the hashing algorithm, don't choose SHA-1 – it's not considered secure any
I'm reading a file from a remote service and they provide a SHA1 hash for error detection. So I can't choose it, no, and I think for error detection SHA1 is still fine anyway?
AndreKR:
You implemented a Write
, I was talking about a Read
. That's just for example purposes, right? Or are you suggesting I should use a Write
I think I just misread that – the implementation for an io::Read
wrapper would be very similar.
AndreKR:
Just to make sure I get this right: In my Sha1Reader
I should implement for example read_vectored()
that calls read_vectored()
of the wrapped reader
. If I don't do that, by implementing Read
for Sha1Reader
I get the default implementation, but that would just call read()
instead of read_vectored()
. Correct?
Yes, correct.
AndreKR:
I'm reading a file from a remote service and they provide a SHA1 hash for error detection. So I can't choose it, no, and I think for error detection SHA1 is still fine anyway?
Indeed in this case, you don't control the algorithm, and for detection of random errors, SHA-1 will probably be fine.
Something like this could with the crate sha-1
(which probably shouldn't be used) could easily be made more generic (through the Digest
trait) and/or just change the wrapped hasher. Other advice above apply.
use std::{fs::File, io, path::Path};
use sha1::Digest;
#[derive(Default)]
struct HashWriter(sha1::Sha1)
fn hash_file(file: &Path) -> io::Result<[u8; 20]> {
let mut hasher = HashWriter::default();
io::copy(&mut File::open(file)?, &mut hasher)?;
Ok(hasher.0.finalize())
impl io::Write for HashWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.update(buf);
Ok(buf.len())
fn flush(&mut self) -> io::Result<()> {
Ok(())
std::io::copy
:
This function will continuously read data from reader
and then write it into writer
in a streaming fashion until reader
returns EOF.
PS: note that std::io::copy
will skip IO interruption errors and continue reading.
These two hash functions are still perfectly secure against preimage attacks. Collisions can't be used for MITM unless the attacker manages to trick the sender to send colliding half of a pair generated by the attacker, and then it can be used to flip certain bytes, but not to replace the payload. So these functions are broken for digital signatures, but integrity verification of non-attacker-controlled data is as strong as ever.
For posterity, here's the reader implementation (the rest is identical to the writer implementation above):
impl<R: Read> Read for Sha1Reader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let n = self.reader.read(buf)?;
self.hasher.update(&buf[..n]);
Ok(n)