前々からパスワード管理のソフトウェアを導入したいなぁと思ってはいたのですが、1Password の様な管理アプリを購入する意欲も無く、せっかくなのでターミナルで動くアプリを作成する事にしました。

作成

必要なモノは下記の通り。

  • Ruby
  • RubyGems
  • SQlite3

後は、RubyGems 頼りで。

Gemfile

RubyGems はこんな感じで、データベースの扱いは、ActiveRecord に頼ります。後は、暗号化、複合化用の bcrypt と、タスク管理の rake と、DB の sqlite3 です。

Gemfile

source 'https://rubygems.org'

gem 'sqlite3'
gem 'bcrypt'
gem 'activerecord'
gem 'rake'
$ bundle install --path vendor/bundle

Rakefile

次に、Rakefile の設定です。

Rakefile

require 'active_record'

namespace :db do
  MIGRATIONS_DIR = 'db/migrate'

  ActiveRecord::Base.establish_connection(
    adapter: 'sqlite3',
    database: 'database/servises.sqlite'
  )

  desc 'Migrate the database'
  task :migrate do
    ActiveRecord::Migrator.migrate(MIGRATIONS_DIR, ENV['VERSION'] ? ENV['VERSION'].to_i : nil)
  end

  desc 'Roll back the database schema to the previous version'
  task :rollback do
    ActiveRecord::Migrator.rollback(MIGRATIONS_DIR, ENV['STEP'] ? ENV['STEP'].to_i : 1)
    end
end

Rakefile を書いた後は rake db:migrate -vT を実行して rake にタスクが登録されているかを確認しましょう。

データベースのセットアップ

次に、データベースをセットアップします。

db/migrate/001_create_servises.rb

def self.up
    create_table :servises do |t|
      t.string :servise
      t.string :username
      t.string :password

      t.timestamps
    end
  end

  def self.down
    drop_table :passwords
  end
end
$ rake db:migrate

管理用スクリプトの作成

ここまでで、用意は出来ているので、後は書くだけです。思いつくままに書きなぐったので、割と書き直したい衝動に駆られてますが、今は面倒くさいので気がのったら考えます。

#!/usr/bin/ruby

require 'active_record'
require 'openssl'
require 'base64'

ActiveRecord::Base.establish_connection(
  adapter: 'sqlite3',
  database: 'database/passwords.sqlite'
)

class Password < ActiveRecord::Base
  self.table_name = 'passwords'
end

SALT = "SALT" # 塩

def encrypt(salt, password)
  enc = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
  enc.encrypt
  enc.pkcs5_keyivgen(salt)
  return Base64.encode64(enc.update(password) + enc.final).encode('utf-8')
end

def decrypt(salt, password)
  dec = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
  dec.decrypt
  dec.pkcs5_keyivgen(salt)
  return dec.update(Base64.decode64(password.encode('ascii-8bit'))) + dec.final
end

def confirm(string)
  print string + "? y/n "
  gets.chomp
end

def input_servise
  print "input servise name: "
  servise = gets.chomp
  raise if  confirm(servise) != "y"
  servise
rescue
  retry
end

def input_username
  print "input username: "
  username = gets.chomp
  raise if confirm(username) != "y"
  username
end

def input_password
  print "input password: "
  password = gets
  print "confirm password: "
  return false if (password != gets)
  password.chomp
end

def exec_mode?
  puts "Please input exec mode"
  puts "save: register, pass: forgot servise password, any input: fin"
  exec(gets.chomp)
end

def save
  servise = input_servise
  username = input_username
  password = input_password
  model = Password.new(servise: servise,
                       password: encrypt(SALT, password),
                       username: username)
  if model.save
    puts "Saving servise password"
  else
    puts "Error save to servise password"
  end
end

def forgot
  Password.all.each do |p|
    print "servise: #{p.servise}, username: #{p.username}, password: #{decrypt(SALT, p.password.chomp)}\n"
  end
end

def exec(mode)
  case mode
  when /save/
    # register password mode
    save
  when /pass/
    # forgot password mode
    forgot
  else
    puts "finish programme"
  end
  rescue => e
  puts e
  puts "error"
end

if __FILE__ == $0
  exec_mode?
end

入出力が多いので、行数は長くなっていますが、本質の部分 (暗号化、複合化) はちょっとです。Web で公開する (サービスのようなフリーアクセス) でも無く、自身の端末でのみ動作するスクリプトなのであまりセキュリティの事は考えてません。

あとは、色々機能が足りないような気もしますが本当に面倒くさいので、また今度考えます。YAGNI の精神です。

Ruby のお供にどうぞ

u8x7co8