> この部分ですが、hmacのハッシュする部分にタイムスタンプを入れると言うことでしょうか?
そのとおりです。
一点確認しておきますが、タイムスタンプはそのデータが生成された時刻を示す値であって、現在時刻ではありません。よって、ここでのタイムスタンプとはサイトAからサイトEに転送するデータの電子署名が作成された時刻のことを指します。
実際の手順としては、電子署名にタイムスタンプを含めておき、そのタイムスタンプを使ってハッシュ値を計算します。また、一定時間以上前のタイムスタンプの場合に検証エラーとすることで、古いタイムスタンプの値を送ってくるのを防ぎます。
> 上記にかんしましても上の部分と共通するかもしれませんが、全ての値を:などで順に連結・・・」ありますが、この部分ももう少し詳しくご教授頂くことは可能でしょうか?
#3で他の方が回答されている通り、どんなデータ形式でも良いのですが、自分が提案していたのは:ですべてのデータをつなげるという原始的な方法です。
例えば、合計金額10000円、会員番号 10番などだと10000:10とすれば良いのでは?と言っているに過ぎません。
#3の回答に出てきたJSONでもXMLでも良いですし、PHPということでPHPのserializeでも良いでしょう。(PHPのserializeは何かと問題持ちだといいますが...)
と、言葉で言っていても通じにくいところもあると思いますので実際にコードを書いてみました。
データは連想配列の配列をserializeした後にBase64エンコードして、転送が簡単に出来る形式に変換したあとにTimeoutSignerを使って電子署名を計算しています。また、TimeoutVerifierで電子署名の検証もしています。
$key = "abc";
$data = array(
0 => array(
'title' => 'Hamlet',
'category' => 'book',
'price' => '1000',
'unit' => 'yen',
'number' => 1,
),
);
$encoded_data = base64_encode(serialize($data));
$signer = new TimeoutSigner($key);
$sig = $signer->sign($encoded_data);
$verifier = new TimeoutVerifier($key);
if ($verifier->verify($encoded_data, $sig)) {
$decoded_data = unserialize(base64_decode($encoded_data));
print $decoded_data[0]['title'];
} else {
print "verify error.";
}
/**
* Signer that sign data with timestamp.
*
* @package default
* @author Hanabutako.
*/
class TimeoutSigner {
const FINGERPRINT_LENGTH = 8;
function __construct($key) {
$this->key = $key;
$this->finger_print = substr(hash("sha256", $key, true), 0,
TimeoutSigner::FINGERPRINT_LENGTH);
}
/**
* Sign the data.
*
* @param string $data to calculate signature.
* @return string Base64 encoded signature with timestamp.
*/
function sign($data) {
$timestamp = pack("N", time());
// Calculate hash value with timestamp.
$hash_value = hash_hmac("sha256", $timestamp . $data, $this->key, true);
$signature = $this->finger_print . $timestamp . $hash_value;
return base64_encode($signature);
}
}
/**
* Verifier that verifies signature signed by TimeoutSigner.
*
* @package default
* @author Hanabutako.
*/
class TimeoutVerifier {
const TIMEOUT_DURATION = 3600; // 1 hour.
function __construct($key) {
$this->key = $key;
$this->finger_print = substr(hash("sha256", $key, true), 0,
TimeoutSigner::FINGERPRINT_LENGTH);
}
/**
* Verify the signature.
*
* @param string $data to calculate signature.
* @param string $b64_signature Base64 encoded signature to be verified.
* @return bool true if verified, otherwise false.
*/
function verify($data, $b64_signature) {
$signature = base64_decode($b64_signature);
$finger_print = substr($signature, 0,
TimeoutSigner::FINGERPRINT_LENGTH);
$timestamp = substr($signature, TimeoutSigner::FINGERPRINT_LENGTH, 4);
$hash_value = substr($signature, TimeoutSigner::FINGERPRINT_LENGTH + 4);
if ($finger_print != $this->finger_print) {
return false;
}
// Check timestamp is not too old.
$decoded_timestamp = unpack("N", $timestamp)[1];
if (time() - $decoded_timestamp > TimeoutVerifier::TIMEOUT_DURATION) {
return false;
}
// Verify hash value with timestamp.
if (hash_hmac("sha256", $timestamp . $data, $this->key, true)
!= $hash_value) {
return false;
}
return true;
}
}
このコード自体は、先日の回答で書いたKeyCzarのTimeoutVerifierに着想を得ています。
http://code.google.com/p/keyczar/source/browse/java/code/src/org/keyczar/TimeoutVerifier.java
というわけで、HMACを計算する値にタイムスタンプを入れますが、電子署名を送るときにそれをつけて送ります。そして、そのタイムスタンプが古過ぎない場合にのみ、それを使ってHMACを計算します。
すべての値を:でつなげるというのはデータをどう送るかという話ですが、PHPだとserializeが使えるならserializeを使えば良いのではないでしょうか。
補足
hanabutakoさん 返信が大変遅くなり申し訳ありません。 教えて頂いたコードで、テスト環境で色々試してみました。 はじめに、私の想像を超えた素晴らしい内容で大変勉強になりました。 本当に質問させて頂きよかったです。 ありがとうございます。 誠に図々しいお願いなんですが、自分なりに解釈してみたので、下記私の解釈が違う部分がございましたら、ご指摘頂ければ幸いです。 まず [送り側] TimeoutSignerクラスを使って、変数keyのハッシュを行い、substrで一部分を切り出す。 切り出す文字数のFINGERPRINT_LENGTH定数は任意の数字で構わないが、TimeoutVerifierクラスのコンストラクタで仕様するので同じ値にする必要がある。 そこから、finger_print変数にkeyのハッシュを行い切り出した一部の文字列を入れ、signメソッドで仕様する。 singメソッドはtime()関数で現在のタイムスタンプを取るこの時、pack関数でタイムスタンプをバイナリ文字列にする←簡単にタイムスタンプをわからなくするため。 また、データとタイムスタンプとkey変数を、先日伺ったhash_hamacで"sha256"アルゴリズムで送るデータとタイムスタンプをハッシュする。 その後、finger_print変数とタイムスタンプとhmacではッシュしたデータを組み合わせて、base64_encodeでエンコードし、返り値とする。 base64_encodeする理由は、簡単に内容を読まれないようにするため。 また、送るデータとして、serialize関数でシリアル化を行い、エンコードする。 [受信側] TimeoutVerifierクラスでコンストラクタはTimeoutSignerクラスと同じ内容を行う。 verifyメソッドで送られたきたデータと鍵情報を引数にして、下記情報をデコードし、それぞれの必要なデータを作る まず初めに、finger_print変数を比較する。 内容の相違があれば終了 取り出したtimestamp変数と現在の時間の差を設定した時間TIMEOUT_DURATION定数と比較してTIMEOUT_DURATIONを超えていれば終了 次に、hash_value変数を比較して、内容が違っていれば終了する 以上の鍵を全てクリアすると、戻り値でtrueを返す。 OKの場合はデータをデコードしてunserialize関数で元の値に戻す。 といった流れになると思います。 以上の解釈で大凡のズレはないでしょうか? 私が今回、気付かされたのではデータを渡すと際のデータをいかに予測のつかない値に変えて渡すかと言うことと、その方法が非常に勉強になりました。 長くなってしまいましたが、ありがとうございます。 また、この他にもご回答頂いたみなさまありがとうございました。