From a8f716de0957826f0d1b30de7426c2007b366231 Mon Sep 17 00:00:00 2001 From: Didactic Drunk <1479616+didactic-drunk@users.noreply.github.com> Date: Mon, 18 May 2020 23:13:50 -0700 Subject: [PATCH] Crystal <= 0.34 backport `final` methods from crystal 0.35. Crystal = 0.35 use new Digest::Base interface. Crystal > 0.35 futureport `hexfinal` method. --- README.md | 4 +- benchmarks/blake2b.cr | 18 ++++-- spec/sodium/digest/blake2b_spec.cr | 10 ++-- src/sodium/digest/blake2b.cr | 92 ++++++++++++++++++------------ 4 files changed, 76 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index d5a36fb..b7cd062 100644 --- a/README.md +++ b/README.md @@ -199,11 +199,11 @@ data = "data".to_slice # output_size, key, salt, and personal are optional. digest = Sodium::Digest::Blake2b.new out_size, key: key, salt: salt, personal: personal digest.update data -output = d.hexdigest +output = d.hexfinal digest.reset # Reuse existing object to hash again. digest.update data -output = d.hexdigest +output = d.hexfinal ``` ### Key derivation diff --git a/benchmarks/blake2b.cr b/benchmarks/blake2b.cr index fc16e03..8e53c75 100644 --- a/benchmarks/blake2b.cr +++ b/benchmarks/blake2b.cr @@ -15,14 +15,14 @@ Benchmark.ips(warmup: 0.5) do |bm| bm.report "blake2b new obj per iter #{size}" do d = Sodium::Digest::Blake2b.new 64 d.update bufs[i] - d.digest + d.final end d = Sodium::Digest::Blake2b.new output_size bm.report "blake2b reset per iter #{size}" do d.reset d.update bufs[i] - d.digest + d.final end d = Sodium::Digest::Blake2b.new output_size @@ -30,7 +30,7 @@ Benchmark.ips(warmup: 0.5) do |bm| bm.report "blake2b reset reusing buffer per iter #{size}" do d.reset d.update bufs[i] - d.finish dst + d.final dst end end @@ -39,17 +39,23 @@ Benchmark.ips(warmup: 0.5) do |bm| bm.report "#{arg} new obj per iter #{size}" do d = OpenSSL::Digest.new arg d.update bufs[i] - d.digest + d.final end d = OpenSSL::Digest.new arg bm.report "#{arg} reset per iter #{size}" do d.reset d.update bufs[i] - d.digest + d.final end - # OpenSSL::Digest doesn't have a public .finish (yet) + d = OpenSSL::Digest.new arg + dst = Bytes.new d.digest_size + bm.report "#{arg} reset reusing buffer per iterr #{size}" do + d.reset + d.update bufs[i] + d.final dst + end end end end diff --git a/spec/sodium/digest/blake2b_spec.cr b/spec/sodium/digest/blake2b_spec.cr index bc00c5a..eee1d4d 100644 --- a/spec/sodium/digest/blake2b_spec.cr +++ b/spec/sodium/digest/blake2b_spec.cr @@ -34,7 +34,7 @@ describe Sodium::Digest::Blake2b do test_vectors.each do |vec| d = Sodium::Digest::Blake2b.new 64, key: vec[:key].hexbytes d.update vec[:input].hexbytes - d.hexdigest.should eq vec[:output] + d.hexfinal.should eq vec[:output] end more_vectors.each do |vec| @@ -42,7 +42,7 @@ describe Sodium::Digest::Blake2b do personal = vec[:personal].empty? ? nil : vec[:personal].hexbytes d = Sodium::Digest::Blake2b.new vec[:out_len], key: vec[:key].hexbytes, salt: salt, personal: personal d.update vec[:input].hexbytes - d.hexdigest.should eq vec[:output] + d.hexfinal.should eq vec[:output] end end @@ -58,15 +58,15 @@ describe Sodium::Digest::Blake2b do d = Sodium::Digest::Blake2b.new key: key, salt: salt, personal: personal d.update "foo".to_slice - output = d.hexdigest + output = d.hexfinal d = Sodium::Digest::Blake2b.new key: key, salt: salt2, personal: personal d.update "foo".to_slice - saltout = d.hexdigest + saltout = d.hexfinal d = Sodium::Digest::Blake2b.new key: key, salt: salt, personal: personal2 d.update "foo".to_slice - personalout = d.hexdigest + personalout = d.hexfinal output.should_not eq saltout output.should_not eq personalout diff --git a/src/sodium/digest/blake2b.cr b/src/sodium/digest/blake2b.cr index eedea95..fa3eeef 100644 --- a/src/sodium/digest/blake2b.cr +++ b/src/sodium/digest/blake2b.cr @@ -1,6 +1,7 @@ require "../lib_sodium" require "../wipe" require "../secure_buffer" +require "digest/base" require "openssl/digest/digest_base" module Sodium::Digest @@ -13,11 +14,9 @@ module Sodium::Digest # digest = Blake2b.new # digest.update data # digest.update data - # digest.hexdigest => String + # digest.hexfinal => String # ``` - class Blake2b - # provides copying digest/hexdigest methods - include OpenSSL::DigestBase + class Blake2b < ::Digest::Base include Wipe # 32 @@ -40,11 +39,11 @@ module Sodium::Digest # 64 OUT_SIZE_MAX = LibSodium.crypto_generichash_blake2b_bytes_max.to_i32 - getter digest_size + getter digest_size : Int32 @[Wipe::Var] @state = StaticArray(UInt8, 384).new 0 - @key_size = 0 + getter key_size = 0 # implemented as static array's so clone works without jumping through hoops. @[Wipe::Var] @@ -57,7 +56,7 @@ module Sodium::Digest # digest_size is selectable. Use 32 for Blake2b256 (libsodium default), 64 for Blake2b512 # or any value between OUT_SIZE_MIN and OUT_SIZE_MAX. Many libsodium bindings only support [256] or [256 and 512] bit output. # - # `key`, `salt`, and `personal` are all optional. Most other libsodium bindings don't support them. + # `key`, `salt`, and `personal` are all optional. Many other libsodium bindings don't support them. # Check the other implementation(s) you need to interoperate with before using. def initialize(@digest_size : Int32 = OUT_SIZE, key : Bytes? | SecureBuffer? = nil, salt : Bytes? = nil, personal : Bytes? = nil) if k = key @@ -80,7 +79,57 @@ module Sodium::Digest reset end - def reset + # Compatibility with Crystal <= 0.35?(TBD) + {% unless @type.has_method?(:hexfinal) %} + def hexfinal : String + final.hexstring + end + {% end %} + + # Compatibility with Crystal <= 0.32 + {% unless @type.has_method?(:final) %} + # provides copying digest/hexdigest methods + include OpenSSL::DigestBase + + def update(data : Bytes) : self + update_impl data + self + end + + def reset : self + reset_impl + self + end + + # Destructive operation. Assumes you know what you are doing. + # Use .digest or .hexdigest instead. + def final + dst = Bytes.new @digest_size + final_impl dst + dst + end + + # Used by OpenSSL::DigestBase for #digest and #hexdigest + # :nodoc: + protected def finish + final + end + {% end %} + + def update_impl(data : Bytes) : Nil + if LibSodium.crypto_generichash_blake2b_update(@state.to_slice, data, data.bytesize) != 0 + raise Sodium::Error.new("crypto_generichash_blake2b_update") + end + end + + def final_impl(dst : Bytes) : Nil + ret = LibSodium.crypto_generichash_blake2b_final(@state.to_slice, dst, dst.bytesize) + if ret != 0 + raise Sodium::Error.new("crypto_generichash_blake2b_final #{ret.inspect}") + end + end + + def reset_impl : Nil key = @key.to_unsafe salt = @salt.to_unsafe personal = @personal.to_unsafe @@ -90,33 +139,6 @@ module Sodium::Digest end end - def update(data : Bytes) - if LibSodium.crypto_generichash_blake2b_update(@state.to_slice, data, data.bytesize) != 0 - raise Sodium::Error.new("crypto_generichash_blake2b_update") - end - - self - end - - # Destructive operation. Assumes you know what you are doing. - # Use .digest or .hexdigest instead. - def finish - dst = Bytes.new @digest_size - finish dst - dst - end - - # Destructive operation. Assumes you know what you are doing. - # Use .digest or .hexdigest instead. - def finish(dst : Bytes) : Bytes - ret = LibSodium.crypto_generichash_blake2b_final(@state.to_slice, dst, dst.bytesize) - if ret != 0 - raise Sodium::Error.new("crypto_generichash_blake2b_final #{ret.inspect}") - end - - dst - end - def clone dup end