SQLiteを使ってパスワード管理ソフトを作ってみる
はじめに
昨今は何かしらの形式でパスワード管理をすることも多いだろう。かくいう私もパスワード管理ソフトを使っているが、なんとなく命を握られている感じがして気に入らない。というのは建前で、SQLiteをC++から直接利用してみたかったため、SQLiteを用いてC++でパスワード管理ソフトを作ってみる。
今回作成をしたソースコードは以下で公開している。
SQLiteのC++ラッパーの実装
SQLiteの導入
SQLite自体はC言語で実装がされているため、C++から利用する分には非常に容易である。今回は以下のダウンロードページにあるバージョン3.45.1のソースをビルドして利用をしている。
現バージョン時点では、sqlite3.c
をビルドしてリンク&sqlite3.h
のインクルードをするだけで簡単に利用ができる。SQLiteのAPIを利用する際は主にドキュメントを参照することになるが、利用をする際は特に以下のドキュメントが参考になる。
ラッパーの構造
ここでのラッパーは、単純なSQLiteのC言語のインターフェースをC++によるオブジェクト指向なインターフェースに変換して利用しやすくするだけとして、高度な抽象化は行わないものとする。今回主に作成したクラスは以下の3つの機能である。なお、ここでのviewというのはC++のRangeにおけるviewを指す。
SQLiteConnection/SQLite
:SQLiteとのコネクションを管理するクラスSQLiteStmtControl/SQLiteStmt
:プリペアドステートメントを管理するクラスであり、インスタンスはSQLiteConnection/SQLite
から生成されるSQLiteIterator/SQLiteView
:SQLの実行結果に関するviewとイテレータであり、インスタンスはSQLiteStmtControl/SQLiteStmt
から生成される
上記は単純ではあるが、各クラスから生成されるインスタンスの生存期間を考慮すると、単純にインスタンスが破棄されるときに、例えばSQLの実行途中にSQLiteとのコネクションを破棄するという操作が行われてしまいエラーが発生するということも考えられる。そのため、これらのインスタンスの生存期間も考慮しながら適宜コネクション等を延長するようにする必要がある。
ラッパーの実装
ここからは具体的にラッパーを実装する。
SQLiteConnection/SQLite
まずは、実際にSQLiteとのコネクションを管理するSQLiteConnection
を示す。SQLiteConnection
で用いているSQLiteのAPIは以下のとおりである。
sqlite3_open
:SQLiteとのコネクションを確立するsqlite3_close
:SQLiteとのコネクションを破棄する(実行中のSQLが存在したりするとエラー)
SQLiteConnection
自体は次に示すSQLite
で管理されるものであり、ユーザが直接生成するべきではない(生成する場合は自己責任である)。SQLite
はSQLiteのコネクションおよびSQL文の実行のためのクラスであり、後述するプリペアドステートメントを扱うクラスSQLiteStmt
もこれから生成される。SQLite
で用いているSQLiteのAPIは以下のとおりである。
sqlite3_exec
:プリペアドステートメントを利用してSQL文を実行する操作に関して、1つの関数で実現するラッパーsqlite3_free
:SQLite内部で確保されたメモリの開放sqlite3_prepare_v2
:プリペアドステートメントを生成
SQLiteStmtControl/SQLiteStmt
次に、プリペアドステートメントを扱うSQLiteStmt
とSQLiteStmtControl
を示す。SQLiteStmt
は単一のSQL文から構成される文字列(単一のSQL文ではない)に対して生成されるプリペアドステートメントであり、本質的に一意である必要がある。そのため、SQLiteStmt
はコピーを禁止する必要がある。SQLiteStmtControl
はSQLite
に対するSQLiteConnection
と似たような役割を持つものであり、プリペアドステートメントを破棄するタイミングを制御する。SQLが実行中等によりプリペアドステートメントの意図しない破棄が発生しないようにするものであるが、破棄するタイミングがSQLの実行の完了や中断が絡むこと、プリペアドステートメントの使いまわしを考慮すると、スマートポインタのデストラクタにより管理するのはあまり意味がないし、考慮する事項が増えるだけで面倒である。そのため、適宜ビットフラグを用いて管理をする。SQLiteStmt
およびSQLiteStmtControl
で用いているSQLiteのAPIは以下のとおりである。
sqlite3_bind_*
:バインド変数を設定sqlite3_reset
:プリペアドステートメントを初期化(バインド変数を除く)sqlite3_db_handle
:sqlite3_stmt
からsqlite3
を取得sqlite3_errmsg
:SQLiteに関する最新のエラーメッセージの取得
SQLiteIterator/SQLiteView
次に、SQLの実行結果を走査するためのSQLiteView
を示す。SQLiteView
についても本質的にSQLiteStmt
と近い性質を持つため、コピーは禁止する必要がある。また、SQLiteView
はSQLiteStmt
からのみ生成できるようにコンストラクタをprivate
なメンバとして実装し、SQLiteStmt
をフレンドクラスとして加える。SQLiteView
で用いているSQLiteのAPIは以下のとおりである。
sqlite3_step
:SQL文を評価(カーソルを次に進める)sqlite3_db_handle
:sqlite3_stmt
からsqlite3
を取得sqlite3_errmsg
:SQLiteに関する最新のエラーメッセージの取得
最後に、SQLiteView
による走査のための部品であるSQLiteIterator
を示す。SQLiteIterator
はSQLiteView
の部品であるため、コピーは禁止する必要があり、SQLiteView
からのみ生成できるようにする必要がある。また、SQLiteIterator
は単一のクラスとして実現されるのではなく、番兵を示すSQLiteViewSentinel
とSQLの実行結果を取得するためのSQLiteData
の3つから実現される。これら用いているSQLiteのAPIは以下のとおりである。
sqlite3_step
:SQL文を評価(カーソルを次に進める)sqlite3_db_handle
:sqlite3_stmt
からsqlite3
を取得sqlite3_errmsg
:SQLiteに関する最新のエラーメッセージの取得sqlite3_column_count
:カラムの数を取得sqlite3_column_type
:カラムの型を取得sqlite3_column_*
:カラムのデータを取得sqlite3_column_decltype
:カラムの型を示す文字列を取得
パスワード管理ソフトの実装
ここからは、実際に作成したSQLiteのC++ラッパーの使い方をパスワード管理ソフトの実装する。ただし、CLIとしての実装のためのコードであったり、あまり本質的ではないソースコードは適宜省略をするため参照したい場合は公開しているソースコードを参照せよ。
仕様
今回作成するパスワード管理ソフトは管理することが目的のため、以下の仕様で作成をする。パスワードの暗号化を実装してもよかったが、そこを考え出すとそれの方が本題になってしまいそうだったため、今回は考えない。
パスワードを保存するためのキー(例えばサイトを示すURLなど)を必須とする
キーに対して複数のユーザ名および複数のパスワードを設定可能とする
パスワードの暗号化は(今回は)特に実施はしない
データの保存にはSQLiteを利用する
CLIからの利用イメージは以下のとおりである。
パスワード管理テーブル
パスワード情報を管理するにあたってそれを管理するテーブルを与える必要がある。今回は以下のような単純なテーブルを採用し、C++に直接これを埋め込む。
実際にこのテーブル情報をC++に埋め込むときは、テーブル名やカラム名が遍在しないように以下のようにカラム系を列挙した構造体を与えて置き、std::format
で構築するようにする(std::foramt
がchar8_t
に対応していないのがつらい)。
DBアクセサの実装
最後にパスワード管理のためのDBアクセサの定義を示す。今回利用するクラスの構造は以下のとおりである。
GetParam
やInsertParam
といった型はパスワード情報をSELECT
する際のWHERE
句の条件であったり、INSERT
を行う際に設定をするパラメータを示す型である。例えば、GetParam
は以下のように実装される。全てのパラメータがstd::optional
でラップされているのは、検索条件を指定しなくてもパスワード情報を取得できるという理由からである(すなわちWHERE
句の指定なしの場合)。
では、以下にパスワード情報を取得するSQLを実行するためのメンバPasswordManagement::get
の実装を示す。このメンバは引数としてpasswords::c_service::index
のようなインデックス値を指定して動的に任意のカラムを取得する実装にしているが、やろうと思えばガチガチに型で固めることも可能である。ただし、そのような実装はCLIからの利用という観点では現状不要のため実装をしていない。以下の実装では、与えられたインデックス値からカラム名のカンマで連結されたリストの文字列を構築し、getWhereStr
により構築されたWHERE
句とbindWhere
により設定されたWHERE
句のバインド変数によりプリペアドステートメントの準備を完了させたらexec
を実行してSQLの実行結果を参照するためのSQLiteView
を返すというシンプルな仕組みである(getWhereStr
やbindWhere
の実装については公開しているソースコードを参照せよ)。
これを実際に利用する際は、以下のように範囲for
文で直感的に走査することができる。
おわりに
後半のパスワード管理ソフトの構築部分はかなり省略をしたが、本題であるSQLiteのAPIの利用方法は示すことができたので満足。また、ソースコードについてはSQLiteにおけるいい感じなCRUDや日時操作などSQLに関する基本的な操作を利用方法が記載されているので、SQLiteの詳しい使い方や実装を詳しく知りたい人はそちらを参照すればいいだろう。
パスワード管理ソフト的には次はGnuPGを使って暗号化をできるようにしたいところである。あるいは、SQLの部分を直接記載するのもあまりよくないと思われるので、そのあたりの改善の実施をするのもいいかもしれない。あと、CLIの実装の内部で用いているCommandLineOption
によるコマンドライン引数の解析が貧弱のため、これも強化したい。