正しいサーバーと通信しているかチェックしよう!
こんにちは!
Abemaテレビの「格闘代理戦争 3rdシーズン」にハマっているあらたまです。 あの番組面白すぎでしょ。。
今日は技術系のお話しです。
サーバーが正しいかチェックする
つい最近簡易な認証サーバーを構築する際に、サーバ認証を実装する必要がありました。
サーバー認証とは、クライアントから見て通信しているサーバーが、正規の正しいサーバーかどうかをチェックすることです。 ユーザー認証の真逆ですね。
これを実装しておかないとユーザーが独自に認証サーバーとDNSサーバーを立ててしまえば 簡単に認証の回避ができてしまうわけですね。
サーバ認証を実装したときのソースコードを簡略化したものを載せておきます。 サーバー側をPHP、クライアントをC#で開発しています。
サーバー側(PHP)
まずはサーバー側の処理です。クライアント側に返すデータに対して秘密鍵で署名を作成します。
<?php
// …省略…
// 秘密鍵
const PRIVATE_KEY = <<<EOM
-----BEGIN RSA PRIVATE KEY-----
…
-----END RSA PRIVATE KEY-----
EOM;
// サーバ側の処理
$result = 'なんやかんやした結果';
// 秘密鍵の取得
$prvkey = openssl_get_privatekey(PRIVATE_KEY, "");
// 秘密鍵でデータに対して署名を作成
$sigResult = openssl_sign($result, $signature, $prvkey);
// 秘密鍵リソースを開放する
openssl_free_key($prvkey);
// クライアントに返すデータの用意
$resultArray = array(
"result" => $result,
"signature" => base64_encode($signature)
);
// Jsonにしてクライアントに返す
echo json_encode($resultArray);
クライアント側(C#)
次にクライアント側の処理です。サーバから返されたデータと署名が正しいことを、公開鍵を用いて検証します。
これによって「サーバーが正しい秘密鍵を持っていること」、つまり正規のサーバーであることが確認できるわけですね。
private const String PUB_KEY = "<RSAKeyValue><Modulus> ... 省略 ... </Modulus><Exponent> ... 省略 ... </Exponent></RSAKeyValue>";
// …省略…
// サーバーと通信
var content = new FormUrlEncodedContent(request);
var response = await client.PostAsync(URL, content);
var responseJson = await response.Content.ReadAsStringAsync();
// サーバーから返されたJsonをDictionaryに変換
Dictionary resultArray = JsonConvert.DeserializeObject>(responseJson);
// 暗号サービスプロバイダの準備
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(PUB_KEY);
// Byte配列の準備
byte[] dataByte = System.Text.Encoding.UTF8.GetBytes(resultArray["result"]);
byte[] sigByte = System.Convert.FromBase64String(resultArray["signature"]);
// 署名チェック
string sha1Oid = CryptoConfig.MapNameToOID("SHA1");
bool res = rsa.VerifyData(dataByte, sha1Oid, sigByte);
if (res) {
// 署名チェックOK → 正規のサーバーである
} else {
// 署名チェックNG → サーバーが正しくない(エラー処理)
}
// …省略…
以上です!
これで「正しいサーバーと通信していること」を確認できるようになりましたっ