[[Start by looking at [source protection].]] [[Follow up by locating information about writing secure Tcl scripts] Finally, consider using interactive prompting rather than hard coding passwords ... [[See [http://expect.nist.gov/doc/bgpasswd.pdf].]] ---- [RJ] 1/16/04: suggests an approach to avoiding embedded passwords in [Expect] scripts. (I work in a NOC - this was a '''BIG''' problem for us.) Feel free to format this for [wiki-reaper] friendly and improve/fix. Works on Sun/[Solaris] 8, [TCL] 8.x and most [UNIX] flavors. ([Don Libes]: Thank you sooo much for Expect - you have no idea how many man-hours you save those who log into 30-100 routers, switches and servers per day.) '''The basic idea''' * Encrypt passwords using UNIX ''[des]'' encryption command (provides 56-bit encyption) * Use a single key for each password or file of passwords, provided by and known only to the user * The ''des'' key becomes the password to the program itself, and is only prompted for at startup * Decrypt the passwords needed, as needed, using the global variable '''key''' '''Procedure to prompt for key''' - shameless pilfer from Mr. Libes' "Exploring Expect" * Takes ''pwprompt'' - ex. "Enter your password: " * Returns only user input stripped of carriage return * Tip: Suggest running this proc twice and comparing input to validate against typos ''only when encrypting passwords''. * Tip: Encrypting the key with the key into the pwdir directory is useful also. Why? As a validation test when the user is just logging into a device, if '''catch utility_decrypt key.enc''' fails, the user entered the wrong key, so prompt again. * User password entry is not displayed when typed ====== proc getpass pwprompt { set oldmode [stty -echo -raw] send_user "\n $pwprompt" set timeout -1 expect_user -re "(.*)\n" send_user "\n" eval stty $oldmode return $expect_out(1,string) } ====== '''Procedure to encrypt device passwords''' * Takes ''pd'' (password list to be encrypted) and ''filename'' (name of file for resulting encrypted password list) as input * Returns nothing * Format of the list of passwords (current and older generations by device type?) depends on how calling program needs them - suggest separated by \n character for readability ====== proc utility_encrypt {pd filename} { global key HOME catch [exec echo "$pd" | des -e -k $key -b > $HOME/pwdir/$filename] return } ====== ---- Incidental remark: I find it handier to write: ====== exec echo "$pd" | des -e -k $key -b > $HOME/pwdir/$filename ====== as: ====== exec des -e -k $key -b > [file join $HOME pwdir $filename] << $pd ====== [[Thank you for that - being a linear thinker, I never would have come up with that. What intrigues me is the "<<" operator. How does it work? Does it only relate to the exec command, are there any other redirectors for exec? Is it interpreted by Tcl, thus saving a Unix call? It looks like the alternative you present would be cross-platform compatible - does the "<<" operator work in Windows?]] ---- '''Procedure to decrypt device passwords''' * Takes ''filename'' (name of password file to decrypt) * Returns ''dpd'' (list of passwords in plaintext) ====== proc utility_decrypt filename { global key HOME catch {exec cat $HOME/pwdir/$filename | des -d -b -k $key} dpd # Some people write the previous command as # catch {exec des -d -b -k $key < $HOME/pwdir/$filename} dpd return $dpd } ====== You can use utility_encrypt in a setup program, and provide functionality that allows new passwords to be added to the encrypted files as they are implemented. Lets assume the encrypted password files are organized by device type and separated with "\n". Then the mechanics of the program look something like this: '''SAMPLE login proc''' ====== proc foo_login_proc {device_name} { global key sid set spawn_id $sid($device_name) set decrypted_all [utility_decrypt foo_passwords.enc] for {set i 0} {$i <= [llength $decrypted_all]} {incr i} { set foo_pws($i) [lindex $decrypted_all $i] } ... #loop through foo_pws array using exp_send/expect until login successful... set i 0 set pw $foo_pws($i) expect { "password for foo: " { exp_send "$pw\n" exp_continue } "login successful" { interact { "invalid password" { incr i set pw $foo_pws($i) exp_continue { "other stuff" { ... } ;#eof, timeout, errors from device, etc. } ... exit } ====== '''SAMPLE Main''' ====== #user launches program to log into a Foo systems Bar device ''device_name'': ... set user [exec whoami] set key [getpass "Enter password for $user: "] spawn -noecho telnet $device_name set sid($device_name) $spawn_id detect_device_prompt ;# an Expect proc that detects this is a foo type device # from it's prompt and launches the foo_login proc ... ====== - [RJ] 1/17/04 ---- [[Q: What's a NOC? A: Network Operations Center [http://searchnetworking.techtarget.com/sDefinition/0,,sid7_gci214122,00.html]. The people that keep ISPs running.]] ---- [PT]: [tcllib] includes [md5crypt] which generates [MD5] based password hashes (as used in Apache and modern Unices). This would be simpler than fiddling with PGP. It also doesn't suffer the password length limitations of standard [crypt]. [RJ] 2/16/04: Thanks [PT]. Ok, I have installed tcllib and tried [md5crypt], but I'm confused on how it works: ====== tclsh> md5crypt::md5crypt rickrick baconbac $1$baconbac$P/Ffd5hoRpvGbNK8SU9st0 ====== There you have it. It seems to dump the key with the encrypted data. Also, how would you decrypt this in an application? I appreciate the help - it would be great to have a pure tcl encryption mechanism for this and md5 128bit would be much stronger than [DES]. Just not sure how to use it and there is no man page for md5crypt with the tcllib dist. [PT] 17-Feb-2004: There is a man page for tcllib's md5crypt. It was contributed at the same time as the package code and will now be in the 1.6 release. Briefly the arguments for this command are a password or secret phrase and a salt. Just as is done for crypt(3) the salt is there to prevent dictionary attacks and is included in the password file. As an example lets compare the OpenBSD ''encrypt'' command which has an md5crypt mode. ====== pat@binky:~% encrypt -m Sekret $1$t/b5AKoN$pf6NnTDXrwTMWxyFDVJ/0. ====== Now with tcllib we can do ====== % package require md5crypt % md5crypt::md5crypt Sekret t/b5AKoN $1$t/b5AKoN$pf6NnTDXrwTMWxyFDVJ/0. ====== Similarly apache's htpasswd program has the same functionality, except that this doesn't use a ''$1'' prefix but ''$apr''. ====== C:\opt\Apache2\bin>htpasswd.exe -nmb pat Sekret pat:$apr1$c75.....$1bIKFn4QP267H8wgQbL.9/ ====== Our equivalent ====== % md5crypt::aprcrypt Sekret c75..... $apr1$c75.....$1bIKFn4QP267H8wgQbL.9/ ====== Finally you don't decrypt passwords. The whole point of the DES based and MD5 based password schemes is to ensure that they are one way. What you do is demonstrate that both sides know the secret by using the same one-way algorithm to generate a hash and comparing the two hashes. [RJ] 02/18/04 - Then sadly, this will not work for this effort. The devices to which we pass the login password will not know what to do with the encrypted password. What we are demonstrating here is a way to safely store router, server, etc. passwords encrypted, then decrypting them as needed to pass to the device. This, without saving the passwords plaintext in the Expect script or any file. This means for Unix, I'm back to [des], [pgp] or [crypt]. Thanks for the suggestion, though. ----- [RJ] 04/19/05 - I have upgraded the encryption method above to now use [PT]'s tcl port of [RC4], thus removing the final '[exec]' in the application. It encrypts ''much'' faster than exec'in DES. Sweet. Thanks Pat. <> Expect | Security