diff options
Diffstat (limited to 'tcllib/modules/md4/md4.tcl')
-rw-r--r-- | tcllib/modules/md4/md4.tcl | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/tcllib/modules/md4/md4.tcl b/tcllib/modules/md4/md4.tcl new file mode 100644 index 0000000..3138d57 --- /dev/null +++ b/tcllib/modules/md4/md4.tcl @@ -0,0 +1,571 @@ +# md4.tcl - Copyright (C) 2003 Pat Thoyts <patthoyts@users.sourceforge.net> +# +# This is a Tcl-only implementation of the MD4 hash algorithm as described in +# RFC 1320 ( http://www.ietf.org/rfc/rfc1320.txt ) +# +# ------------------------------------------------------------------------- +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# ------------------------------------------------------------------------- + +package require Tcl 8.2; # tcl minimum version +catch {package require md4c 1.0}; # tcllib critcl alternative + +# @mdgen EXCLUDE: md4c.tcl + +namespace eval ::md4 { + variable accel + array set accel {critcl 0 cryptkit 0} + + namespace export md4 hmac MD4Init MD4Update MD4Final + + variable uid + if {![info exists uid]} { + set uid 0 + } +} + +# ------------------------------------------------------------------------- + +# MD4Init - create and initialize an MD4 state variable. This will be +# cleaned up when we call MD4Final +# +proc ::md4::MD4Init {} { + variable uid + variable accel + set token [namespace current]::[incr uid] + upvar #0 $token state + + # RFC1320:3.3 - Initialize MD4 state structure + array set state \ + [list \ + A [expr {0x67452301}] \ + B [expr {0xefcdab89}] \ + C [expr {0x98badcfe}] \ + D [expr {0x10325476}] \ + n 0 i "" ] + if {$accel(cryptkit)} { + cryptkit::cryptCreateContext state(ckctx) CRYPT_UNUSED CRYPT_ALGO_MD4 + } + return $token +} + +proc ::md4::MD4Update {token data} { + variable accel + upvar #0 $token state + + if {$accel(critcl)} { + if {[info exists state(md4c)]} { + set state(md4c) [md4c $data $state(md4c)] + } else { + set state(md4c) [md4c $data] + } + return + } elseif {[info exists state(ckctx)]} { + if {[string length $data] > 0} { + cryptkit::cryptEncrypt $state(ckctx) $data + } + return + } + + # Update the state values + incr state(n) [string length $data] + append state(i) $data + + # Calculate the hash for any complete blocks + set len [string length $state(i)] + for {set n 0} {($n + 64) <= $len} {} { + MD4Hash $token [string range $state(i) $n [incr n 64]] + } + + # Adjust the state for the blocks completed. + set state(i) [string range $state(i) $n end] + return +} + +proc ::md4::MD4Final {token} { + upvar #0 $token state + + if {[info exists state(md4c)]} { + set r $state(md4c) + unset state + return $r + } elseif {[info exists state(ckctx)]} { + cryptkit::cryptEncrypt $state(ckctx) "" + cryptkit::cryptGetAttributeString $state(ckctx) \ + CRYPT_CTXINFO_HASHVALUE r 16 + cryptkit::cryptDestroyContext $state(ckctx) + # If nothing was hashed, we get no r variable set! + if {[info exists r]} { + unset state + return $r + } + } + + # RFC1320:3.1 - Padding + # + set len [string length $state(i)] + set pad [expr {56 - ($len % 64)}] + if {$len % 64 > 56} { + incr pad 64 + } + if {$pad == 0} { + incr pad 64 + } + append state(i) [binary format a$pad \x80] + + # RFC1320:3.2 - Append length in bits as little-endian wide int. + append state(i) [binary format ii [expr {8 * $state(n)}] 0] + + # Calculate the hash for the remaining block. + set len [string length $state(i)] + for {set n 0} {($n + 64) <= $len} {} { + MD4Hash $token [string range $state(i) $n [incr n 64]] + } + + # RFC1320:3.5 - Output + set r [bytes $state(A)][bytes $state(B)][bytes $state(C)][bytes $state(D)] + unset state + return $r +} + +# ------------------------------------------------------------------------- +# HMAC Hashed Message Authentication (RFC 2104) +# +# hmac = H(K xor opad, H(K xor ipad, text)) +# +proc ::md4::HMACInit {K} { + + # Key K is adjusted to be 64 bytes long. If K is larger, then use + # the MD4 digest of K and pad this instead. + set len [string length $K] + if {$len > 64} { + set tok [MD4Init] + MD4Update $tok $K + set K [MD4Final $tok] + set len [string length $K] + } + set pad [expr {64 - $len}] + append K [string repeat \0 $pad] + + # Cacluate the padding buffers. + set Ki {} + set Ko {} + binary scan $K i16 Ks + foreach k $Ks { + append Ki [binary format i [expr {$k ^ 0x36363636}]] + append Ko [binary format i [expr {$k ^ 0x5c5c5c5c}]] + } + + set tok [MD4Init] + MD4Update $tok $Ki; # initialize with the inner pad + + # preserve the Ko value for the final stage. + # FRINK: nocheck + set [subst $tok](Ko) $Ko + + return $tok +} + +proc ::md4::HMACUpdate {token data} { + MD4Update $token $data + return +} + +proc ::md4::HMACFinal {token} { + # FRINK: nocheck + variable $token + upvar 0 $token state + + set tok [MD4Init]; # init the outer hashing function + MD4Update $tok $state(Ko); # prepare with the outer pad. + MD4Update $tok [MD4Final $token]; # hash the inner result + return [MD4Final $tok] +} + +# ------------------------------------------------------------------------- + +set ::md4::MD4Hash_body { + variable $token + upvar 0 $token state + + # RFC1320:3.4 - Process Message in 16-Word Blocks + binary scan $msg i* blocks + foreach {X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15} $blocks { + set A $state(A) + set B $state(B) + set C $state(C) + set D $state(D) + + # Round 1 + # Let [abcd k s] denote the operation + # a = (a + F(b,c,d) + X[k]) <<< s. + # Do the following 16 operations. + # [ABCD 0 3] [DABC 1 7] [CDAB 2 11] [BCDA 3 19] + set A [expr {($A + [F $B $C $D] + $X0) <<< 3}] + set D [expr {($D + [F $A $B $C] + $X1) <<< 7}] + set C [expr {($C + [F $D $A $B] + $X2) <<< 11}] + set B [expr {($B + [F $C $D $A] + $X3) <<< 19}] + # [ABCD 4 3] [DABC 5 7] [CDAB 6 11] [BCDA 7 19] + set A [expr {($A + [F $B $C $D] + $X4) <<< 3}] + set D [expr {($D + [F $A $B $C] + $X5) <<< 7}] + set C [expr {($C + [F $D $A $B] + $X6) <<< 11}] + set B [expr {($B + [F $C $D $A] + $X7) <<< 19}] + # [ABCD 8 3] [DABC 9 7] [CDAB 10 11] [BCDA 11 19] + set A [expr {($A + [F $B $C $D] + $X8) <<< 3}] + set D [expr {($D + [F $A $B $C] + $X9) <<< 7}] + set C [expr {($C + [F $D $A $B] + $X10) <<< 11}] + set B [expr {($B + [F $C $D $A] + $X11) <<< 19}] + # [ABCD 12 3] [DABC 13 7] [CDAB 14 11] [BCDA 15 19] + set A [expr {($A + [F $B $C $D] + $X12) <<< 3}] + set D [expr {($D + [F $A $B $C] + $X13) <<< 7}] + set C [expr {($C + [F $D $A $B] + $X14) <<< 11}] + set B [expr {($B + [F $C $D $A] + $X15) <<< 19}] + + # Round 2. + # Let [abcd k s] denote the operation + # a = (a + G(b,c,d) + X[k] + 5A827999) <<< s + # Do the following 16 operations. + # [ABCD 0 3] [DABC 4 5] [CDAB 8 9] [BCDA 12 13] + set A [expr {($A + [G $B $C $D] + $X0 + 0x5a827999) <<< 3}] + set D [expr {($D + [G $A $B $C] + $X4 + 0x5a827999) <<< 5}] + set C [expr {($C + [G $D $A $B] + $X8 + 0x5a827999) <<< 9}] + set B [expr {($B + [G $C $D $A] + $X12 + 0x5a827999) <<< 13}] + # [ABCD 1 3] [DABC 5 5] [CDAB 9 9] [BCDA 13 13] + set A [expr {($A + [G $B $C $D] + $X1 + 0x5a827999) <<< 3}] + set D [expr {($D + [G $A $B $C] + $X5 + 0x5a827999) <<< 5}] + set C [expr {($C + [G $D $A $B] + $X9 + 0x5a827999) <<< 9}] + set B [expr {($B + [G $C $D $A] + $X13 + 0x5a827999) <<< 13}] + # [ABCD 2 3] [DABC 6 5] [CDAB 10 9] [BCDA 14 13] + set A [expr {($A + [G $B $C $D] + $X2 + 0x5a827999) <<< 3}] + set D [expr {($D + [G $A $B $C] + $X6 + 0x5a827999) <<< 5}] + set C [expr {($C + [G $D $A $B] + $X10 + 0x5a827999) <<< 9}] + set B [expr {($B + [G $C $D $A] + $X14 + 0x5a827999) <<< 13}] + # [ABCD 3 3] [DABC 7 5] [CDAB 11 9] [BCDA 15 13] + set A [expr {($A + [G $B $C $D] + $X3 + 0x5a827999) <<< 3}] + set D [expr {($D + [G $A $B $C] + $X7 + 0x5a827999) <<< 5}] + set C [expr {($C + [G $D $A $B] + $X11 + 0x5a827999) <<< 9}] + set B [expr {($B + [G $C $D $A] + $X15 + 0x5a827999) <<< 13}] + + # Round 3. + # Let [abcd k s] denote the operation + # a = (a + H(b,c,d) + X[k] + 6ED9EBA1) <<< s. + # Do the following 16 operations. + # [ABCD 0 3] [DABC 8 9] [CDAB 4 11] [BCDA 12 15] + set A [expr {($A + [H $B $C $D] + $X0 + 0x6ed9eba1) <<< 3}] + set D [expr {($D + [H $A $B $C] + $X8 + 0x6ed9eba1) <<< 9}] + set C [expr {($C + [H $D $A $B] + $X4 + 0x6ed9eba1) <<< 11}] + set B [expr {($B + [H $C $D $A] + $X12 + 0x6ed9eba1) <<< 15}] + # [ABCD 2 3] [DABC 10 9] [CDAB 6 11] [BCDA 14 15] + set A [expr {($A + [H $B $C $D] + $X2 + 0x6ed9eba1) <<< 3}] + set D [expr {($D + [H $A $B $C] + $X10 + 0x6ed9eba1) <<< 9}] + set C [expr {($C + [H $D $A $B] + $X6 + 0x6ed9eba1) <<< 11}] + set B [expr {($B + [H $C $D $A] + $X14 + 0x6ed9eba1) <<< 15}] + # [ABCD 1 3] [DABC 9 9] [CDAB 5 11] [BCDA 13 15] + set A [expr {($A + [H $B $C $D] + $X1 + 0x6ed9eba1) <<< 3}] + set D [expr {($D + [H $A $B $C] + $X9 + 0x6ed9eba1) <<< 9}] + set C [expr {($C + [H $D $A $B] + $X5 + 0x6ed9eba1) <<< 11}] + set B [expr {($B + [H $C $D $A] + $X13 + 0x6ed9eba1) <<< 15}] + # [ABCD 3 3] [DABC 11 9] [CDAB 7 11] [BCDA 15 15] + set A [expr {($A + [H $B $C $D] + $X3 + 0x6ed9eba1) <<< 3}] + set D [expr {($D + [H $A $B $C] + $X11 + 0x6ed9eba1) <<< 9}] + set C [expr {($C + [H $D $A $B] + $X7 + 0x6ed9eba1) <<< 11}] + set B [expr {($B + [H $C $D $A] + $X15 + 0x6ed9eba1) <<< 15}] + + # Then perform the following additions. (That is, increment each + # of the four registers by the value it had before this block + # was started.) + incr state(A) $A + incr state(B) $B + incr state(C) $C + incr state(D) $D + } + + return +} + +proc ::md4::byte {n v} {expr {((0xFF << (8 * $n)) & $v) >> (8 * $n)}} +proc ::md4::bytes {v} { + #format %c%c%c%c [byte 0 $v] [byte 1 $v] [byte 2 $v] [byte 3 $v] + format %c%c%c%c \ + [expr {0xFF & $v}] \ + [expr {(0xFF00 & $v) >> 8}] \ + [expr {(0xFF0000 & $v) >> 16}] \ + [expr {((0xFF000000 & $v) >> 24) & 0xFF}] +} + +# 32bit rotate-left +proc ::md4::<<< {v n} { + return [expr {((($v << $n) \ + | (($v >> (32 - $n)) \ + & (0x7FFFFFFF >> (31 - $n))))) \ + & 0xFFFFFFFF}] +} + +# Convert our <<< pseudo-operator into a procedure call. +regsub -all -line \ + {\[expr {(.*) <<< (\d+)}\]} \ + $::md4::MD4Hash_body \ + {[<<< [expr {\1}] \2]} \ + ::md4::MD4Hash_body + +# RFC1320:3.4 - function F +proc ::md4::F {X Y Z} { + return [expr {($X & $Y) | ((~$X) & $Z)}] +} + +# Inline the F function +regsub -all -line \ + {\[F (\$[ABCD]) (\$[ABCD]) (\$[ABCD])\]} \ + $::md4::MD4Hash_body \ + {( (\1 \& \2) | ((~\1) \& \3) )} \ + ::md4::MD4Hash_body + +# RFC1320:3.4 - function G +proc ::md4::G {X Y Z} { + return [expr {($X & $Y) | ($X & $Z) | ($Y & $Z)}] +} + +# Inline the G function +regsub -all -line \ + {\[G (\$[ABCD]) (\$[ABCD]) (\$[ABCD])\]} \ + $::md4::MD4Hash_body \ + {((\1 \& \2) | (\1 \& \3) | (\2 \& \3))} \ + ::md4::MD4Hash_body + +# RFC1320:3.4 - function H +proc ::md4::H {X Y Z} { + return [expr {$X ^ $Y ^ $Z}] +} + +# Inline the H function +regsub -all -line \ + {\[H (\$[ABCD]) (\$[ABCD]) (\$[ABCD])\]} \ + $::md4::MD4Hash_body \ + {(\1 ^ \2 ^ \3)} \ + ::md4::MD4Hash_body + +# Define the MD4 hashing procedure with inline functions. +proc ::md4::MD4Hash {token msg} $::md4::MD4Hash_body +unset ::md4::MD4Hash_body + +# ------------------------------------------------------------------------- + +if {[package provide Trf] != {}} { + interp alias {} ::md4::Hex {} ::hex -mode encode -- +} else { + proc ::md4::Hex {data} { + binary scan $data H* result + return [string toupper $result] + } +} + +# ------------------------------------------------------------------------- + +# LoadAccelerator -- +# +# This package can make use of a number of compiled extensions to +# accelerate the digest computation. This procedure manages the +# use of these extensions within the package. During normal usage +# this should not be called, but the test package manipulates the +# list of enabled accelerators. +# +proc ::md4::LoadAccelerator {name} { + variable accel + set r 0 + switch -exact -- $name { + critcl { + if {![catch {package require tcllibc}] + || ![catch {package require md4c}]} { + set r [expr {[info commands ::md4::md4c] != {}}] + } + } + cryptkit { + if {![catch {package require cryptkit}]} { + set r [expr {![catch {cryptkit::cryptInit}]}] + } + } + #trf { + # if {![catch {package require Trf}]} { + # set r [expr {![catch {::md4 aa} msg]}] + # } + #} + default { + return -code error "invalid accelerator package:\ + must be one of [join [array names accel] {, }]" + } + } + set accel($name) $r +} + +# ------------------------------------------------------------------------- + +# Description: +# Pop the nth element off a list. Used in options processing. +# +proc ::md4::Pop {varname {nth 0}} { + upvar $varname args + set r [lindex $args $nth] + set args [lreplace $args $nth $nth] + return $r +} + +# ------------------------------------------------------------------------- + +# fileevent handler for chunked file hashing. +# +proc ::md4::Chunk {token channel {chunksize 4096}} { + # FRINK: nocheck + variable $token + upvar 0 $token state + + if {[eof $channel]} { + fileevent $channel readable {} + set state(reading) 0 + } + + MD4Update $token [read $channel $chunksize] +} + +# ------------------------------------------------------------------------- + +proc ::md4::md4 {args} { + array set opts {-hex 0 -filename {} -channel {} -chunksize 4096} + while {[string match -* [set option [lindex $args 0]]]} { + switch -glob -- $option { + -hex { set opts(-hex) 1 } + -file* { set opts(-filename) [Pop args 1] } + -channel { set opts(-channel) [Pop args 1] } + -chunksize { set opts(-chunksize) [Pop args 1] } + default { + if {[llength $args] == 1} { break } + if {[string compare $option "--"] == 0 } { Pop args; break } + set err [join [lsort [array names opts]] ", "] + return -code error "bad option $option:\ + must be one of $err" + } + } + Pop args + } + + if {$opts(-filename) != {}} { + set opts(-channel) [open $opts(-filename) r] + fconfigure $opts(-channel) -translation binary + } + + if {$opts(-channel) == {}} { + + if {[llength $args] != 1} { + return -code error "wrong # args:\ + should be \"md4 ?-hex? -filename file | string\"" + } + set tok [MD4Init] + MD4Update $tok [lindex $args 0] + set r [MD4Final $tok] + + } else { + + set tok [MD4Init] + # FRINK: nocheck + set [subst $tok](reading) 1 + fileevent $opts(-channel) readable \ + [list [namespace origin Chunk] \ + $tok $opts(-channel) $opts(-chunksize)] + vwait [subst $tok](reading) + set r [MD4Final $tok] + + # If we opened the channel - we should close it too. + if {$opts(-filename) != {}} { + close $opts(-channel) + } + } + + if {$opts(-hex)} { + set r [Hex $r] + } + return $r +} + +# ------------------------------------------------------------------------- + +proc ::md4::hmac {args} { + array set opts {-hex 0 -filename {} -channel {} -chunksize 4096} + while {[string match -* [set option [lindex $args 0]]]} { + switch -glob -- $option { + -key { set opts(-key) [Pop args 1] } + -hex { set opts(-hex) 1 } + -file* { set opts(-filename) [Pop args 1] } + -channel { set opts(-channel) [Pop args 1] } + -chunksize { set opts(-chunksize) [Pop args 1] } + default { + if {[llength $args] == 1} { break } + if {[string compare $option "--"] == 0 } { Pop args; break } + set err [join [lsort [array names opts]] ", "] + return -code error "bad option $option:\ + must be one of $err" + } + } + Pop args + } + + if {![info exists opts(-key)]} { + return -code error "wrong # args:\ + should be \"hmac ?-hex? -key key -filename file | string\"" + } + + if {$opts(-filename) != {}} { + set opts(-channel) [open $opts(-filename) r] + fconfigure $opts(-channel) -translation binary + } + + if {$opts(-channel) == {}} { + + if {[llength $args] != 1} { + return -code error "wrong # args:\ + should be \"hmac ?-hex? -key key -filename file | string\"" + } + set tok [HMACInit $opts(-key)] + HMACUpdate $tok [lindex $args 0] + set r [HMACFinal $tok] + + } else { + + set tok [HMACInit $opts(-key)] + # FRINK: nocheck + set [subst $tok](reading) 1 + fileevent $opts(-channel) readable \ + [list [namespace origin Chunk] \ + $tok $opts(-channel) $opts(-chunksize)] + vwait [subst $tok](reading) + set r [HMACFinal $tok] + + # If we opened the channel - we should close it too. + if {$opts(-filename) != {}} { + close $opts(-channel) + } + } + + if {$opts(-hex)} { + set r [Hex $r] + } + return $r +} + +# ------------------------------------------------------------------------- + +# Try and load a compiled extension to help. +namespace eval ::md4 { + variable e {} + foreach e {critcl cryptkit} { if {[LoadAccelerator $e]} { break } } + unset e +} + +package provide md4 1.0.6 + +# ------------------------------------------------------------------------- +# Local Variables: +# mode: tcl +# indent-tabs-mode: nil +# End: + + |