From: "Sandor Szücs" Date: 2008-10-20T04:32:40+09:00 Subject: Re: how to manage passwords It's a month ago but maybe it helps anyway. On 22.09.2008, at 16:58, Junkone wrote: > i just store passwords in text files. i am looking for a secure way of > keeping these passwords and using it thro ruby as my progs need the > pass. > for eg. i send auto IM using bot and need my IM pass to be kept > secure. I don't know for sure if the following can help: http://pog.rubyforge.org/doc/ Otherwise I had a similar problem in a daemon. I solved it like that: M::main_loop() # entry point M::passwd_eventloop() # daemon mode, use an UNIX domain socket M::set_password_interactive() # interactive mode (CLI without socket) M::PasswordStorage saves the username password tuple in the memory by encrypting the tuple. It has all methods you need to get and set passwords. It uses OpenSSL::Cipher::Cipher.new("AES-256-CBC") and a generated random key with key length 52 to save the tuple in memory. If you just need an interactive way to set your passsword then just use the *_interactive() methods from M, without the client and without M::passwd_eventloop(). Disclaimer: I am not a crypto expert and maybe there are a few timing issues in the following code! #server: require 'rubygems' require 'termios' require 'openssl' module M class ShutdownException < StandardError; end @@pw_storage = nil # safe password storage if anyone can read your memory # If you change the next line, don't forget to change it in client.rb, too! PASS_UNIX_SOCK="/tmp/my.sock" class PasswordStorage def initialize @secret = {} @shared_key = {} end # PasswordStorage::get_password returns the cleartext password for a user. def get_password(username=nil) decode(@secret[username], username) if username end def set_password_interactive(username="") # get password from prompt at the start print "provide password for #{username}: " begin M::echo(false) passwd = STDIN.gets.strip ensure M::echo(true) end if passwd.empty? STDERR.puts("An empty password is not allowed.") exit end @secret[username] = encode(passwd,username) passwd="" puts "" end def set_password(user=nil, passwd=nil) if user.nil? or passwd.nil? or user.empty? or passwd.empty? return false else @secret[user] = encode(passwd,user) end end # private Methods of PasswordStorage private def encode(s,username) cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC") @iv = @iv || cipher.random_iv cipher.iv = @iv # for each user we want to have a long random key to encrypt the # user/passwd storage @shared_key[username] = @shared_key[username] || OpenSSL::Random::random_bytes(52) cipher.encrypt @shared_key[username] << "\0" until @shared_key[username].length == 52 cipher.key = @shared_key[username] return (cipher.update(s) << cipher.final) end def decode(key,username) cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC") cipher.iv = @iv cipher.decrypt cipher.key = @shared_key[username] return (cipher.update( key ) << cipher.final) end end # PasswordStorage module_function # getter/setter def getpass(username=nil) @@pw_storage.get_password(username) end def setpass_interactive(username=nil) @@pw_storage = @@pw_storage || M::PasswordStorage.new @@pw_storage.set_password_interactive(username) end def setpass(username=nil, password=nil) @@pw_storage = @@pw_storage || M::PasswordStorage.new @@pw_storage.set_password(username,password) unless username.nil? or password.nil? end def clean_unixsocket File.delete(PASS_UNIX_SOCK) if File.exists? PASS_UNIX_SOCK end # It reads the passwd from a unix domain socket. def passwd_eventloop clean_unixsocket res = "" server = UNIXServer.new(PASS_UNIX_SOCK) begin sock = server.accept_nonblock res = sock.readpartial(4096).split(" ") sock.close rescue Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR, EOFError IO.select([server]) retry end clean_unixsocket # res: [user passwd] if @@pw_storage.nil? b = [] b << setpass(res.shift,res.shift) return b.all? end return false end # M::main_loop # The daemon mode has two states, user/password set and not set. def main_loop begin Signal.trap("INT") {raise ShutdownException } Signal.trap("TERM") {raise ShutdownException } # Comment out, if you want to use interactive mode passwd_eventloop # Uncomment, if you want to use it without reading from socket. #set_password_interactive loop do #do your stuff... end # loop rescue ShutdownException clean_unixsocket end end end # module M # interactive mode #M::setpass_interactive("user") #puts M::getpass("user")[ # daemon mode M::main_loop() # end server # client #!/usr/bin/env ruby -wKU require 'socket' require 'rubygems' require 'termios' PASS_UNIX_SOCK="/tmp/my.sock" unless File.exists? PASS_UNIX_SOCK puts "server is not running" exit end # enable/disable echo on prompt def echo(on=true, masked=false) term = Termios::getattr( $stdin ) if on term.c_lflag |= ( Termios::ECHO | Termios::ICANON ) else # off term.c_lflag &= ~Termios::ECHO term.c_lflag &= ~Termios::ICANON if masked end Termios::setattr( $stdin, Termios::TCSANOW, term ) end print "user:" user = Kernel.gets.strip puts "" print "#{user}'s password: " echo(false) passwd = Kernel.gets.chomp UNIXSocket.open PASS_UNIX_SOCK do |socket| socket.write_nonblock("#{user} #{passwd}") end echo(true) puts "" puts "Thank you for providing the necessary information" # end client regards, Sandor Szücs --