Qt Tech. OpenSSL通信、HTTPS通信を行うプログラムが簡単じゃない!対処方法
Qtテクニンカルノートへ戻る
概要
OpenSSLは数々の国々で武器輸出管理法の対象になることもあり、拠点を米国に置いていません。そして、Qtはフィンランドエスポーを拠点においていて、主要株主はフィンランドのノキアやデジアとなっています。開発の拠点はノルウェーのオスロという構造から、OpenSSLを抱き合わせる訳にはいかない技術になっています。( ゚д゚)ポカーン ソンナニスゴインダネ。でもQtだから使いやすいハズ。というわけで。
説明書を読んでHTTP通信のプログラムを勉強しようとしたのですが、手に出来る説明書が古くて、HTTP通信とかやってるんすよ。今どきはhttp://のような通信プロトコルは、あまり使わない。https://これです。暗号化ですね。通信ログっていろいろな部分で確認できるようになっていて、暗号化していないと途中に待ち構えているルーティングの中にいるサーバの主が、悪意を持ってパスワードの入力や個人情報が飛んでくるのを待っていることがあります。これを防ぐのが暗号化です。
一般的に使われるのが公開鍵方式と呼ばれている方式です。大事なのは公開する鍵(公開キーワード文字列)が秘密の鍵(非公開キーワード文字列)から生成されているということであり、この生成処理の逆が難しいということです。
難しいというのは、一般人が自由に保有できるであろう演算装置で計算させても数年以上かかるくらい難しいのだけれど、生成処理は簡単であり、生成処理には法則が唯一に存在するという仕組みにある。例えば因数分解みたいな計算がその一例だ。学校で習った因数分解(例えばx^2+4x+3=(x+1)(x+3))は考えれば解ける程度の問題になっているが、桁数が増えると無理っ!ってなる。じゃコンピュータにやらせてみるか?ってなってプログラムを作ってみたら、a+b=4、axb=3となるようなaとbを求めるというのは桁数が増えるとコンピュータを使ってやっても時間がかかるということがわかってきたりした。そういうたぐいの演算は存在するということがわかったのがこの仕組みの大事なところでもある。(x+1)(x+3)のような分解された値を演算するのはスグだよね。手順通り計算すれば、x^2+4x+3だってわかる。数字だけを扱う因数分解の桁数が少し増えただけでも大変なのに文字列をキーワードにしたら短くまとめたような長さでも相当時間がかかる。例えばx^2+439,193,310x+32,261,087,323,411,425の因数分解の答えは導き出せますか?無理だと思います。求めるプログラムも割かし大きなループが発生します。そんなイメージがいいかなって思う。全貌は掴めなくともイメージは大事です。求めるのが大変な演算が存在する。このイメージができれば十分だと思います。実際の演算アルゴリズムでは単純な因数分解のような演算ではありません。SSLのメカニズムそのものを作りたいという人は、もうちょっと別のsiteを徘徊して、理解するとよいと思います。素因数分解の68,718,821,377 = 131,071 x 524,287も同様に人間にとってもコンピュータにとっても困難な計算です。
簡単には、2つの素数pとq、それからp x qを法(その値に達したところで0に戻る仕組み。除算の余りみたいなもの)とする。pを秘密鍵、qを公開鍵とする。ユーザは法の中で平文の数値コードをq乗したものを送付。サーバはp値を演算においてをp、qそれぞれを1引いた値の最小公倍数+1をrとした場合、r - q +1 の結果のみが、法の中で復元できる暗号データに対する乗数になります。法の中でべき乗の結果が循環するという法則が、最小公倍数を求めるのに必要な、秘密の鍵pを知る側だけに複合できることに繋がっています。
公開鍵暗号方式でのやりとりの手順は以下のようなものです。
- 1.Webサーバー側が秘密の鍵(非公開キーワード文字列)を所有している状態になります。
- 2.ユーザはサーバにて計算された秘密鍵から生成された公開鍵(公開キーワード文字列)を受け取ります。
- 3.ユーザは送信するデータを公開鍵で暗号化して、サーバ側に送ります。公開鍵で暗号化されたデータを元のデータに戻すことは難しい状態になっている。
- 4.Webサーバは受け取ったデータを秘密鍵で複合化(元のデータ)して処理をする。
こんな感じ。
これがSSLのやり取りになると少しだけ変わります。変わる部分を追記した文章を以下に記します。
- 1.Webサーバー側が秘密の鍵(非公開キーワード文字列)を所有している状態になります。通信の要求が来る。
- 2.ユーザはサーバにて計算された秘密鍵から生成された公開鍵(公開キーワード文字列)を受け取ります。同時に証明書を受け取り、後々、レジストラと照合する。そして、ユーザ側で公開鍵(公開キーワード文字列)から更に共通鍵(公開共通キーワード文字列)を生成します。
- 3.ユーザは送信するデータを公開鍵で暗号化して、サーバ側に送ります。公開鍵で暗号化されたデータを元のデータに戻すことは難しい状態になっている。共通鍵を公開鍵で暗号化した暗号化共通鍵をサーバ側に送る。サーバー側は秘密鍵によって暗号化共通鍵の復元を行い共通鍵をユーザとサーバだけが保有した状態にする。
- 4.Webサーバは受け取ったデータを秘密鍵で複合化(元のデータ)して処理をする。共通鍵を使って暗号文を作る通信を行う。
双方の秘密を、サーバーの秘密のキーワードを使って生成することにより、手順4ではすっかり変わった通信方法になってしまったことが面白いところです。
この文字だけの説明を理解できた人は凄いかもしれない。あるいは、管理人が凄いのか。共通鍵を生成するときのメカニズムはセッション毎に変わるので、一定時間で変わります。したがって、計算は追いつかない仕組みです。盗もうと思った頃には鍵は変わっている筈です。これだけの技術でありながらも、盗まれている可能性はゼロではない。いや今後もか。
- あるIP区間からの通信のすべての通信ログをとっておいて、いつの日か全ての暗号を解読して読み解くという方式
- 秘密裏に超高速計算コンピュータを開発して、読み解く
とかあるんかな。ないだろうな。量子コンピュータを使ったとしても、生きている内には無理だそうです。
なんしか、本題はここからです。なんしか。日本語なのかな。なんしか。QtでSSL!
本題
まず、自分が使っているQtのOpenSSL(オープンソースのSSL通信技術)はどういうモノなのか確認します。プログラム中で以下のようなプログラムを実行します。
<syntaxhighlight2 lang="Cpp"> qDebug() << QSslSocket::sslLibraryBuildVersionString(); </syntaxhighlight2>
上記のプログラムの出力結果はQt5.15.2では、以下のようなものになりました。
<syntaxhighlight2 lang="Cpp"> "OpenSSL 1.1.1g 21 Apr 2020" </syntaxhighlight2>
2020年4月21日のVersion1.1.1.gを使っているようです。
ちなみにうまくSSLが使えていない場合は以下のようなエラーが取得できます。
<syntaxhighlight2 lang="Cpp"> qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed </syntaxhighlight2>
上記のメッセージは以下のようなシグナルとスロットの接続があった場合に
<syntaxhighlight2 lang="Cpp"> …
connect(xxxxxNetAccess, SIGNAL(finished(QNetworkReply*)), this, SLOT(readXml(QNetworkReply*)));//xxxxxNetAccess is QNetworkAccessManager Object.
…
xxxxxNetAccess->get(QNetworkRequest(QStringLiteral("https://www.yo-net.jp")));
… void xxx...xxx::readXml(QNetworkReply* reply){
qDebug() << reply->error();
} </syntaxhighlight2>
上記のように取得できるエラー出力で得られるモノです。処理は以下のような流れです。
- 1.QNetworkAccessManagerクラスのxxxxxNetAccessのシグナルfinished(QNetworkReply*)は状態通知を引数とする通信処理が完了関数が呼び出されたときに、reedXml(QNetworkReply*)関数を呼び出す仕組みにします。
- 2.xxxxxNetAccess->get()メンバ関数でQNetworkrequest(QStringLiteral("https://www.yo-net.jp"))の戻り値を引数にして通信処理を行います。これによりxxxxxNetAccessのfineshed(QNetworkReply*)が呼び出され、readXml関数が実行されます。
- 3.readXmlの引数である通信応答オブジェクトreplyからエラー内容を出力。
というような流れです。
これはSSL通信の準備が出来ていない事を示しています。ということですから、SSL設定を何かやらなくてはいけなくて、それをやっていないということらしい。
そういうことなので、Qtのリファレンスを読んでみました。https://doc.qt.io/qt-5/windows-requirements.html。するとやっぱり、自分でOpenSSLをセットアップしないとダメなんだそうな。ヘ(゚д゚)ノ ナンジャソリャ?
上記のリンクの記事の見出し辺りを読むと、スグにオープンソースの本家https://github.com/openssl/opensslのWinows版(https://slproweb.com/products/Win32OpenSSL.html)を提供しているサイトに出向いて、インストールしたくなりますが、実際はQtの追加コンポーネントとして、最適なOpenSSLが入手できるとなっています。そして、Qtのコンポーネントとして導入するOpenSSLは、最も最適化されているという文献を見つけたりしたのです。
なので、まずはC:\QtにあるMaintenanceTool.exeを起動して、ログイン情報を入力して、[Next](※SignUpボタンは新規アカウント作成なので押し間違えない。)を押して、add or remove Components を選択して[Next]、追加コンポーネント選択画面でQt>>Developper and Designer Tools>>>>OpenSSL1.1.1j Toolkitを選択する。あとは[Next]を続けざまに押し行くだけです。これで、C:\Qt\Tool\OpenSSLに一式が配置されます。
ネットの多くの文献によると、プロジェクトファイルのディレクトリに2つのdllを置くべきだと言っている。その二つはssleay32.dll、libeay32.dllだという(以降は二つを合わせて*eay32.dllと表現)。あるいはlibssl-1_1.dll、libcrypto-1_1.dll(以降は二つを合わせて*-1_1.dllと表現)。とは言いつつ*eay32.dllは手元に無い状態です。古いSSLにあるらしい。OpenSSL1.1.1j Toolkitで入手したファイル群のC:\Qt\Tool\OpenSSL\binはlibssl-1_1.dll、libcrypto-1_1.dllの二つしかない。
*eay32.dllと*1_1.dllの違いは端的に言えば、古いか新しいかの違いがあるらしい。中身が似通っているかっていうとそうでもなないらしい。そして、Qt5.15.2では*1_1.dllが配置されている。なので、使い方があるのかどうかわからないにしても、*1_1.dllをなんとかして使うらしい。
ここからが難しそうだなと思ったのですが、開発しているプログラムの実行ファイルが配置されるディレクトリと同じディレクトリに*1_1.dllを配置すると有効になりました。
<syntaxhighlight2 lang="cpp"> qDebug() << QSslSocket::supportsSsl(); </syntaxhighlight2> 出力結果 <syntaxhighlight2 lang="cpp"> true </syntaxhighlight2>
とくに、SSL通信をするための準備をする必要も無くQNetworkAccessManageクラスだけで通信できるようになります。
簡単なプログラムの流れは以下になると思います。
処理の流れ
<syntaxhighlight2 lang="Cpp"> void CqApp::Init() //CqApp は QObject を継承したQtの機能的クラスの派生クラス(QSystemTrayIcon,QPlainTextEdit,とか
//QNetworkAccessMangeの機能を使う以上は引数thisポインタが必要なので //QObjectから派生しているクラスである必要がある。
{
//ここでのInitメンバ関数は初期化とかコンストラクタのような処理
qNetworkAcessManager_Data = new QNetworkAccessManager(this); //QNetworkAccessManagerクラスのオブジェクトを生成します。左辺は自分で決めていいです。任意でどうぞ。 //QNetworkAccessManager* qNetworkAcessManager_Data;のようなメンバ変数としての宣言が事前に必要です。
…
connect(qNetworkAcessManager_Data, SIGNAL(finished(QNetworkReply*)), this, SLOT(readData(QNetworkReply*)));
//こんな感じでQt独特のシグナルとスロットという考え方の関数で //qNetworkAcessManager_Dataのfinishedという通知(引数は内部的にQNetworkRply*型)を受け取ると //このクラスのreadDataメンバ関数を(内部的にQNetworkRply*型を引数として)呼び出すことを宣言します。 //イベント駆動のやり方です。
qDebug() << QSslSocket::supportsSsl(); //SSL通信ができるか確認できる。true(=正常) or false(=ちゃんと設定出来ていない)。
CqApp::request();
… } … void CqApp::request() { …
qNetworkAcessManager_Data->get( QNetworkRequest(QUrl(QStringLiteral("https://xxx.xx/data.xml")))); //メンバ変数を利用してget関数に指定したアドレスの内容を読み込みます。 //通信が終わったらシグナルスロットの仕組みによりthis::readData関数が呼び出されます。
… } … void CqApp::readData(QNetworkReply *qNetworkReply_Reply) {
… if(qNetworkReply_Reply->error() != QNetworkReply::NoError) { // この区間でSSL通信エラーが発生した時の処理 } else{ // この区間で通信成功時の処理 }
… } </syntaxhighlight2> 大まかな流れは、こんな感じですね。具体的なサンプルじゃなくて申し訳ない。
発展的な内容
dll(動的リンク)で動作するのはわかった。じゃぁ静的リンクはどうなんだ?C:\Qt\Tools\OpenSSL\Win_x86\libにlibcrypt.libとlibssl.libの二つのライブラリがありました。これで取り込まれるようにできるのかなって思った。静的リンクに変更できるか確認をしてみたいと思います。もうちょっと時間かかるかもな。管理人にとっては割かし複雑な構造をしていると感じるQt手強い。
Qtテクニンカルノートへ戻る