暗号化したパスワードを保存名から呼び出して複合化し、クリッポボードにコピー

ネトゲのパスワードを、
乱数で発生させた8文字くらいの文字列にしてるんだけど、
セキュリティが甘いというかよくアカウントハックを聞くので、
定期的にパスワードを変えている。

が、
乱数発生した文字列を覚えるのは正直最初3回くらいは覚えてたけど、
もう覚えられんとなり、txtファイルにプレーン状態のパスワードを保存して、
ログイン前に開いてコピペしてた。
別にEvernoteとかでもいいっちゃいいんだけど、結局プレーンな状態で保存するのはなーと。

そしてもう1つ、最近さっぱりやってないけれど、
ネトゲニコニコ生放送でやったりしてたんですが、
まさかそんな最中にプレーンテキストなパスワードを画面に映してしまったらオワタすぎるwww

ということで、どうにか出来ないかなと考えていた。


疑問&手順1.クリップボードにコピーってRubyから出来るの?

参考
[Win32::Clipboard.set_data("hoge moge poo")]
http://tobysoft.net/wiki/index.php?Ruby%2F%A5%AF%A5%EA%A5%C3%A5%D7%A5%DC%A1%BC%A5%C9%A4%F2%BB%C8%A4%A6%CA%FD%CB%A1


gem install win32-clipboad #入っていなければ

require 'win32/clipboard'
Win32::Clipboard.set_data("aiueo")

これをirbで実行して、テキストエディタにCtrl+Vしたら、クリップボードにコピー出来た。
これは簡単!
Macとかだとどうなるかはわかりませんが。



疑問&手順2. 暗号化と複合化どうしよう

色々あった中、こちらのソースをほぼ拝借
[NAMAKESUGI | [Rails][Ruby]Blowfishで暗号化してデータをDBに保存する[OpenSSL]]
http://namakesugi.blog42.fc2.com/blog-entry-71.html

ここはこうかいた方が好きかなってのをちょっと変えたりしつつ。



疑問&手順3. 保存どうしよう

MySQLでやるより使ったことないのがいいな、お手軽さが売りなSQLite使ってみようかな。
参考
[SQLite/Ruby - Ruby]
http://www.gesource.jp/programming/ruby/database/sqlite.html


gem install sqlite3-ruby #Ruby1.8.7以上であること


require 'sqlite3'
db = SQLite3::Database.new("data.db") #同ディレクトリにDB作成。作成済みの場合開く
db.results_as_hash = true #select結果は配列で値の返るので、Hash形式にする(重要)
#処理
sql = "XXXX" #SQL
db.execute(sql) #select系なら record = を先につけるとよし
#処理
db.close
基本はこんな感じ


疑問&手順4. テーブル設計どうしよう

・id INTEGER 主キー
・name VARCHAR(255) NOT NULL 保存名。呼び出し時これでwhereかける
・cipher_password VARCHAR(255) 暗号化パスワード(16進数)。呼び出してから複合化する
・salt CHAR(8) 重複パスワードが同じ暗号化にならないように。乱数8字固定


CREATE TABLE passwoords(
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
cipher_password VARCHAR(255) NOT NULL,
salt CHAR(8) NOT NULL
)

こんな感じかな。
SQLiteは、INTEGER PRIMARY KEYとするとAUTO INCREMENTに勝手になるらしい。
nameカラムは重複を許すと呼び出し時に困るのでユニークに。
この時MySQLでいうところのUNIQUEインデックスが勝手に張られるかまでは調べてなかった。

必要なら


CREATE UNIQUE INDEX password_name_index ON passwords(name)
もしたほうが良いかも。
ただ、複合インデックスじゃなくて単インデックスなら勝手に内部的にやってんでしょ?と過信して思い込む(こういう事を繰り返すと罠にはまるんだけど)


疑問&手順5. 乱数発生どうしよう

疑問2で参考にした暗号化/複合化のソースで使っている


require 'openssl'
OpenSSL::Random.random_bytes(return_size) #return_sizeはバイト数
だと、SJISじゃないだけだろうけどWindowsのコンソールで文字化けして怖かったので自分で作成。
やり様はいくらでもあるけれどもね。


seed = ("a".."z").to_a
seed.concat(("A".."Z").to_a)
seed.concat(("0".."9").to_a)
seed.concat(
%w(! " # $ % & ' - = ^ ~ | @ ` [ { ] } : * ; + / ? _ , < . >)
)

SEEDS = seed

def create_random_str(bytes = DEFAULT_SIZE)
time_seed = Time.now.to_i.to_s
10.times do
time_seed << rand(10).to_s
end
srand(time_seed.to_i)

salt = ""
bytes.times do
salt << SEEDS.rand
end
return salt
end

無駄は多いけどこんな感じにした。
10.times{ p Time.now.to_i } とかすれば確認できるけど
Time.now.to_iだと秒で10桁くらいの時間を表すようなので、
瞬間的に2回やったらsrandで同じのが取得できてしまう!
というわけで文字列化して乱数で数値を表す文字列を10文字付け足した。
最初からそれで20文字とかでいい気がするのは気にしたらいけないと思う。

乱数で得られる文字は


a〜z,
A〜Z,
0〜9,
!"#$%&'-=^~|@`[{]}:*;+/?_,<.>
からいずれか。
記号に()入れてないのは%w()内でエスケープしてまで入れるのも…と思ったので。
記号に\入れてないのは、\入れてエスケープ関連で何か起きても困るので除外した。


上記メソッドで
64文字の乱数を発生させて、KEYとしてプログラム側に所持させた。
暗号化・複合化時に使う、salt以外の物としてプログラム側で長めのを保持。


疑問&手順6. 残り1

プログラム実行時に、
パスワードをDBに新規登録(regist)させるか、
パスワードをDBから読み込む(read)させるか選択させるために、
getsでコマンドラインから入力させるメニューと、その受付を簡単に作成。


疑問&手順6. 残り2 登録時

1.保存名入力
2.プレーンテキストなパスワード入力

暗号化ざっくり


cipher_password = 暗号化メソッド("プレーンパスワード", "新規発生8文字乱数salt", "64文字内部所持乱数KEY")

db保存


sql = "INSERT INTO password(name, cipher_password, salt) VALUES(?, ?, ?)"
db.execute(sql, name, cipher_password, salt)
SQLiteプレースホルダはこんな感じで出来るらしい。

これで新規追加。
idはちゃんとインクリメントされた。


疑問&手順7. 残り3 読み出し時

dbから呼び出し


password_name = "xxxxx" #getsで入力された保存名

db.results_as_hash = true
sql = "SELECT * FROM passwords WHERE name = ? LIMIT 1"
password = db.execute(sql,password_name)

db.results_as_hash = true忘れず。
UNIQUEかけてるからLIMIT 1いらないけど念のため。
実はここで保存されてないパスワード名でのnilエラー対応を軽くしか入れてないからソースを全部見せられないなんていえない


復号化ざっくり


plain_password = 復号化メソッド(password["name"], password["salt"], "64文字内部所持乱数KEY")

Win32::Clipboard.set_data(plain_password)

これでパスワードが見えることなくクリップボードにコピーされるので、
スクリプト起動

新規登録(regist), 読み込み(read)
実行モード入力: read

保存名: ネトゲパス

クリップボードにコピーされてるー!(。A。)

ってことが出来ましたっと。

結局その状態でテキストエディタ開いたりCtrl+Vしたら人に見える可能性はあるけれどw
あと保存時はgetsでおもむろにプレーンパスワード入力するから、後ろに人が居たら大変ww

で、一番の問題は、
SQLiteがファイルに保存する形式で同ディレクトリに置いちゃってるから、
ディレクトリまるごと持ってかれてSQLiteのViewerソフトで保存名見て、
Rubyスクリプト実行されて保存名からプレーンパスワード読み込まれたら結局ダメじゃんとww

それはEvernoteで人が後ろに立ってたり、
そもそもアカウントハックされたら〜とかと大差はない気がしますが・・・

ある意味暗号化・複合化するスクリプトコードと、
SQLiteのデータベースファイルと、
salt以外に使うKEYもコード内に居るんだからこれもこれで間抜けな話だよなと作ってから思うのでした。


まぁでも仮にニコニコ生放送とかで映っても、パスワード漏洩の心配はなくなったって事で、主目的は果たした。
SQLiteの実践と軽い勉強、クリップボードにコピー出来るかもわかったし!


実行画像だけ貼っておく。


登録実行


暗号化確認(SQLite Database Browserにて)
最後2行がどちらもプレーンパスワードqwerty
見事に同じパスワードでも
15b169cdc2a0004a
4d4f882d738102d5
と同一にならずに保存出来ている事がわかる


呼び出し実行
対応して入れてなかったエラーも見せますor2
で、irbクリップボードに複合化パスワードがコピーされてることを確認。
事前にコピーはしてないです。


と、こんな日曜日の適当なノリでの軽プログラミングでした(´・ω・`)

今作りたいWebサービスのアイデアがあるので、
そろそろRails3とかRuby1.9系とか、
jpmobileとかgeokitとかプラグインを使って位置情報系の勉強をしようかなと思ってます。
あとはHTML5とかCSS3も勉強したい。coofeescriptなんかも!