-
Notifications
You must be signed in to change notification settings - Fork 82
/
blake2b.rb
182 lines (162 loc) · 7.4 KB
/
blake2b.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# encoding: binary
# frozen_string_literal: true
module RbNaCl
module Hash
# The Blake2b hash function
#
# Blake2b is based on Blake, a SHA3 finalist which was snubbed in favor of
# Keccak, a much slower hash function but one sufficiently different from
# SHA2 to let the SHA3 judges panel sleep easy. Back in the real world,
# it'd be great if we can calculate hashes quickly if possible.
#
# Blake2b provides for up to 64-bit digests and also supports a keyed mode
# similar to HMAC
class Blake2b
extend Sodium
sodium_type :generichash
sodium_primitive :blake2b
sodium_type_primitive_constant :BYTES_MIN
sodium_type_primitive_constant :BYTES_MAX
sodium_type_primitive_constant :KEYBYTES_MIN
sodium_type_primitive_constant :KEYBYTES_MAX
sodium_type_primitive_constant :SALTBYTES
sodium_type_primitive_constant :PERSONALBYTES
sodium_function :generichash_blake2b,
:crypto_generichash_blake2b_salt_personal,
%i[pointer size_t pointer ulong_long pointer size_t pointer pointer]
sodium_function :generichash_blake2b_init,
:crypto_generichash_blake2b_init_salt_personal,
%i[pointer pointer size_t size_t pointer pointer]
sodium_function :generichash_blake2b_update,
:crypto_generichash_blake2b_update,
%i[pointer pointer ulong_long]
sodium_function :generichash_blake2b_final,
:crypto_generichash_blake2b_final,
%i[pointer pointer size_t]
EMPTY_PERSONAL = ("\0" * PERSONALBYTES).freeze
EMPTY_SALT = ("\0" * SALTBYTES).freeze
# Calculate a Blake2b digest
#
# @param [String] message Message to be hashed
# @param [Hash] options Blake2b configuration
# @option opts [String] :key for Blake2b keyed mode
# @option opts [Integer] :digest_size size of output digest in bytes
# @option opts [String] :salt Provide a salt to support randomised hashing.
# This is mixed into the parameters block to start the hashing.
# @option opts [Personal] :personal Provide personalisation string to allow pinning a hash for a particular purpose.
# This is mixed into the parameters block to start the hashing
#
# @raise [RbNaCl::LengthError] Invalid length specified for one or more options
#
# @return [String] Blake2b digest of the string as raw bytes
def self.digest(message, options)
opts = validate_opts(options)
digest = Util.zeros(opts[:digest_size])
generichash_blake2b(digest, opts[:digest_size], message, message.bytesize,
opts[:key], opts[:key_size], opts[:salt], opts[:personal]) ||
raise(CryptoError, "Hashing failed!")
digest
end
# Validate and sanitize values for Blake2b configuration
#
# @param [Hash] options Blake2b configuration
# @option opts [String] :key for Blake2b keyed mode
# @option opts [Integer] :digest_size size of output digest in bytes
# @option opts [String] :salt Provide a salt to support randomised hashing.
# This is mixed into the parameters block to start the hashing.
# @option opts [Personal] :personal Provide personalisation string to allow pinning a hash for a particular purpose.
# This is mixed into the parameters block to start the hashing
#
# @raise [RbNaCl::LengthError] Invalid length specified for one or more options
#
# @return [Hash] opts Configuration hash with sanitized values
def self.validate_opts(opts)
key = opts.fetch(:key, nil)
if key
key_size = key.bytesize
raise LengthError, "key too short" if key_size < KEYBYTES_MIN
raise LengthError, "key too long" if key_size > KEYBYTES_MAX
else
key_size = 0
end
opts[:key_size] = key_size
digest_size = opts.fetch(:digest_size, BYTES_MAX)
raise LengthError, "digest size too short" if digest_size < BYTES_MIN
raise LengthError, "digest size too long" if digest_size > BYTES_MAX
opts[:digest_size] = digest_size
personal = opts.fetch(:personal, EMPTY_PERSONAL)
opts[:personal] = Util.zero_pad(PERSONALBYTES, personal)
salt = opts.fetch(:salt, EMPTY_SALT)
opts[:salt] = Util.zero_pad(SALTBYTES, salt)
opts
end
private_class_method :validate_opts
def self.new(opts = {})
opts = validate_opts(opts)
super
end
# Create a new Blake2b hash object
#
# @param [Hash] opts Blake2b configuration
# @option opts [String] :key for Blake2b keyed mode
# @option opts [Integer] :digest_size size of output digest in bytes
# @option opts [String] :salt Provide a salt to support randomised hashing.
# This is mixed into the parameters block to start the hashing.
# @option opts [Personal] :personal Provide personalisation string to allow pinning a hash for a particular purpose.
# This is mixed into the parameters block to start the hashing
#
# @raise [RbNaCl::LengthError] Invalid length specified for one or more options
#
# @return [RbNaCl::Hash::Blake2b] A Blake2b hasher object
def initialize(opts = {})
@key = opts[:key]
@key_size = opts[:key_size]
@digest_size = opts[:digest_size]
@personal = opts[:personal]
@salt = opts[:salt]
@incycle = false
@instate = nil
end
# Initialize state for Blake2b hash calculation,
# this will be called automatically from #update if needed
def reset
@instate.release if @instate
@instate = State.new
self.class.generichash_blake2b_init(@instate.pointer, @key, @key_size, @digest_size, @salt, @personal) ||
raise(CryptoError, "Hash init failed!")
@incycle = true
@digest = nil
end
# Reentrant version of Blake2b digest calculation method
#
# @param [String] message Message to be hashed
def update(message)
reset unless @incycle
self.class.generichash_blake2b_update(@instate.pointer, message, message.bytesize) ||
raise(CryptoError, "Hashing failed!")
end
alias << update
# Finalize digest calculation, return cached digest if any
#
# @return [String] Blake2b digest of the string as raw bytes
def digest
raise(CryptoError, "No message to hash yet!") unless @incycle
return @digest if @digest
@digest = Util.zeros(@digest_size)
self.class.generichash_blake2b_final(@instate.pointer, @digest, @digest_size) ||
raise(CryptoError, "Hash finalization failed!")
@digest
end
# The crypto_generichash_blake2b_state struct representation
# ref: jedisct1/libsodium/src/libsodium/include/sodium/crypto_generichash_blake2b.h#L23
class State < FFI::Struct
layout :h, [:uint64, 8],
:t, [:uint64, 2],
:f, [:uint64, 2],
:buf, [:uint8, 2 * 128],
:buflen, :size_t,
:last_node, :uint8
end
end
end
end