;;;; SHA3 --- Secure Hash Algorithm 3 (Keccak) Implementation ;;;; ;;;; Copyright (C) 2012 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:defpackage #:keccak-reference (:use #:common-lisp) (:nicknames #:keccak) (:export #:keccak #:print-digest #:test-with-testsuite #:read-testsuite-from-file #:test-with-testsuite-from-file #:test-keccak-msgkat)) (cl:in-package #:keccak-reference) ;;;; %File Description: ;;;; ;;;; This file contains a simple and unoptimized reference ;;;; implementation of the Keccak secure hash algorithm (which forms ;;;; the basis of the SHA-3 Secure Hash Algorithm Standard). ;;;; ;;;; It is used as a reference for the more optimized and specialized ;;;; implementations of the SHA-3 standard contained in this package. ;;;; ;;;; The main entry point is the functions keccak which calculates a ;;;; keccak hash for a given set of parameters and a given input ;;;; vector and returns both the extracted output hash and the state ;;;; after hashing as its values. ;;;; ;;; ;;; Keccak state and parametes ;;; (defconstant +keccak-state-columns+ 5 "Width of Keccak state in the x axis") (defconstant +keccak-state-rows+ 5 "Width of Keccak state in the y axis") (defconstant +keccak-state-lanes+ (* +keccak-state-columns+ +keccak-state-rows+) "Total number of lanes in Keccak state") (eval-when (:compile-toplevel :load-toplevel :execute) (defun keccak-lane-width (total-bits) (multiple-value-bind (width rest) (truncate total-bits +keccak-state-lanes+) (assert (zerop rest) (total-bits) "Illegal Keccak Total-Bits value: ~S. Must be a multiple of ~D." total-bits +keccak-state-lanes+) width))) (deftype keccak-lane (total-bits) "Type of a keccak lane for a given total bit number." `(unsigned-byte ,(keccak-lane-width total-bits))) (deftype keccak-state (total-bits) "Type of a keccak working state object for a given total bit number." `(simple-array (keccak-lane ,total-bits) (,+keccak-state-columns+ ,+keccak-state-rows+))) (defun make-keccak-state (total-bits) (make-array (list +keccak-state-columns+ +keccak-state-rows+) :element-type `(keccak-lane ,total-bits) :initial-element 0)) ;;; ;;; Transforming linear input/output to state array ;;; (defun keccak-state-merge-input (state total-bits bit-rate input start) (let ((lane-width (keccak-lane-width total-bits)) (rate-bytes (truncate bit-rate 8))) (dotimes (x +keccak-state-columns+) (dotimes (y +keccak-state-rows+) (let ((offset (truncate (* (+ (* y +keccak-state-columns+) x) lane-width) 8))) (unless (>= offset rate-bytes) (setf (aref state x y) (logxor (aref state x y) (loop with value = 0 for index from (1- (+ start offset (ceiling lane-width 8))) downto (+ start offset) do (setq value (logior (ash value 8) (aref input index))) finally (return value)))))))))) (defun keccak-state-extract-output (state total-bits output-bits) (let* ((lane-width (keccak-lane-width total-bits)) (output-bytes (truncate output-bits 8)) (digest (make-array (list output-bytes) :element-type '(unsigned-byte 8)))) (dotimes (x +keccak-state-columns+) (dotimes (y +keccak-state-rows+) (let ((offset (truncate (* (+ (* y +keccak-state-columns+) x) lane-width) 8))) (unless (>= offset output-bytes) (loop with value = (aref state x y) for index from offset below (min (+ offset (ceiling lane-width 8)) output-bytes) do (setf (aref digest index) (ldb (byte 8 0) value) value (ash value -8))))))) digest)) ;;; ;;; Keccak Constants ;;; (defparameter *keccak-f-round-constants* (make-array (list 24) :element-type '(unsigned-byte 64) :initial-contents '(#x0000000000000001 #x0000000000008082 #x800000000000808a #x8000000080008000 #x000000000000808b #x0000000080000001 #x8000000080008081 #x8000000000008009 #x000000000000008a #x0000000000000088 #x0000000080008009 #x000000008000000a #x000000008000808b #x800000000000008b #x8000000000008089 #x8000000000008003 #x8000000000008002 #x8000000000000080 #x000000000000800a #x800000008000000a #x8000000080008081 #x8000000000008080 #x0000000080000001 #x8000000080008008))) (defun keccak-f-round-constant (total-bits i) (let ((lane-width (keccak-lane-width total-bits))) (ldb (byte lane-width 0) (aref *keccak-f-round-constants* i)))) (defparameter *keccak-f-rotate-offsets* (make-array (list +keccak-state-columns+ +keccak-state-rows+) :element-type '(unsigned-byte 8) :initial-contents '(( 0 36 3 41 18) ( 1 44 10 45 2) (62 6 43 15 61) (28 55 25 21 56) (27 20 39 8 14)))) ;;; ;;; Helper: Rotation ;;; (defun keccak-f-rot (total-bits value offset) (let ((lane-width (keccak-lane-width total-bits))) (logior (ldb (byte lane-width 0) (ash value (mod offset lane-width))) (ash value (- (mod offset lane-width) lane-width))))) ;;; ;;; Single Keccak-f round ;;; (defun keccak-f-round (total-bits a rc) (macrolet ((modref (array &rest indices) `(aref ,array ,@(mapcar #'(lambda (idx) `(mod ,idx +keccak-state-columns+)) indices)))) (let ((c (make-array (list +keccak-state-columns+) :element-type `(keccak-lane ,total-bits))) (d (make-array (list +keccak-state-columns+) :element-type `(keccak-lane ,total-bits))) (b (make-array (list +keccak-state-columns+ +keccak-state-rows+) :element-type `(keccak-lane ,total-bits)))) (dotimes (x +keccak-state-columns+) (setf (aref c x) (logxor (aref a x 0) (aref a x 1) (aref a x 2) (aref a x 3) (aref a x 4)))) (dotimes (x +keccak-state-columns+) (setf (aref d x) (logxor (modref c (1- x)) (keccak-f-rot total-bits (modref c (1+ x)) 1)))) (dotimes (x +keccak-state-columns+) (dotimes (y +keccak-state-rows+) (setf (aref a x y) (logxor (aref a x y) (aref d x))))) (dotimes (x +keccak-state-columns+) (dotimes (y +keccak-state-rows+) (setf (modref b y (+ (* 2 x) (* 3 y))) (keccak-f-rot total-bits (aref a x y) (aref *keccak-f-rotate-offsets* x y))))) (dotimes (x +keccak-state-columns+) (dotimes (y +keccak-state-rows+) (setf (aref a x y) (logxor (aref b x y) (logandc1 (modref b (1+ x) y) (modref b (+ x 2) y)))))) (setf (aref a 0 0) (logxor (aref a 0 0) rc)) a))) ;;; ;;; Keccak-f permutation ;;; (defun keccak-f (total-bits a) (assert (member total-bits '(25 50 100 200 400 800 1600)) (total-bits) "Illegal bit-width ~S!" total-bits) (let ((rounds (+ 12 (* 2 (truncate (log (keccak-lane-width total-bits) 2)))))) (dotimes (i rounds a) (keccak-f-round total-bits a (keccak-f-round-constant total-bits i))))) ;;; ;;; Message Padding for last block ;;; (defun pad-message-to-width (message bit-width) (let ((message-byte-length (length message)) (width-bytes (truncate bit-width 8))) (setq message (adjust-array message (list width-bytes))) (setf (aref message message-byte-length) #x01) (loop for index from (1+ message-byte-length) below width-bytes do (setf (aref message index) #x00) finally (setf (aref message (1- width-bytes)) (logior #x80 (aref message (1- width-bytes)))))) message) ;;; ;;; Main Entry Point: Keccak ;;; (defun keccak (total-bits bit-rate output-bits message) (assert (member total-bits '(25 50 100 200 400 800 1600)) (total-bits) "Illegal bit-width ~S!" total-bits) (let ((state (make-keccak-state total-bits)) (rate-bytes (truncate bit-rate 8)) (length (length message))) (loop for block-offset from 0 to length by rate-bytes do (if (<= (+ block-offset rate-bytes) length) (keccak-state-merge-input state total-bits bit-rate message block-offset) (keccak-state-merge-input state total-bits bit-rate (pad-message-to-width (subseq message block-offset) bit-rate) 0)) (setq state (keccak-f total-bits state))) (let ((digest (keccak-state-extract-output state total-bits output-bits))) (values digest state)))) ;;; ;;; Utility functions ;;; (defun print-digest (digest &optional (stream *standard-output*)) (format stream "~{~2,'0X~}~%" (coerce digest 'list))) (defun pprint-keccak-state (state &optional (stream *standard-output*)) (dotimes (y +keccak-state-rows+) (dotimes (x +keccak-state-columns+) (format stream "~16,'0X " (aref state x y))) (format stream "~%"))) ;;; ;;; Testing ;;; (defun test-with-testsuite (testsuite function) (flet ((from-hex (hex-string) (loop with result = (make-array (list (truncate (length hex-string) 2)) :element-type '(unsigned-byte 8)) for char-index from 0 below (length hex-string) by 2 for result-index upfrom 0 do (setf (aref result result-index) (parse-integer hex-string :start char-index :end (+ char-index 2) :radix 16)) finally (return result))) (to-hex (vector) (format nil "~{~2,'0X~}" (map 'list #'identity vector)))) (loop for count from 1 for (source . digest) in testsuite for binary-source = (from-hex source) for binary-digest = (from-hex digest) for test-digest = (funcall function binary-source) do (format *trace-output* "~2&Test-Case ~D:~% Input: ~A~% Required: ~A~% Returned: ~A~%" count source digest (to-hex test-digest)) when (equalp binary-digest test-digest) do (format *trace-output* " OK~%") else count 1 into failed and do (format *trace-output* " FAILED~%") finally (format *trace-output* "~2&~[All ~D test cases succeeded~:;~:*~D of ~D test cases failed~].~%" failed (1- count)) (return (zerop failed))))) (defun read-testsuite-from-file (file) (with-open-file (in file) (loop with testsuite = nil with skip = nil with length = nil with message = nil for line = (read-line in nil nil) until (null line) do (cond ((zerop (length line))) ((char= (char line 0) #\#)) ((and (>= (length line) 6) (string= line "Len = " :end1 6)) (setq length (ignore-errors (parse-integer line :start 6)) skip (or (null length) (not (zerop (mod length 8)))))) ((and (not skip) (>= (length line) 6) (string= line "Msg = " :end1 6)) (setq message (if (zerop length) "" (subseq line 6)))) ((and (not skip) message (>= (length line) 5) (string= line "MD = " :end1 5)) (push (cons message (subseq line 5)) testsuite))) finally (return (nreverse testsuite))))) (defun test-with-testsuite-from-file (file function) (let ((testsuite (read-testsuite-from-file file))) (test-with-testsuite testsuite function))) (defun test-keccak-msgkat (directory &optional function) (loop with result = t for (filename total-bits bit-rate output-bits) in '(("ShortMsgKAT_224.txt" 1600 1152 224) ("ShortMsgKAT_256.txt" 1600 1088 256) ("ShortMsgKAT_384.txt" 1600 832 384) ("ShortMsgKAT_512.txt" 1600 576 512) ("LongMsgKAT_224.txt" 1600 1152 224) ("LongMsgKAT_256.txt" 1600 1088 256) ("LongMsgKAT_384.txt" 1600 832 384) ("LongMsgKAT_512.txt" 1600 576 512)) do (unless (test-with-testsuite-from-file (merge-pathnames filename directory) (if (null function) (lambda (message) (keccak total-bits bit-rate output-bits message)) (lambda (message) (funcall function total-bits bit-rate output-bits message)))) (setq result nil)) finally (return result)))