Authentication by formula

WikiDbImage passform.jpg

Summary

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.

See Also

A little login dialog

Description

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]!"
}