Every developer has faced computing SHA256 checksums for files in their project at some point. For instance, you might need to search for exact file matches or, conversely, prevent duplicate uploads of an existing file. In Ruby, you can use the Digest::SHA256 class for this. But there’s a catch: the built-in method only works for files that already exist on the file system:
require 'digest'
require 'stringio'
require 'tempfile'
src = StringIO.new('Hello, Ruby!')
f = Tempfile.new
f.sync = true
IO.copy_stream(src, f)
Digest::SHA256.file(f.path).hexdigest
However, since a file uploaded by a user is typically represented as a stream (IO) in your web application, Ruby surprisingly doesn’t have a built-in way to calculate a hash directly from a stream. To work around this limitation, you usually have to compute the hash explicitly in a loop, like this:
require 'digest'
require 'stringio'
src = StringIO.new('Hello, Ruby!')
h = Digest::SHA256.new
while(chunk = src.read(2 * 1024 * 1024))
h.update(chunk)
end
h.hexdigest
But there’s a much more elegant way: define a write method directly on the Digest::SHA256 object, making it behave like a writable stream. Then you can simply call IO.copy_stream, passing in the source stream and our hash calculation object:
require 'digest'
require 'stringio'
src = StringIO.new('Hello, Ruby!')
dst = Digest::SHA256.new
def dst.write(*objs)
objs.sum do |obj|
update(obj)
obj.bytesize
end
end
IO.copy_stream(src, dst)
dst.hexdigest
And hey, if you aren’t a fan of this dynamic approach with singleton methods, you can implement a wrapper class DigestIO and define the write method there:
require 'digest'
require 'stringio'
class DigestIO
attr_reader :digest
def initialize(digest)
@digest = digest
end
def write(*objs)
objs.sum do |obj|
@digest.update(obj)
obj.bytesize
end
end
end
src = StringIO.new('Hello, Ruby!')
dst = DigestIO.new(Digest::SHA256.new)
IO.copy_stream(src, dst)
dst.digest.hexdigest
Which checksum calculation approach do you like best? How would you implement hash calculation in your own projects?