Richard Suchenwirth 2005-07-31: Instead of letting users enter a known password, one can also ask them to apply a "secret" formula to a random set of variable values ("challenge") and enter the result. This way, even though challenge and response are openly transmitted, an onlooker can not easily deduce the underlying formula, as compared with a password.
Here is some code to experiment with this concept. User interaction can be done either via stdio, or a dialog widget. Both return the user input and the random values of the variables.
proc pf_stdio {} { puts -nonewline "Username: " flush stdout gets stdin user foreach var {A B C} {set $var [lpick {1 2 3 4 5 6 7 8 9}]} puts -nonewline "A: $A B: $B C: $C > " flush stdout gets stdin res list $user $A $B $C $res } proc pf_dialog {} { toplevel .w label .w.1 -text Username: entry .w.2 -textvar ::user foreach var {A B C} {set $var [lpick {1 2 3 4 5 6 7 8 9}]} label .w.3 -text "A: $A B: $B C: $C > " entry .w.4 -textvar ::res bind .w.4 <Return> {.w.ok invoke} button .w.ok -text " OK " -command {set ok 1} button .w.c -text Cancel -command {set ok 0} grid .w.1 .w.2 - -sticky ew grid .w.3 .w.4 - -sticky ew grid x .w.ok .w.c -sticky ew raise .w vwait ::ok destroy .w if {!$::ok} return list $::user $A $B $C $::res }
The validation function takes the result list plus the name of an accessor function that returns the formula associated to a user, in a format like (A+B)*(C-1). For simple calculation without paper trail, the formula should contain only the operators +, -, *, in addition to the variable names, and possibly integer constants, and parens. For expr evaluation, $ signs are inserted in the formula before every variable name, with regsub.
proc pf_validate {reslist function} { if {![llength $reslist]} {return 0} foreach {user A B C res} $reslist break set formula [$function $user] if {$formula eq ""} {return 0} regsub -all {([ABC])} $formula {$\1} formula expr ($formula) eq {$res} } #-- Generally useful routine: proc lpick list {lindex $list [expr {int(rand()*[llength $list])}]}
Now testing (either stdio or the dialog, depending on the presence of Tk, that is, whether you call this script with tclsh or wish). The accessor function is very simple, barely sufficient to test for a single user:
proc verysimple user { if {$user eq "suchenwi"} {return "(A+B)*(C-1)"} } set Tk [expr {[package version Tk] ne ""}] set interaction [expr {$Tk? "pf_dialog" : "pf_stdio"}] set hit 0 foreach try {1 2 3} { #-- allow three attempts, as mental arithmetics may go wrong set answer [$interaction] if [pf_validate $answer verysimple] {incr hit; break} } if !$hit {error "Access denied!"; exit 1} #... passform-protected code starts here... if $Tk { pack [label .0 -text "welcome, [lindex $answer 0]!"] raise . } else { puts "welcome, [lindex $answer 0]!" }