Last active
July 24, 2021 14:14
-
-
Save melwyn95/338a13d6cc0a9d4cd7521c9f9db383e9 to your computer and use it in GitHub Desktop.
SHA-1 implemented in Racket
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#lang racket | |
;;;;;;;;;;;;;;;; Hex conversion ;;;;;;;;;;;;;;;; | |
(define (hex-char-to-bin-list c) | |
(cond [(eq? #\0 c) (list 0 0 0 0)] | |
[(eq? #\1 c) (list 0 0 0 1)] | |
[(eq? #\2 c) (list 0 0 1 0)] | |
[(eq? #\3 c) (list 0 0 1 1)] | |
[(eq? #\4 c) (list 0 1 0 0)] | |
[(eq? #\5 c) (list 0 1 0 1)] | |
[(eq? #\6 c) (list 0 1 1 0)] | |
[(eq? #\7 c) (list 0 1 1 1)] | |
[(eq? #\8 c) (list 1 0 0 0)] | |
[(eq? #\9 c) (list 1 0 0 1)] | |
[(eq? #\A c) (list 1 0 1 0)] | |
[(eq? #\B c) (list 1 0 1 1)] | |
[(eq? #\C c) (list 1 1 0 0)] | |
[(eq? #\D c) (list 1 1 0 1)] | |
[(eq? #\E c) (list 1 1 1 0)] | |
[(eq? #\F c) (list 1 1 1 1)] | |
[#t (error "hex-char-to-bin-list: invalid hex char")])) | |
(define (hex-string-to-vector s) | |
(list->vector (append* (map hex-char-to-bin-list (string->list s))))) | |
(define (vector-to-hex n) | |
(cond [(equal? '#(0 0 0 0) n) "0"] | |
[(equal? '#(0 0 0 1) n) "1"] | |
[(equal? '#(0 0 1 0) n) "2"] | |
[(equal? '#(0 0 1 1) n) "3"] | |
[(equal? '#(0 1 0 0) n) "4"] | |
[(equal? '#(0 1 0 1) n) "5"] | |
[(equal? '#(0 1 1 0) n) "6"] | |
[(equal? '#(0 1 1 1) n) "7"] | |
[(equal? '#(1 0 0 0) n) "8"] | |
[(equal? '#(1 0 0 1) n) "9"] | |
[(equal? '#(1 0 1 0) n) "a"] | |
[(equal? '#(1 0 1 1) n) "b"] | |
[(equal? '#(1 1 0 0) n) "c"] | |
[(equal? '#(1 1 0 1) n) "d"] | |
[(equal? '#(1 1 1 0) n) "e"] | |
[(equal? '#(1 1 1 1) n) "f"] | |
[#t (error "vector-to-hex: un-reachable")])) | |
;;;;;;;;;;;;;;;; Binary operations ;;;;;;;;;;;;;;;; | |
(define (b-xor m n) (if (= m n) 0 1)) | |
(define (b-and m n) (if (and (= m 1) (= n 1)) 1 0)) | |
(define (b-or m n) (if (or (= m 1) (= n 1)) 1 0)) | |
(define (b-not x) (if (= x 1) 0 1)) | |
(define (vector-bitwise-op xs ys op) | |
(letrec ([f (lambda (i) | |
(if (= i 32) | |
null | |
(cons (op (vector-ref xs i) (vector-ref ys i)) (f (+ i 1)))))]) | |
(list->vector (f 0)))) | |
(define (vector-xor xs ys) | |
(vector-bitwise-op xs ys b-xor)) | |
(define (vector-and xs ys) | |
(vector-bitwise-op xs ys b-and)) | |
(define (vector-or xs ys) | |
(vector-bitwise-op xs ys b-or)) | |
(define (vector-not xs) | |
(letrec ([f (lambda (i) | |
(if (= i 32) | |
null | |
(cons (b-not (vector-ref xs i)) (f (+ i 1)))))]) | |
(list->vector (f 0)))) | |
(define (vector-reverse xs) | |
(list->vector (reverse (vector->list xs)))) | |
(define (vector-add-2 xs ys) | |
(letrec ([f (lambda (xs ys i rem) | |
(if (= i 32) | |
null | |
(let ([v (+ (vector-ref xs i) (vector-ref ys i) rem)]) | |
(cond [(= v 0) (cons 0 (f xs ys (+ i 1) 0))] | |
[(= v 1) (cons 1 (f xs ys (+ i 1) 0))] | |
[(= v 2) (cons 0 (f xs ys (+ i 1) 1))] | |
[(= v 3) (cons 1 (f xs ys (+ i 1) 1))] | |
[#f (error "unreachable")]))))]) | |
(list->vector (reverse (f (vector-reverse xs) (vector-reverse ys) 0 0))))) | |
(define (foldl fn init xs) | |
(letrec ([f (lambda (acc ys) | |
(if (null? ys) | |
acc | |
(f (fn acc (car ys)) (cdr ys))))]) | |
(f init xs))) | |
(define vector-add (lambda xs | |
(foldl vector-add-2 (make-vector 32 0) xs))) | |
(define (vector-left-rotate xs n) | |
(let* ([size (vector-length xs)] | |
[idx (remainder n size)]) | |
(let-values ([(a b) (vector-split-at xs idx)]) | |
(vector-append b a)))) | |
;;;;;;;;;;;;;;;; SHA-1 specific ;;;;;;;;;;;;;;;; | |
(define (bytes-to-vector bs) | |
(list->vector | |
(append* | |
(map | |
(lambda (b) | |
(map (lambda (c) | |
(- (char->integer c) 48)) | |
(string->list (~r b #:base 2 #:min-width 8 #:pad-string "0")))) | |
bs)))) | |
(define (partition bs n) | |
(letrec ([size (vector-length bs)] | |
[f (lambda (xs) | |
(cond [(= (vector-length xs) 0) null] | |
[#t (let-values ([(c n) (vector-split-at xs n)]) | |
(cons c (f n)))]))]) | |
(f bs))) | |
(define (preprocess msg) | |
(let* ([bs (bytes->list (string->bytes/utf-8 msg))] | |
[size (* (string-utf-8-length msg) 8)] | |
[zeros (make-vector (- (* (ceiling (/ (+ size 1) 512)) 512) (+ size 1) 64))] | |
[padded (vector-append (bytes-to-vector bs) (make-vector 1 1) zeros)] | |
[final (vector-append padded (bytes-to-vector (bytes->list (integer->integer-bytes size 8 #f #t))))]) | |
(partition final 512))) | |
(define (extend-w s e vec) | |
(if (= s e) | |
vec | |
(let ([wi (vector-left-rotate | |
(vector-xor (vector-ref vec (- s 3)) | |
(vector-xor (vector-ref vec (- s 8)) | |
(vector-xor (vector-ref vec (- s 14)) | |
(vector-ref vec (- s 16))))) | |
1)]) | |
(extend-w (+ s 1) e (vector-append vec (make-vector 1 wi)))))) | |
(define (process-chunk h0 h1 h2 h3 h4 chunk) | |
(let ([w (extend-w 16 80 (list->vector (partition chunk 32)))]) | |
(letrec ([fk (lambda (a b c d e i) | |
(cond [(and (>= i 0) (<= i 19)) | |
(values | |
;f = (b and c) or ((not b) and d) | |
(vector-or (vector-and b c) | |
(vector-and (vector-not b) | |
d)) | |
(hex-string-to-vector "5A827999"))] | |
[(and (>= i 20) (<= i 39)) | |
(values | |
;f = b xor c xor d | |
(vector-xor b (vector-xor c d)) | |
(hex-string-to-vector "6ED9EBA1"))] | |
[(and (>= i 40) (<= i 59)) | |
(values | |
;f = (b and c) or (b and d) or (c and d) | |
(vector-or (vector-and b c) | |
(vector-or (vector-and b d) | |
(vector-and c d))) | |
(hex-string-to-vector "8F1BBCDC"))] | |
[(and (>= i 60) (<= i 79)) | |
(values | |
;f = b xor c xor d | |
(vector-xor b (vector-xor c d)) | |
(hex-string-to-vector "CA62C1D6"))] | |
[#t (error "unreachable")]))] | |
[loop (lambda (a b c d e i) | |
(if (= i 80) | |
(values a b c d e) | |
(let-values ([(f k) (fk a b c d e i)]) | |
(let ([temp (vector-add (vector-left-rotate a 5) f e k (vector-ref w i))]) | |
(loop temp a (vector-left-rotate b 30) c d (+ i 1))))))]) | |
(let-values ([(a b c d e) (loop h0 h1 h2 h3 h4 0)]) | |
(values (vector-add h0 a) | |
(vector-add h1 b) | |
(vector-add h2 c) | |
(vector-add h3 d) | |
(vector-add h4 e))) | |
))) | |
(define (sha1 msg) | |
(let ([h0 (hex-string-to-vector "67452301")] | |
[h1 (hex-string-to-vector "EFCDAB89")] | |
[h2 (hex-string-to-vector "98BADCFE")] | |
[h3 (hex-string-to-vector "10325476")] | |
[h4 (hex-string-to-vector "C3D2E1F0")] | |
[chunks (preprocess msg)]) | |
(letrec ([f (lambda (h0 h1 h2 h3 h4 cs) | |
(if (null? cs) | |
(values h0 h1 h2 h3 h4) | |
(let-values ([(h0_ h1_ h2_ h3_ h4_) (process-chunk h0 h1 h2 h3 h4 (car cs))]) | |
(f h0_ h1_ h2_ h3_ h4_ (cdr cs)))))]) | |
(let-values ([(h0 h1 h2 h3 h4) (f h0 h1 h2 h3 h4 chunks)]) | |
(let ([bit-hash (vector-append h0 (vector-append h1 (vector-append h2 (vector-append h3 h4))))]) | |
(string-append* (map vector-to-hex (partition bit-hash 4)))))))) | |
;;;;;;;;;;;;;;;; Tests ;;;;;;;;;;;;;;;; | |
(require rackunit) | |
(require rackunit/text-ui) | |
(define tests | |
(test-suite | |
"Test SHA-1" | |
(check-equal? (sha1 "") "da39a3ee5e6b4b0d3255bfef95601890afd80709") | |
(check-equal? (sha1 "abc") "a9993e364706816aba3e25717850c26c9cd0d89d") | |
(check-equal? (sha1 "The quick brown fox jumps over the lazy dog") "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12") | |
(check-equal? (sha1 (make-string 1000 #\a)) "291e9a6c66994949b57ba5e650361e98fc36b1ba") | |
(check-equal? (sha1 (make-string 10000 #\a)) "a080cbda64850abb7b7f67ee875ba068074ff6fe"))) | |
(run-tests tests) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment