Crystal = 0.35 use new Digest::Base interface. Crystal > 0.35 futureport `hexfinal` method.
157 lines
4.9 KiB
Crystal
157 lines
4.9 KiB
Crystal
require "../lib_sodium"
|
|
require "../wipe"
|
|
require "../secure_buffer"
|
|
require "digest/base"
|
|
require "openssl/digest/digest_base"
|
|
|
|
module Sodium::Digest
|
|
# Hash data using Blake2b.
|
|
#
|
|
# Compatible with the Crystal OpenSSL::Digest interface.
|
|
#
|
|
# Usage:
|
|
# ```
|
|
# digest = Blake2b.new
|
|
# digest.update data
|
|
# digest.update data
|
|
# digest.hexfinal => String
|
|
# ```
|
|
class Blake2b < ::Digest::Base
|
|
include Wipe
|
|
|
|
# 32
|
|
KEY_SIZE = LibSodium.crypto_generichash_blake2b_keybytes.to_i
|
|
# 16
|
|
KEY_SIZE_MIN = LibSodium.crypto_generichash_blake2b_keybytes_min.to_i
|
|
# 64
|
|
KEY_SIZE_MAX = LibSodium.crypto_generichash_blake2b_keybytes_max.to_i
|
|
|
|
# 16
|
|
SALT_SIZE = LibSodium.crypto_generichash_blake2b_saltbytes.to_i
|
|
|
|
# 16
|
|
PERSONAL_SIZE = LibSodium.crypto_generichash_blake2b_personalbytes.to_i
|
|
|
|
# 32
|
|
OUT_SIZE = LibSodium.crypto_generichash_blake2b_bytes.to_i32
|
|
# 16
|
|
OUT_SIZE_MIN = LibSodium.crypto_generichash_blake2b_bytes_min.to_i32
|
|
# 64
|
|
OUT_SIZE_MAX = LibSodium.crypto_generichash_blake2b_bytes_max.to_i32
|
|
|
|
getter digest_size : Int32
|
|
|
|
@[Wipe::Var]
|
|
@state = StaticArray(UInt8, 384).new 0
|
|
getter key_size = 0
|
|
|
|
# implemented as static array's so clone works without jumping through hoops.
|
|
@[Wipe::Var]
|
|
@key = StaticArray(UInt8, 64).new 0
|
|
@salt = StaticArray(UInt8, 16).new 0
|
|
@personal = StaticArray(UInt8, 16).new 0
|
|
|
|
# Create a new Blake2b 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. 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
|
|
k = k.to_slice
|
|
raise ArgumentError.new("key larger than KEY_SIZE_MAX(#{KEY_SIZE_MAX}), got #{k.bytesize}") if k.bytesize > KEY_SIZE_MAX
|
|
@key_size = k.bytesize
|
|
k.copy_to @key.to_slice
|
|
end
|
|
|
|
if sa = salt
|
|
raise ArgumentError.new("salt must be SALT_SIZE(#{SALT_SIZE}) bytes, got #{sa.bytesize}") if sa.bytesize != SALT_SIZE
|
|
sa.copy_to @salt.to_slice
|
|
end
|
|
|
|
if pe = personal
|
|
raise ArgumentError.new("personal must be PERSONAL_SIZE(#{PERSONAL_SIZE}) bytes, got #{pe.bytesize}") if pe.bytesize != PERSONAL_SIZE
|
|
pe.copy_to @personal.to_slice
|
|
end
|
|
|
|
reset
|
|
end
|
|
|
|
# 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
|
|
|
|
if LibSodium.crypto_generichash_blake2b_init_salt_personal(@state.to_slice, key, @key_size, @digest_size, salt, personal) != 0
|
|
raise Sodium::Error.new("blake2b_init_key_salt_personal")
|
|
end
|
|
end
|
|
|
|
def clone
|
|
dup
|
|
end
|
|
|
|
# :nodoc:
|
|
def __validate_sizes__
|
|
state_size = LibSodium.crypto_generichash_blake2b_statebytes
|
|
abort "@state.bytesize doesn't match library version #{@state.to_slice.bytesize} #{state_size}" if @state.to_slice.bytesize < state_size
|
|
abort "@key.bytesize doesn't match library version" if @key.to_slice.bytesize != KEY_SIZE_MAX
|
|
abort "@salt.bytesize doesn't match library version #{@salt.to_slice.bytesize} #{SALT_SIZE}" if @salt.to_slice.bytesize != SALT_SIZE
|
|
abort "@personal.bytesize doesn't match library version #{@personal.to_slice.bytesize} #{PERSONAL_SIZE}" if @personal.to_slice.bytesize != SALT_SIZE
|
|
end
|
|
end
|
|
|
|
Blake2b.new.__validate_sizes__
|
|
end
|