Session IDの作り方
どんな風に作ったりするのか疑問だったのでちょっと調べた。
Kahuaの場合
- Kahua-1.0.7.3/src/kahua/session.scm
- Sessionクラス(
)を取り扱うファイル - Kahua-1.0.7.3/src/kahua/gsid.scm
- SessionIDを取り扱うファイル
継続サーバということで、継続のためのContinuation ID、通常のSession IDとして使うState IDの二つのIDを利用するらしい。Continuation IDは再利用するらしい。
- gsid.scm 46行 グローバルセッションの形
(define (make-gsid worker-id body) (format "1-~a-~a" worker-id body))
こんな感じで、1はバージョン、bodyにはContinuation IDもしくはState IDが入るらしい。
- session.scm 66行 初期化
;;; initialization -------------------------------------------- ;; ;; application server must tell this module its worker Id. (define worker-id (make-parameter #f)) (define session-server-id (make-parameter #f)) (define (session-manager-init wid . maybe-ssid) (let-optionals* maybe-ssid ((ssid #f)) (worker-id wid) (when (string? ssid) (session-server-id ssid)) #t))
- session.scm 77行 IDの範囲
(define-constant IDRANGE #x10000000)
- session.scm 134行 Continuation IDの作成
(define (make-cont-key) (check-initialized) (let ((id (make-gsid (worker-id) (number->string (random-integer IDRANGE) 36)))) (if (cont-key->session id) (make-cont-key) id)))
- session.scm 263行 State IDの作成
;; Creates local key. Only used in process-local mode. (define (make-cont-key) (check-initialized) (let ((id (make-gsid (worker-id) (number->string (random-integer IDRANGE) 36)))) (if (cont-key->session id) (make-cont-key) id)))
とりあえず、make-gsidの部分を読むと、random-integerを使ってIDを作成している。random-integerはsrfi-27で定義されている手続きで、Gaucheでは?メルセンヌ・ツイスタ - Wikipediaを使って乱数を生成する。
Rails(2.1.0)の場合
- rails/vendor/rails/actionpack/lib/action_controller/cgi_ext/session.rb
- CGI::Sessionクラス
- SecureRandom
- 標準添付の乱数生成ライブラリ。opensslか/dev/urandomを利用する
Railsではcgiやcgi/sessionモジュールを再定義している。
- Railsのsession.rb
class CGI #:nodoc: # * Expose the CGI instance to session stores. # * Don't require 'digest/md5' whenever a new session id is generated. class Session #:nodoc: begin require 'securerandom' # Generate a 32-character unique id using SecureRandom. # This is used to generate session ids but may be reused elsewhere. def self.generate_unique_id(constant = nil) SecureRandom.hex(16) end rescue LoadError # Generate an 32-character unique id based on a hash of the current time, # a random number, the process id, and a constant string. This is used # to generate session ids but may be reused elsewhere. def self.generate_unique_id(constant = 'foobar') md5 = Digest::MD5.new now = Time.now md5 << now.to_s md5 << String(now.usec) md5 << String(rand(0)) md5 << String($$) md5 << constant md5.hexdigest end end # Make the CGI instance available to session stores. attr_reader :cgi attr_reader :dbman alias_method :initialize_without_cgi_reader, :initialize def initialize(cgi, options = {}) @cgi = cgi initialize_without_cgi_reader(cgi, options) end private # Create a new session id. def create_new_id @new_session = true self.class.generate_unique_id end # ... end
- cgi/session.rb
# Create a new session id. # # The session id is an MD5 hash based upon the time, # a random number, and a constant string. This routine # is used internally for automatically generated # session ids. def create_new_id require 'digest/md5' md5 = Digest::MD5::new now = Time::now md5.update(now.to_s) md5.update(String(now.usec)) md5.update(String(rand(0))) md5.update(String($$)) md5.update('foobar') @new_session = true md5.hexdigest end private :create_new_id
securerandomがあればそれを、そうでなければruby添付のCGI::Session#create_new_idの内容を再定義して利用する。
- securerandomがない場合
"#{現在時刻の文字列表現}#{現在時刻のマイクロ秒}#{乱数}#{PID}#{定数(種)}" # これをMD5で
constantがfoobar固定でない分親切。
Ethnaの場合
/** * セッションを開始する * * @access public * @param int $lifetime セッション有効期間(秒単位, 0ならセッションクッキー) * @return bool true:正常終了 false:エラー */ function start($lifetime = 0, $anonymous = false) { if ($this->session_start) { // we need this? $_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR']; $_SESSION['__anonymous__'] = $anonymous; return true; } if (is_null($lifetime)) { ini_set('session.use_cookies', 0); } else { ini_set('session.use_cookies', 1); } session_set_cookie_params($lifetime); session_id(Ethna_Util::getRandom()); session_start(); $_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR']; $_SESSION['__anonymous__'] = $anonymous; $this->session_start = true; return true; }
// {{{ getRandom /** * ランダムなハッシュ値を生成する * * 決して高速ではないので乱用は避けること * * @access public * @param int $length ハッシュ値の長さ(〜64) * @return string ハッシュ値 */ function getRandom($length = 64) { static $srand = false; if ($srand == false) { list($usec, $sec) = explode(' ', microtime()); mt_srand((float) $sec + ((float) $usec * 100000) + getmypid()); $srand = true; } $value = ""; for ($i = 0; $i < 2; $i++) { // for Linux if (file_exists('/proc/net/dev')) { $rx = $tx = 0; $fp = fopen('/proc/net/dev', 'r'); if ($fp != null) { $header = true; while (feof($fp) === false) { $s = fgets($fp, 4096); if ($header) { $header = false; continue; } $v = preg_split('/[:\s]+/', $s); if (is_array($v) && count($v) > 10) { $rx += $v[2]; $tx += $v[10]; } } } $platform_value = $rx . $tx . mt_rand() . getmypid(); } else { $platform_value = mt_rand() . getmypid(); } $now = strftime('%Y%m%d %T'); $time = gettimeofday(); $v = $now . $time['usec'] . $platform_value . mt_rand(0, time()); $value .= md5($v); } if ($length < 64) { $value = substr($value, 0, $length); } return $value; } // }}}
/proc/net/devを/dev/urandomの代わりにして利用しているように見える。MD5でハッシュ値を求めている。
- /proc/net/dev の内容
Inter-| Receive | Transmit face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed lo: 7545 45 0 0 0 0 0 0 7545 45 0 0 0 0 0 0 eth2:136544103 116979 0 0 0 0 0 0 10058659 73982 0 0 0 0 0 0
- MD5でハッシュ値を求める値
platform_value = "#{/proc/net/dev packets}#{/proc/net/dev bytes}#{乱数}#{PID}" # for linux platform_value = "#{乱数}#{PID}" # other "#{現在時刻の文字列表現}#{現在時刻のマイクロ秒}#{platform_value}#{乱数}" # これをMD5で
見てわかったこと。
乱数性質の無作為性、予測不可能性、再現不可能性のうち、予測不可能性が満たされていればよいのかなと思った。なので、そうなるようにsession idを作ればよい。
てか、こうして見るとKahuaのSession IDってこれでいいのっておもう。メルセンヌツイスタは予測不可能性を持たないらしいし(Wikipediaの知識だけど)。見落としているのかな?
まとめ
kahua
body = "#{36進の乱数}" "#{バージョン}-#{worker-id}-#{body}"