ブログが続かないわけ

この日記のはてなブックマーク数
Webエンジニアが思うこと by junichiro on Facebook

サーバ間通信のデータ詐称回避

このエントリーを含むはてなブックマーク hateb

サーバ間で通信をする場合、その引数の内容が詐称されていないかを判定するためにひと工夫凝らさないといけません。良くある方法としては、サーバ同士お互いに秘密鍵を持ち、通信の引数とその秘密鍵を種にして同じロジックでハッシュを生成しそれを比較するというものがあります。

例えばユーザーIDが712904684のユーザーの残高を200円上げるという処理をサーバ間通信で依頼することを考えると、こんなAPI(URI) を考えるかもしれません。

http://www.example.com/api?uid=712904684&money=200

ところがこれがこのまま通って残高が200円あがるとなると、200の部分を9000に詐称するのも簡単な気がしてきます。

http://www.example.com/api?uid=712904684&money=9000

サーバ間通信を行うアプリケーション以外からでも、例えばこんなURLをブラウザのアドレス欄に入力して実行するだけで簡単にこのユーザーの残高を9000円もあげることができてしまいます。サーバ間通信の内容は普通はユーザーには見えにくいので、こういう問題を気にせず実装されているソーシャルアプリもたくさんあり、巷のソーシャルゲームでチートが横行している一因にもなっています。さて、話を戻して、このような詐称をさせないために、最初にお話したハッシュを比較するという方法をどのように適用するのかみてみましょう。

  1. サーバ間通信を行う2者で事前にsecret_key となる文字列を決めておく
  2. 引数のkeyをアルファベット順に並び替える
  3. その順番でkey=valueという文字列を連結させる
  4. その文字列の最後にsecret_key を連結する
  5. できた文字列をmd5 でハッシュにする
  6. 通信の最後の引数にこのハッシュを付加する

secret_key をa1b23c とします。 PHPで書くとこんな感じでしょうか。

function generate_hash($params_array) {
    $secret='a1b23c';
    ksort($params_array);
    foreach ($params_array as $k=>$v) {
        $str .= "$k=$v";
    }
    $str .= $secret;
    return md5($str);
}
これで生成されたhashをx43q98tgaji45asoiとすると、次のようなURI でユーザーの残高をあげるようにリクエストを投げます。

http://www.example.com/api?uid=712904684&money=200&hash=x43q98tgaji45asoi

こうしておいて、リクエストを受け付けたサーバ側でも同じsecret_key を用いて同じロジックでハッシュを計算して、それがhashという引数で渡ってきたもの(x43q98tgaji45asoi)と一致しているかをチェックしてデータが詐称されていないかを調べることができます。

この場合に最初に見たような詐称をするとどうなるでしょう。

http://www.example.com/api?uid=712904684&money=9000&hash=x43q98tgaji45asoi

money=9000の部分の文字列がmoney=200の文字列と異なるため、同じロジックでハッシュを生成しても、できあがるhash がx43q98tgaji45asoiという文字列になりません。そのためリクエストを受け付けたサーバ側ではhash の不一致が発生し、エラーとして処理されます。一方、詐称したいと考える第三者がこのハッシュもちゃんと書き換えたURI を作成しようと考えたとしましょう。しかし、その第三者はあいにくsecret_key を知らないのでそれも不可能です。ここはmd5 が一方通行の変換であるために、正しい通信からsecret_key が逆算されないことを利用しています。詳細は割愛しますが、「正しい通信の内容からもsecret_key は推測できない」というのは大事な部分です。

以上が、サーバ間通信における詐称回避のひとつの解であり、Facebook でもほとんど似た方法が取られています。

自前でセキュアなサーバ間通信を行う場合はデータの詐称というのがなかなか厄介な悩みとなります。第三者に公開しないようなシステムであればアクセス元IP をチェックするというような簡単な方法でも対応できるかもしれませんが、第三者に広く公開する場合はこういう方法を検討するのもいいと思います。その場合、API 利用者をしっかりと管理し、その利用者ごとに別のsecret_key を割り当てるのがまたもう一つ難しいところなのですが。

この記事のトラックバックURL
http://en.yummy.stripper.jp/trackback/1395970
トラックバック
コメント









関連情報