forked from pmai/sha3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sha3.lisp
executable file
·273 lines (259 loc) · 13.2 KB
/
sha3.lisp
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
;;;; SHA3 --- Secure Hash Algorithm 3 (Keccak) Implementation
;;;;
;;;; Copyright (C) 2012 -- 2018 PMSF IT Consulting Pierre R. Mai.
;;;;
;;;; Permission is hereby granted, free of charge, to any person obtaining
;;;; a copy of this software and associated documentation files (the
;;;; "Software"), to deal in the Software without restriction, including
;;;; without limitation the rights to use, copy, modify, merge, publish,
;;;; distribute, sublicense, and/or sell copies of the Software, and to
;;;; permit persons to whom the Software is furnished to do so, subject to
;;;; the following conditions:
;;;;
;;;; The above copyright notice and this permission notice shall be
;;;; included in all copies or substantial portions of the Software.
;;;;
;;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
;;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
;;;; IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR
;;;; OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
;;;; ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
;;;; OTHER DEALINGS IN THE SOFTWARE.
;;;;
;;;; Except as contained in this notice, the name of the author shall
;;;; not be used in advertising or otherwise to promote the sale, use or
;;;; other dealings in this Software without prior written authorization
;;;; from the author.
;;;;
;;;; $Id$
(cl:in-package #:sha3)
;;;; %File Description:
;;;;
;;;; This file contains the implementation of mid- and high-level
;;;; SHA-3 functions based on the optimized Keccak implementations.
;;;;
;;;; The main entry points are the mid-level functions sha3-init,
;;;; sha3-update and sha3-final to initialize, update and finalize an
;;;; sha3 hash, and sha3-copy in order to copy a sha3 state.
;;;;
;;;; For convenience high-level functions to hash a given vector,
;;;; stream or file are provided as sha3-digest-vector,
;;;; sha3-digest-stream and sha3-digest-vector.
;;;;
;;;
;;; Mid-Level Routines
;;;
(defstruct (sha3-state
(:constructor make-sha3-state (bit-rate))
(:copier))
(state (make-keccak-1600-state) :type keccak-1600-state :read-only t)
(bit-rate 1024 :type (integer 0 1600) :read-only t)
(buffer (make-array 200 :element-type '(unsigned-byte 8)) :read-only t
:type (simple-array (unsigned-byte 8) (200)))
(buffer-index 0 :type (integer 0 199))
(finalized-p nil))
(defun sha3-init (&key (output-bit-length nil output-bit-length-p)
(bit-rate (if (and output-bit-length-p
output-bit-length)
(- 1600 (* 2 output-bit-length))
1024)))
"Create and return a new SHA-3 state. If `output-bit-length' is specified
then the state will run at the bit rate specified for the given output bit
length. If `output-bit-length' is unspecified, `bit-rate' can be specified
to select a suitable bit rate. If both are left unspecified then a default
bit rate of 1024 bits is selected, which is suitable for arbitrary output
bit lengths of up to 288 bits."
(check-type output-bit-length (member nil 224 256 288 384 512)
"Legal output-bit-length specifier for SHA-3/Keccak-1600")
(check-type bit-rate (member 576 832 1024 1088 1152)
"Legal bit-rate for SHA-3/Keccak-1600")
(if (or (null output-bit-length)
(= bit-rate (- 1600 (* 2 output-bit-length)))
(= bit-rate 1024))
(make-sha3-state bit-rate)
(error "Illegal combination of output-bit-length ~D and bit-rate ~D."
output-bit-length bit-rate)))
(defun sha3-copy (state)
"Return an independent copy of the SHA-3 state `state'."
(let ((result (make-sha3-state (sha3-state-bit-rate state))))
(setf (sha3-state-buffer-index result) (sha3-state-buffer-index state)
(sha3-state-finalized-p result) (sha3-state-finalized-p state))
(replace (sha3-state-buffer result) (sha3-state-buffer state))
(replace (sha3-state-state result) (sha3-state-state state))
result))
(defun sha3-update (state vector &key (start 0) (end (length vector)))
"Update the given SHA-3 state `state' from `vector', which must be
a simple-array with element-type (unsigned-byte 8), bounded by `start'
and `end', which must be numeric bounding-indices."
(declare (type sha3-state state)
(type (simple-array (unsigned-byte 8) (*)) vector)
(type fixnum start end)
(optimize (speed 3) (safety 1) (space 0) (debug 1)))
(let* ((keccak-state (sha3-state-state state))
(buffer (sha3-state-buffer state))
(buffer-index (sha3-state-buffer-index state))
(bit-rate (sha3-state-bit-rate state))
(rate-bytes (truncate bit-rate 8)))
(declare (type keccak-1600-state keccak-state)
(type (simple-array (unsigned-byte 8) (200)) buffer)
(type (integer 0 199) buffer-index)
(type (integer 0 1600) bit-rate)
(type (integer 0 200) rate-bytes)
#.*optimize-declaration*)
;; Handle potential remaining bytes
(unless (zerop buffer-index)
(let ((remainder (- rate-bytes buffer-index))
(length (- end start)))
(declare (type fixnum remainder length))
(replace buffer vector :start1 buffer-index :start2 start :end2 end)
;; Return if still unfilled buffer
(when (< length remainder)
(incf (sha3-state-buffer-index state) length)
(return-from sha3-update))
;; Else handle now complete buffer
(keccak-state-merge-input keccak-state bit-rate buffer 0)
(keccak-f keccak-state)
(setf (sha3-state-buffer-index state) 0
start (+ start remainder))))
;; Now handle full blocks, stuff any remainder into buffer
(loop for block-offset of-type fixnum from start below end by rate-bytes
do
(cond
((<= (+ block-offset rate-bytes) end)
(keccak-state-merge-input keccak-state bit-rate vector block-offset)
(keccak-f keccak-state))
(t
(replace buffer vector :start1 0 :start2 block-offset)
(setf (sha3-state-buffer-index state) (- end block-offset)))))))
(defun sha3-final (state &key (output-bit-length nil output-bit-length-p) raw-keccak-p)
"If the given SHA-3 state `state' has not already been finalized,
finalize it by processing any remaining input in its buffer, with
the specified suffix of 01 and suitable padding as specified by the
SHA-3 standard (the specified SHA-3 suffix can be elided with the
optional keyword argument `raw-keccak-p' to generate digests as the
initial Keccak submission would have generated). Returns the
message digest as a simple-array of (unsigned-byte 8). The length
of the returned digest is determined either by the output bit length
or bit rate specified on state creation, or for the special case of
default parameters being used, by the optional keyword argument
`output-bit-length'. If the state has previously been finalized,
the function will return the digest again."
(declare (type sha3-state state)
(type (or null (integer 0 1600)) output-bit-length)
(optimize (speed 3) (safety 1) (space 0) (debug 1)))
(let ((keccak-state (sha3-state-state state))
(buffer (sha3-state-buffer state))
(buffer-index (sha3-state-buffer-index state))
(bit-rate (sha3-state-bit-rate state))
(finalized-p (sha3-state-finalized-p state)))
(declare (type keccak-1600-state keccak-state)
(type (simple-array (unsigned-byte 8) (200)) buffer)
(type (integer 0 199) buffer-index)
(type (integer 0 1600) bit-rate)
(type (or null (simple-array (unsigned-byte 8) (*))) finalized-p)
#.*optimize-declaration*)
;; Determine output-bit-length
(if output-bit-length-p
(unless (or (= bit-rate 1024)
(= (* 2 output-bit-length) (- 1600 bit-rate)))
(error "Illegal output-bit-length ~D specified!" output-bit-length))
(setq output-bit-length (truncate (- 1600 bit-rate) 2)))
(cond
;; Check if already finalized
(finalized-p
(unless (= (* (length finalized-p) 8) output-bit-length)
(error "Mismatch in output-bit-length ~D in repeated call to sha3-final! ~
Should be: ~D!"
output-bit-length (* (length finalized-p) 8)))
finalized-p)
;; Finalize
(t
(keccak-state-merge-input keccak-state bit-rate
(pad-message-to-width
(subseq buffer 0 buffer-index)
bit-rate
(not raw-keccak-p))
0)
(keccak-f keccak-state)
(setf (sha3-state-buffer-index state) 0
(sha3-state-finalized-p state)
(keccak-state-extract-output keccak-state output-bit-length))))))
;;;
;;; High-Level Routines
;;;
(defun sha3-digest-vector (vector &key (start 0) end
(output-bit-length 512)
raw-keccak-p)
"Calculate an SHA-3 message-digest of data in `vector', which should
be a 1d simple-array with element type (unsigned-byte 8), bounded by
`start' and `end'. The bit length of the message digest produced is
controlled by `output-bit-length', which can take on the values 224,
256, 288, 384 and 512, which is the default value. Using the optional
`raw-keccak-p' keyword argument the SHA-3 mandated 01 suffix that is
appended to the actual message prior to padding can be elided to yield
message digests that match the original Keccak submission instead of
the actual SHA-3 standard. Use this option only for compatibility
with historical implementations."
(declare (optimize (speed 3) (safety 3) (space 0) (debug 1))
(type (simple-array (unsigned-byte 8) (*)) vector)
(type fixnum start)
(type (or null fixnum) end)
(type (integer 0 1600) output-bit-length))
(locally
(declare (optimize (safety 1) (debug 0)))
(let ((state (sha3-init :output-bit-length output-bit-length)))
(declare (type sha3-state state))
(let ((real-end (or end (length vector))))
(declare (type fixnum real-end))
(sha3-update state vector :start start :end real-end))
(sha3-final state :raw-keccak-p raw-keccak-p))))
(eval-when (:compile-toplevel :load-toplevel :execute)
(defconstant +buffer-size+ (* 128 1024)
"Size of internal buffer to use for `sha3-digest-stream' and
`sha3-digest-file' operations."))
(deftype buffer-index () `(integer 0 ,+buffer-size+))
(defun sha3-digest-stream (stream &key (output-bit-length 512) raw-keccak-p)
"Calculate an SHA-3 message-digest of data read from `stream', which
should be a stream with element type (unsigned-byte 8). The bit
length of the message digest produced is controlled by
`output-bit-length', which can take on the values 224, 256, 288, 384
and 512, which is the default value. Using the optional `raw-keccak-p'
keyword argument the SHA-3 mandated 01 suffix that is appended to the
actual message prior to padding can be elided to yield message digests
that match the original Keccak submission instead of the actual SHA-3
standard. Use this option only for compatibility with historical
implementations."
(declare (optimize (speed 3) (safety 3) (space 0) (debug 1))
(type stream stream)
(type (integer 0 1600) output-bit-length))
(locally
(declare (optimize (safety 1) (debug 0)))
(unless (equal (stream-element-type stream) '(unsigned-byte 8))
(error "Illegal stream-element-type ~S, must be ~S."
(stream-element-type stream) '(unsigned-byte 8)))
(let ((buffer (make-array '(#.+buffer-size+) :element-type '(unsigned-byte 8)))
(state (sha3-init :output-bit-length output-bit-length)))
(declare (type sha3-state state)
(type (simple-array (unsigned-byte 8) (#.+buffer-size+)) buffer))
(loop for bytes of-type buffer-index = (read-sequence buffer stream)
do (sha3-update state buffer :end bytes)
until (< bytes +buffer-size+)
finally
(return (sha3-final state :raw-keccak-p raw-keccak-p))))))
(defun sha3-digest-file (pathname &key (output-bit-length 512) raw-keccak-p)
"Calculate an SHA-3 message-digest of the file specified by
`pathname'. The bit length of the message digest produced is
controlled by `output-bit-length', which can take on the values 224,
256, 288, 384 and 512, which is the default value. Using the optional
`raw-keccak-p' keyword argument the SHA-3 mandated 01 suffix that is
appended to the actual message prior to padding can be elided to yield
message digests that match the original Keccak submission instead of
the actual SHA-3 standard. Use this option only for compatibility
with historical implementations."
(declare (optimize (speed 3) (safety 3) (space 0) (debug 1))
(type (integer 0 1600) output-bit-length))
(locally
(declare (optimize (safety 1) (debug 0)))
(with-open-file (stream pathname :element-type '(unsigned-byte 8))
(sha3-digest-stream stream :output-bit-length output-bit-length :raw-keccak-p raw-keccak-p))))