rc4.tcl

Go to the documentation of this file.
00001 /*  rc4.tcl - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>*/
00002 /* */
00003 /*  RC4 is a symmetric stream cipher developed by Ron Rivest of RSA Data*/
00004 /*  Security Inc. The algorithm was a trade secret of RSA but was reverse*/
00005 /*  engineered and published to the internet in 1994. This pure Tcl*/
00006 /*  implementation is based on the description of the algorithm.*/
00007 /* */
00008 /*  The algorithm is a pseudo-random number generator with the output of*/
00009 /*  the PRNG being xored with the plaintext stream. Decryption is done*/
00010 /*  by feeding the ciphertext as input with the same key.*/
00011 /* */
00012 /*  $Id: rc4.tcl,v 1.7 2005/12/20 16:19:38 patthoyts Exp $*/
00013 
00014 package require Tcl 8.2
00015 
00016 /*  @mdgen EXCLUDE: rc4c.tcl*/
00017 
00018 namespace ::rc4 {
00019     variable version 1.1.0
00020     variable rcsid {$Id: rc4.tcl,v 1.7 2005/12/20 16:19:38 patthoyts Exp $}
00021 
00022     namespace export rc4
00023 
00024     variable uid
00025     if {![info exists uid]} {
00026          uid =  0
00027     }
00028 }
00029 
00030 /*  RC4Init - create and initialize the RC4 state as an array*/
00031 /* */
00032 ret  ::rc4::RC4Init_Array (type keystr) {
00033     variable uid
00034 
00035     binary scan $keystr c* key
00036     set keylen [llength $key]
00037 
00038     set Key [namespace current]::key[incr uid]
00039     # FRINK: nocheck
00040     variable $Key
00041     upvar #0 $Key state
00042     catch {unset state}
00043 
00044     set state(x) 0
00045     set state(y) 0
00046     for {set cn 0} {$cn < 256} {incr cn} {
00047         set state(s,$cn) $cn
00048     }
00049     set i 0
00050     set j 0
00051     for {set cn 0} {$cn < 256} {incr cn} {
00052         set j [expr {([lindex $key $i] + $state(s,$cn) + $j) % 256}]
00053         set t $state(s,$cn)
00054         set state(s,$cn) $state(s,$j)
00055         set state(s,$j) $t
00056         set i [expr {($i + 1) % $keylen}]
00057     }
00058 
00059     return $Key
00060 }
00061 
00062 /*  RC4 - process the data using the array based state*/
00063 /* */
00064 ret  ::rc4::RC4_Array (type Key , type datastr) {
00065     upvar #0 $Key state
00066     set res {}
00067 
00068     binary scan $datastr c* data
00069     set datalen [llength $data]
00070     
00071     set x $state(x)
00072     set y $state(y)
00073 
00074     for {set cn 0} {$cn < $datalen} {incr cn} {
00075         set x [expr {($x + 1) % 256}]
00076         set y [expr {($state(s,$x) + $y) % 256}]
00077         set t $state(s,$y)
00078         set state(s,$y) $state(s,$x)
00079         set state(s,$x) $t
00080         set i [expr {($state(s,$x) + $state(s,$y)) % 256}]
00081         lappend res [expr {([lindex $data $cn] ^ $state(s,$i)) & 0xFF}]
00082     }
00083     set state(x) $x
00084     set state(y) $y
00085     return [binary format c* $res]
00086 }
00087 
00088 /*  RC4Init - create and initialize the RC4 state as a list.*/
00089 /* */
00090 ret  ::rc4::RC4Init_List (type keystr) {
00091     variable uid
00092 
00093     binary scan $keystr c* key
00094     set keylen [llength $key]
00095 
00096     set Key [namespace current]::key[incr uid]
00097     # FRINK: nocheck
00098     variable $Key
00099     upvar #0 $Key State
00100     catch {unset State}
00101 
00102     set i 0
00103     set j 0
00104     set s {}; #[::struct::list::Liota 256]
00105     for {set n 0} {$n < 256} {incr n} {lappend s $n}
00106     
00107     for {set cn 0} {$cn < 256} {incr cn} {
00108         set j [expr {([lindex $key $i] + [lindex $s $cn] + $j) % 256}]
00109         set t [lindex $s $cn]
00110         lset s $cn [lindex $s $j]
00111         lset s $j $t
00112         set i [expr {($i + 1) % $keylen}]
00113     }
00114     
00115     set State(x) 0
00116     set State(y) 0
00117     set State(s) $s
00118 
00119     return $Key
00120 }
00121 
00122 /*  RC4 - process the data using the list-based state.*/
00123 /* */
00124 ret  ::rc4::RC4_List (type Key , type datastr) {
00125     upvar #0 $Key State
00126     set res {}
00127 
00128     binary scan $datastr c* data
00129     set datalen [llength $data]
00130     
00131     set x $State(x)
00132     set y $State(y)
00133     set s $State(s)
00134 
00135     for {set cn 0} {$cn < $datalen} {incr cn} {
00136         set x [expr {($x + 1) % 256}]
00137         set y [expr {([lindex $s $x] + $y) % 256}]
00138         set t [lindex $s $y]
00139         lset s $y [lindex $s $x]
00140         lset s $x $t
00141         set i [expr {([lindex $s $x] + [lindex $s $y]) % 256}]
00142         lappend res [expr {([lindex $data $cn] ^ [lindex $s $i]) & 0xFF}]
00143     }
00144     set State(x) $x
00145     set State(y) $y
00146     set State(s) $s
00147     return [binary format c* $res]
00148 }
00149 
00150 /*  PRAGMA: nocheck*/
00151 ret  ::rc4::K (type x , type y) {set x}
00152 
00153 /*  Using this compat function for < 8.4 is 2x slower than using arrays.*/
00154 if {[package vcompare [package provide Tcl] 8.4] < 0} {
00155     ret  ::rc4::lset (type var , type index , type arg) {
00156         upvar 1 $var list
00157         set list [::lreplace [K $list [set list {}]] $index $index $arg]
00158     }
00159 }
00160 
00161 ret  ::rc4::RC4Final (type Key) {
00162     upvar #0 $Key state
00163     catch {unset state}
00164     return {}
00165 }
00166 
00167 /*  -------------------------------------------------------------------------*/
00168 /*  Helper to turn binary data into hex format.*/
00169 /* */
00170 ret  ::rc4::Hex (type data) {
00171     binary scan $data H* result
00172     return $result
00173 }
00174 
00175 /*  Demo function for use with Trf transform command to add automatic*/
00176 /*  RC4 encryption to a channel. Illustrates use of [transform]*/
00177 /* */
00178 /*  For instance, to create a file with all ondisk data encrypted:*/
00179 /*    set f [open secretfile r+]*/
00180 /*    transform -attach $f -command [list rc4::Transform $f Secret]*/
00181 /*    puts -nonewline $f yourdata   ;# write to encrypt*/
00182 /*    read $f                       ;# read to decrypt*/
00183 /*    close $f*/
00184 /* */
00185 ret  ::rc4::Transform (type channel , type keystr , type operation , type data) {
00186     set readkey [namespace current]::R$channel
00187     # FRINK: nocheck
00188     variable $readkey
00189     upvar #0 $readkey rk
00190     set writekey [namespace current]::W$channel
00191     # FRINK: nocheck
00192     variable $writekey
00193     upvar #0 $writekey wk
00194     set result {}
00195 
00196     #puts stderr "$operation {$data}"
00197     switch -- $operation {
00198         create/write {
00199             if {[info exists wk]} {
00200                 RCFinal $wk
00201             }
00202             set wk [RC4Init $keystr] 
00203         }
00204         clear/write {}
00205         delete/write {
00206             if {[info exists wk]} {
00207                 RC4Final $wk
00208                 unset wk
00209             }
00210         }
00211         write - flush/write {
00212             if {![info exists wk]} {
00213                 set wk [RC4Init $keystr]
00214             }
00215             set result [RC4 $wk $data] 
00216         }
00217 
00218         create/read {
00219             if {[info exists rk]} {
00220                 RCFinal $rk
00221             }
00222             set rk [RC4Init $keystr] 
00223         }
00224         clear/read {}
00225         delete/read {
00226             if {[info exists rk]} {
00227                 RC4Final $rk
00228                 unset rk
00229             }
00230         }
00231         read - flush/read {
00232             if {![info exists rk]} {
00233                 set rk [RC4Init $keystr]
00234             }
00235             set result [RC4 $rk $data] 
00236         }
00237         
00238         query/ratio {
00239             set result {1 1};           # RC4 is a 1:1 stream cipher.
00240         }
00241         query/maxRead {
00242             set result -1;              # Permit read of any amount
00243         }
00244         default {
00245             # ignore unknown operations.
00246         }
00247     }
00248     return $result
00249 }
00250 
00251 /*  -------------------------------------------------------------------------*/
00252 /*  Description:*/
00253 /*   Pop the nth element off a list. Used in options processing.*/
00254 /* */
00255 ret  ::rc4::Pop (type varname , optional nth =0) {
00256     upvar $varname args
00257     set r [lindex $args $nth]
00258     set args [lreplace $args $nth $nth]
00259     return $r
00260 }
00261 
00262 /*  -------------------------------------------------------------------------*/
00263 /*  Fileevent handler for chunked file hashing.*/
00264 /* */
00265 ret  ::rc4::Chunk (type State) {
00266     upvar #0 $State state
00267     
00268     if {[eof $state(-in)]} {
00269         fileevent $state(-in) readable {}
00270         set state(reading) 0
00271     }
00272     set data [read $state(-in) $state(-chunksize)]
00273     if {[llength $state(-out)] == 0} {
00274         append state(output) [RC4 $state(Key) $data]
00275     } else {
00276         puts -nonewline $state(-out) [RC4 $state(Key) $data]
00277     }
00278     if {!$state(reading) && [llength $state(-command)] != 0} {
00279         Cleanup $State; # cleanup and call users command 
00280     }
00281 }
00282 
00283 
00284 ret  ::rc4::Cleanup (type State) {
00285     upvar #0 $State state
00286     set cmd $state(-command)
00287     set res $state(output)
00288     # If we opened the channel then we should close it too.
00289     if {[string length $state(-infile)] > 0} {
00290         close $state(-in)
00291     }
00292     RC4Final $state(Key)
00293     unset state
00294     if {[llength $cmd] != 0} {
00295         eval $cmd [list $res]
00296     }
00297     return $res
00298 }
00299 
00300 /*  -------------------------------------------------------------------------*/
00301 
00302 ret  ::rc4::rc4 (type args) {
00303     array set opts {-hex 0 -infile {} -in {} -out {} -chunksize 4096
00304         -key {} -command {}}
00305     while {[string match -* [set option [lindex $args 0]]]} {
00306         switch -exact -- $option {
00307             -key        { set opts(-key) [Pop args 1] }
00308             -hex        { set opts(-hex) 1}
00309             -infile     { set opts(-infile) [Pop args 1] }
00310             -in         { set opts(-in) [Pop args 1] }
00311             -out        { set opts(-out) [Pop args 1] }
00312             -chunksize  { set opts(-chunksize) [Pop args 1] }
00313             -command    { set opts(-command) [Pop args 1] }
00314             default {
00315                 if {[llength $args] == 1} { break }
00316                 if {[string compare $option "--"] == 0} { Pop args; break }
00317                 set err [join [lsort [array names opts]] ", "]
00318                 return -code error "bad option $option:\
00319                     must be one of $err"
00320             }
00321         }
00322         Pop args
00323     }
00324 
00325     if {[string length $opts(-key)] < 1} {
00326         return -code error "wrong # args:\
00327             should be \"rc4 ?-hex? -key key -in channel | string\""
00328     }
00329 
00330     if {$opts(-infile) != {}} {
00331         set opts(-in) [open $opts(-infile) r]
00332         fconfigure $opts(-in) -translation binary
00333     }
00334 
00335     set r {}
00336     if {$opts(-in) == {}} {
00337         if {[llength $args] != 1} {
00338             return -code error "wrong # args:\
00339                 should be \"rc4 ?-hex? -key key -in channel | string\""
00340         }
00341 
00342         set Key [RC4Init $opts(-key)]
00343         set r [RC4 $Key [lindex $args 0]]
00344         if {[llength $opts(-command)] != 0} {
00345             eval $opts(-command) [list $r]
00346             set r {}
00347         } elseif {$opts(-out) != {}} {
00348             puts -nonewline $opts(-out) $r
00349             set r {}
00350         }
00351         RC4Final $Key
00352 
00353     } else {
00354 
00355         variable uid
00356         set State [namespace current]::state[incr uid]
00357         upvar #0 $State state
00358         array set state [array get opts]
00359         set state(Key) [RC4Init $opts(-key)]
00360         set state(reading) 1
00361         set state(output) ""
00362         fileevent $opts(-in) readable [list [namespace origin Chunk] $State]
00363         if {[llength $opts(-command)] != 0} {
00364             return {}
00365         } else {
00366             vwait [set State](reading)
00367             set r [Cleanup $State]
00368         }
00369     }
00370 
00371     if {$opts(-hex)} {
00372         set r [Hex $r]
00373     }
00374     return $r
00375 }
00376 
00377 /*  -------------------------------------------------------------------------*/
00378 
00379 ret  ::rc4::SelectImplementation (type impl) {
00380     switch -exact -- $impl {
00381         critcl {
00382             interp alias {} ::rc4::RC4Init {} ::rc4::rc4c_init
00383             interp alias {} ::rc4::RC4     {} ::rc4::rc4c
00384         }
00385         array {
00386             interp alias {} ::rc4::RC4Init {} ::rc4::RC4Init_Array
00387             interp alias {} ::rc4::RC4     {} ::rc4::RC4_Array
00388         }
00389         list {
00390             interp alias {} ::rc4::RC4Init {} ::rc4::RC4Init_List
00391             interp alias {} ::rc4::RC4     {} ::rc4::RC4_List
00392         }
00393         default {
00394             return -code error "invalid implementation \"$impl\":\
00395                 must be one of \"critcl\", \"array\" or \"list\""
00396         }
00397     }
00398 }
00399 
00400 /*  -------------------------------------------------------------------------*/
00401 
00402 /*  Using a list to hold the keystream state is a lot faster than using*/
00403 /*  an array. However, for Tcl < 8.4 we don't have the lset command.*/
00404 /*  Using a compatability lset is slower than using arrays.*/
00405 /*  Obviously, a compiled C version is fastest of all.*/
00406 /*  So lets pick the fastest method we can find...*/
00407 /* */
00408 namespace ::rc4 {
00409     if {[catch {package require tcllibc}]} {
00410         catch {package require rc4c}
00411     }
00412     if {[info commands ::rc4::rc4c] != {}} {
00413         SelectImplementation critcl
00414     } elseif {[package vcompare [package provide Tcl] 8.4] < 0} {
00415         SelectImplementation array
00416     } else {
00417         SelectImplementation list
00418     }
00419 }
00420 
00421 package provide rc4 $::rc4::version
00422 
00423 /*  -------------------------------------------------------------------------*/
00424 /*  Local variables:*/
00425 /*    mode: tcl*/
00426 /*    indent-tabs-mode: nil*/
00427 /*  End:*/
00428 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1