Skip to content

Commit

Permalink
Refactor Digest (#9864)
Browse files Browse the repository at this point in the history
* Move MD5 and SHA1 to Crystal namespace for internal use

* Use OpenSSL::Digest as base for Digest::MD5 and Digest::SHA1

Temporal removal of OpenSSL::DigestBase, those methods will be moved to Digest

* Add Digest::SHA256 and Digest::SHA512

* Fix #dup methods for OpenSSL based digests

* Move IO / File convenient methods to Digest::Base

And drop OpenSSL::DigestBase

* Fix deprecation warning in specs

* Move OpenSSL::DigestIO to IO::Digest (in "digest" package)

* Move file location of openssl/digest

* Move Digest::Base to Digest directly

* Introduce Digest::Algorithm module for Digests with argless .new

Enable pending spec

* Add safe guard to prevent linking openssl when -Dwithout_openssl is used

* Use Crystal::Digest in compiler and in WebSocket (if -Dwithout_openssl)

* Fix std_spec compilation

* Update win32_std_spec.cr

* Rename Algorithm to ClassMethods

* Keep using Crystal::Digest::MD5 on base64_spec.cr

* Update win32_std_spec.cr

* Drop IO::Digest initialization by algorithm name

Drop base64digest delegation. There is no Digest#base64digest

* Rework docs

* Drop deprecated Digest#digest and Digest#hexdigest methods
  • Loading branch information
Brian J. Cardiff committed Nov 9, 2020
1 parent 50a623d commit 4e8e004
Show file tree
Hide file tree
Showing 36 changed files with 1,009 additions and 592 deletions.
6 changes: 3 additions & 3 deletions spec/std/base64_spec.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require "spec"
require "base64"
require "digest/md5"
require "crystal/digest/md5"

describe "Base64" do
context "simple test" do
Expand Down Expand Up @@ -82,15 +82,15 @@ describe "Base64" do
it "big message" do
a = "a" * 100000
b = Base64.encode(a)
Digest::MD5.hexdigest(Base64.decode_string(b)).should eq(Digest::MD5.hexdigest(a))
Crystal::Digest::MD5.hexdigest(Base64.decode_string(b)).should eq(Crystal::Digest::MD5.hexdigest(a))
end

it "works for most characters" do
a = String.build(65536 * 4) do |buf|
65536.times { |i| buf << (i + 1).chr }
end
b = Base64.encode(a)
Digest::MD5.hexdigest(Base64.decode_string(b)).should eq(Digest::MD5.hexdigest(a))
Crystal::Digest::MD5.hexdigest(Base64.decode_string(b)).should eq(Crystal::Digest::MD5.hexdigest(a))
end
end

Expand Down
62 changes: 62 additions & 0 deletions spec/std/crystal/digest/md5_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "../../spec_helper"
require "../../digest/spec_helper"
require "crystal/digest/md5"

describe Crystal::Digest::MD5 do
it_acts_as_digest_algorithm Crystal::Digest::MD5

it "calculates digest from string" do
Crystal::Digest::MD5.digest("foo").to_slice.should eq Bytes[0xac, 0xbd, 0x18, 0xdb, 0x4c, 0xc2, 0xf8, 0x5c, 0xed, 0xef, 0x65, 0x4f, 0xcc, 0xc4, 0xa4, 0xd8]
end

it "calculates hash from string" do
Crystal::Digest::MD5.hexdigest("foo").should eq("acbd18db4cc2f85cedef654fccc4a4d8")
end

it "calculates hash from unicode string" do
Crystal::Digest::MD5.hexdigest("fooø").should eq("d841c4eb31535db11faab98d10316b29")
end

it "calculates hash from UInt8 slices" do
s = Bytes[0x66, 0x6f, 0x6f] # f,o,o
Crystal::Digest::MD5.hexdigest(s).should eq("acbd18db4cc2f85cedef654fccc4a4d8")
end

it "calculates hash of #to_slice" do
buffer = StaticArray(UInt8, 1).new(1_u8)
Crystal::Digest::MD5.hexdigest(buffer).should eq("55a54008ad1ba589aa210d2629c1df41")
end

it "can take a block" do
Crystal::Digest::MD5.hexdigest do |ctx|
ctx.update "f"
ctx.update Bytes[0x6f, 0x6f]
end.should eq("acbd18db4cc2f85cedef654fccc4a4d8")
end

it "calculates base64'd hash from string" do
Crystal::Digest::MD5.base64digest("foo").should eq("rL0Y20zC+Fzt72VPzMSk2A==")
end

it "resets" do
digest = Crystal::Digest::MD5.new
digest.update "foo"
digest.final.hexstring.should eq("acbd18db4cc2f85cedef654fccc4a4d8")

digest.reset
digest.update "foo"
digest.final.hexstring.should eq("acbd18db4cc2f85cedef654fccc4a4d8")
end

it "can't call final twice" do
digest = Crystal::Digest::MD5.new
digest.final
expect_raises(Digest::FinalizedError) do
digest.final
end
end

it "return the digest size" do
Crystal::Digest::MD5.new.digest_size.should eq 16
end
end
62 changes: 62 additions & 0 deletions spec/std/crystal/digest/sha1_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "../../spec_helper"
require "../../digest/spec_helper"
require "crystal/digest/sha1"

describe Crystal::Digest::SHA1 do
it_acts_as_digest_algorithm Crystal::Digest::SHA1

[
{"", "da39a3ee5e6b4b0d3255bfef95601890afd80709", "2jmj7l5rSw0yVb/vlWAYkK/YBwk="},
{"The quick brown fox jumps over the lazy dog", "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", "L9ThxnotKPzthJ7hu3bnORuT6xI="},
{"abc", "a9993e364706816aba3e25717850c26c9cd0d89d", "qZk+NkcGgWq6PiVxeFDCbJzQ2J0="},
{"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84983e441c3bd26ebaae4aa1f95129e5e54670f1", "hJg+RBw70m66rkqh+VEp5eVGcPE="},
{"a", "86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", "hvfkN/qlp/zhXR3cuerq6jd2Z7g="},
{"0123456701234567012345670123456701234567012345670123456701234567", "e0c094e867ef46c350ef54a7f59dd60bed92ae83", "4MCU6GfvRsNQ71Sn9Z3WC+2SroM="},
{"fooø", "dcf4a1e3542b1a40a4ac2a3f7c92ffdb2d19812f", "3PSh41QrGkCkrCo/fJL/2y0ZgS8="},
].each do |(string, hexstring, base64digest)|
it "does digest for #{string.inspect}" do
bytes = Crystal::Digest::SHA1.digest(string)
bytes.hexstring.should eq(hexstring)
end

it "resets" do
digest = Crystal::Digest::SHA1.new
digest.update string
digest.final.hexstring.should eq(hexstring)

digest.reset
digest.update string
digest.final.hexstring.should eq(hexstring)
end

it "can't call #final more than once" do
digest = Crystal::Digest::SHA1.new
digest.final
expect_raises(Digest::FinalizedError) do
digest.final
end
end

it "does digest for #{string.inspect} in a block" do
bytes = Crystal::Digest::SHA1.digest do |ctx|
string.each_char do |chr|
ctx.update chr.to_s
end
end

bytes.hexstring.should eq(hexstring)
end

it "does hexdigest for #{string.inspect}" do
Crystal::Digest::SHA1.hexdigest(string).should eq(hexstring)
end

it "does base64digest for #{string.inspect}" do
Crystal::Digest::SHA1.base64digest(string).should eq(base64digest)
end
end

it "returns the digest_size" do
Crystal::Digest::SHA1.new.digest_size.should eq(20)
end
end
Original file line number Diff line number Diff line change
@@ -1,82 +1,96 @@
require "spec"
require "../../../src/openssl"
require "digest"

describe OpenSSL::DigestIO do
describe IO::Digest do
it "calculates digest from reading" do
base_io = IO::Memory.new("foo")
base_digest = OpenSSL::Digest.new("SHA256")
io = OpenSSL::DigestIO.new(base_io, base_digest)
io = IO::Digest.new(base_io, ::Digest::SHA256.new)
slice = Bytes.new(256)
io.read(slice).should eq(3)

slice[0, 3].should eq("foo".to_slice)
io.digest.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
io.final.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
end

it "can be created with ongoing digest" do
base_digest = OpenSSL::Digest.new("SHA256")
base_digest.update("foo")

base_io = IO::Memory.new("bar")
io = IO::Digest.new(base_io, base_digest)
slice = Bytes.new(256)
io.read(slice).should eq(3)

base_digest.update("baz")

# sha256("foobarbaz")
io.final.should eq("97df3588b5a3f24babc3851b372f0ba71a9dcdded43b14b9d06961bfc1707d9d".hexbytes)
end

it "calculates digest from multiple reads" do
base_io = IO::Memory.new("foo")
base_digest = OpenSSL::Digest.new("SHA256")
io = OpenSSL::DigestIO.new(base_io, base_digest)
io = IO::Digest.new(base_io, base_digest)
slice = Bytes.new(2)
io.read(slice).should eq(2)
slice[0, 2].should eq("fo".to_slice)

io.read(slice).should eq(1)
slice[0, 1].should eq("o".to_slice)

io.digest.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
io.final.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
end

it "does not calculate digest on read" do
base_io = IO::Memory.new("foo")
base_digest = OpenSSL::Digest.new("SHA256")
empty_digest = OpenSSL::Digest.new("SHA256").digest
io = OpenSSL::DigestIO.new(base_io, base_digest, OpenSSL::DigestIO::DigestMode::Write)
empty_digest = OpenSSL::Digest.new("SHA256").final
io = IO::Digest.new(base_io, base_digest, IO::Digest::DigestMode::Write)
slice = Bytes.new(256)
io.read(slice).should eq(3)
slice[0, 3].should eq("foo".to_slice)
io.digest.should eq(empty_digest)
io.final.should eq(empty_digest)
end

it "calculates digest from writing" do
base_io = IO::Memory.new
base_digest = OpenSSL::Digest.new("SHA256")
io = OpenSSL::DigestIO.new(base_io, base_digest, OpenSSL::DigestIO::DigestMode::Write)
io = IO::Digest.new(base_io, base_digest, IO::Digest::DigestMode::Write)
io.write("foo".to_slice)

base_io.to_slice[0, 3].should eq("foo".to_slice)
io.digest.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
io.final.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
end

it "calculates digest from writing a string" do
base_io = IO::Memory.new
base_digest = OpenSSL::Digest.new("SHA256")
io = OpenSSL::DigestIO.new(base_io, base_digest, OpenSSL::DigestIO::DigestMode::Write)
io = IO::Digest.new(base_io, base_digest, IO::Digest::DigestMode::Write)
io.print("foo")

base_io.to_slice[0, 3].should eq("foo".to_slice)
io.digest.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
io.final.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
end

it "calculates digest from multiple writes" do
base_io = IO::Memory.new
base_digest = OpenSSL::Digest.new("SHA256")
io = OpenSSL::DigestIO.new(base_io, base_digest, OpenSSL::DigestIO::DigestMode::Write)
io = IO::Digest.new(base_io, base_digest, IO::Digest::DigestMode::Write)
io.write("fo".to_slice)
io.write("o".to_slice)
base_io.to_slice[0, 3].should eq("foo".to_slice)

io.digest.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
io.final.should eq("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae".hexbytes)
end

it "does not calculate digest on write" do
base_io = IO::Memory.new
base_digest = OpenSSL::Digest.new("SHA256")
empty_digest = OpenSSL::Digest.new("SHA256").digest
io = OpenSSL::DigestIO.new(base_io, base_digest, OpenSSL::DigestIO::DigestMode::Read)
empty_digest = OpenSSL::Digest.new("SHA256").final
io = IO::Digest.new(base_io, base_digest, IO::Digest::DigestMode::Read)
io.write("foo".to_slice)

base_io.to_slice[0, 3].should eq("foo".to_slice)
io.digest.should eq(empty_digest)
io.final.should eq(empty_digest)
end
end
2 changes: 1 addition & 1 deletion spec/std/digest/md5_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "spec"
require "../spec_helper"
require "./spec_helper"
require "digest/md5"

Expand Down
2 changes: 1 addition & 1 deletion spec/std/digest/sha1_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "spec"
require "../spec_helper"
require "./spec_helper"
require "digest/sha1"

Expand Down
62 changes: 62 additions & 0 deletions spec/std/digest/sha256_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "../spec_helper"
require "./spec_helper"
require "digest/sha256"

describe Digest::SHA256 do
it_acts_as_digest_algorithm Digest::SHA256

[
{"", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="},
{"The quick brown fox jumps over the lazy dog", "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", "16j7swfXgJRpypq8sAguT41WUeRtPNt2LQLQvzfJ5ZI="},
{"abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0="},
{"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "JI1qYdIGOLjlwCaTDD5gOaM85Flk/yFn9uzt1BnbBsE="},
{"a", "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", "ypeBEsobvcr6wjGzmiPcTaeG7/gUfE5yuYB3ha/uSLs="},
{"0123456701234567012345670123456701234567012345670123456701234567", "8182cadb21af0e37c06414ece08e19c65bdb22c396d48ba7341012eea9ffdfdd", "gYLK2yGvDjfAZBTs4I4ZxlvbIsOW1IunNBAS7qn/390="},
{"fooø", "df81eea14671ce970fb1052e9f5dd6dbda652ed37423ed3624120ec1534784a7", "34HuoUZxzpcPsQUun13W29plLtN0I+02JBIOwVNHhKc="},
].each do |(string, hexstring, base64digest)|
it "does digest for #{string.inspect}" do
bytes = Digest::SHA256.digest(string)
bytes.hexstring.should eq(hexstring)
end

it "resets" do
digest = Digest::SHA256.new
digest.update string
digest.final.hexstring.should eq(hexstring)

digest.reset
digest.update string
digest.final.hexstring.should eq(hexstring)
end

it "can't call #final more than once" do
digest = Digest::SHA256.new
digest.final
expect_raises(Digest::FinalizedError) do
digest.final
end
end

it "does digest for #{string.inspect} in a block" do
bytes = Digest::SHA256.digest do |ctx|
string.each_char do |chr|
ctx.update chr.to_s
end
end

bytes.hexstring.should eq(hexstring)
end

it "does hexdigest for #{string.inspect}" do
Digest::SHA256.hexdigest(string).should eq(hexstring)
end

it "does base64digest for #{string.inspect}" do
Digest::SHA256.base64digest(string).should eq(base64digest)
end
end

it "returns the digest_size" do
Digest::SHA256.new.digest_size.should eq(32)
end
end

0 comments on commit 4e8e004

Please sign in to comment.