PHPとC#でサーバ認証を行う

正しいサーバーと通信しているかチェックしよう!

こんにちは!

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 → サーバーが正しくない(エラー処理)
}

// …省略…

以上です!

これで「正しいサーバーと通信していること」を確認できるようになりましたっ