第 15 章: Windows HTTP サービスの使用

Hilo Browser アプリケーションでは、オンライン写真共有アプリケーションである Flickr への写真のアップロードが可能になりました。この機能を提供するため、Windows HTTP サービスが使用されています。この章では、Hilo Browser でこのライブラリをどのように使用して、写真共有機能を実装しているかについて説明します。

[Share] ダイアログを通じた Flickr との連携

Hilo で Flickr に写真をアップロードする場合、Flickr の Web サーバーに対して複数の呼び出しが実行されます。これらの呼び出しは、Hilo アプリケーションの認証 (および frob (フロブ) と呼ばれるセッション トークンの取得)、Hilo Flickr アプリケーションが Flickr アカウントに写真をアップロードするためのアクセスの承認 (およびアクセス トークンの取得)、および写真のアップロードのために行われます。これらの呼び出しは、ネットワーク経由で行われ、非常に時間がかかる場合があります。よって、Flickr Web サーバーからの応答を待つ間、ユーザーに情報を提供する方法が必要になります。これが [Share] ダイアログ ボックスの目的です。

Hilo Browser のユーザー インターフェイスには、Share というラベルのボタン (図 1) が用意されています。このボタンをクリックすると、[Share] ダイアログ ボックス (図 2) が表示されます。このダイアログ ボックスは ShareDialog クラスによって実装されています。このクラスには静的メソッドが用意されており、選択した写真をアップロードすることも、現在のフォルダー内のすべての写真をアップロードすることもできます。[Share] ダイアログ ボックスでは、写真のアップロード メカニズムの進行状況を反映して、3 通りのコントロール セットが提供されます。最初のコントロール セットを、図 2 に示します。[Upload] ボタンをクリックすると、ラジオ ボタンの下に進行状況バーが表示され (図 3)、アップロードの進行状況が表示されます。アップロードが完了すると、最初のコントロールはすべて非表示になり、[Cancel] からラベルが変更された [Close] ボタンと、[View Photos] リンク コントロールのみ表示されます (図 4)。このダイアログ ボックスのすべてのバージョンで同じクラスが使用されます。

図 1 Hilo Browser の [Share] ボタン

Gg316358.9e263de5-bd41-48b4-81af-98a5f5bfeca8(ja-jp,MSDN.10).png

図 2 [Share] ダイアログ ボックスの初期表示

Gg316358.0960cac2-36c6-42b5-b237-59887d8895b0(ja-jp,MSDN.10).png

図 3 写真をアップロードするときの [Share] ダイアログ ボックスの表示

Gg316358.2993b951-23aa-427d-b57c-613e0403074b(ja-jp,MSDN.10).png

図 4 アップロードが完了したときの [Share] ダイアログ ボックスの表示

Gg316358.7a743e15-27e0-4b34-a0d5-9483f01a4732(ja-jp,MSDN.10).png

ユーザーが [Upload] ボタンをクリックすると、ShareDialog::UploadImages メソッドによって、ネットワーク アクセスを実行する FlickrUploader クラスのインスタンスが作成されます。ShareDialog::UploadImages メソッドは、最初に FlickrUploader::Connect メソッドを呼び出して、セッション キー (フロブ値) を取得し、システムに登録されたブラウザーを起動して Flickr ログオン ページを表示します。ユーザーは、Hilo からの写真のアップロードに使用する Flickr アカウントにログオンします。次に、UploadImages メソッドは FlickrUploader::GetToken メソッドを呼び出して、写真のアップロード先のアカウントに関連付けられたアクセス トークンを取得します。最後に、新しいスレッドが作成され、SharedDialog::ImageUploadThreadProc メソッドが非同期的に実行されます。このメソッドは、FlickrUploader.UploadPhotos メソッドを呼び出してトークンと写真へのパスを渡すことにより、各写真をアップロードします。写真がアップロードされるごとに、進行状況バーが 1 目盛りずつ更新されます。各写真がアップロードされるたびに、Flickr は ID を返し、すべての写真がアップロードされると、SharedDialog::ImageUploadThreadProc メソッドはアップロードされた画像を表示するための URL を作成します。この URL の形式をリスト 1 に示します。ここで、ids パラメーターは表示する写真の ID を、コンマ区切りのリストで提供します。この URL は、図 4 の [View Photos] リンク コントロールのリンク先として指定されます。

リスト 1 アップロードされた写真の URL

http://www.flickr.com/photos/upload/edit/?ids=[comma separated list]

アップロード プロセスのいくつかの段階で、ユーザー入力のためにコードを一時停止する必要があります。最初にこれが行われるのは、ShareDialog::UploadImages メソッドで Flickr からフロブを取得するときです。UploadImages メソッドが FlickrUploader::Connect メソッドを呼び出すと、このメソッドがシステムに登録されたブラウザーを起動して Flickr ログオン ページを表示し、ユーザー タスクがこのブラウザー ページに切り替わります。この間、ログオン プロセスが完了するまで、UploadImages メソッドは一時停止している必要があります。そのために、UploadImages メソッドは 2 つのタスク ダイアログ ウィンドウを表示します (これらは TaskDialogIndirect 関数を呼び出すことによって作成されます)。

図 5 Hilo を承認する必要があることをユーザーに通知するダイアログ

Gg316358.796b81c1-4d45-4b32-80a1-ba14b9047711(ja-jp,MSDN.10).png

最初のダイアログ (図 5) は、Hilo アプリケーションからユーザー アカウントへのアクセスを、ユーザーが承認する必要があることを通知します。これにより、ユーザーはこれから行われる処理を知り、ここで操作を取り消すこともできます。ユーザーがこの最初のダイアログで [Authorize] ボタンをクリックすると、FlickrUploader::Connect メソッドが呼び出されてログオン ページが表示され、背景に 2 番目のダイアログ (図 6) が表示されます。このモーダル ダイアログは、UploadImages メソッドをブロックしています。ユーザーは、ログオン手順を完了して Browser に戻ったときに、このダイアログの [Authorization Complete] ボタンをクリックして UploadImages メソッドのブロックを解除することができます。これにより、ワーカー スレッドで SharedDialog:: ImageUploadThreadProc メソッドが呼び出され、写真がアップロードされます。

図 6 ユーザーが Hilo Flickr アプリケーションを承認するまでの間、Browser をブロックするダイアログ

Gg316358.173936bc-8ff8-4f8f-b663-1ce2d0060132(ja-jp,MSDN.10).png

写真のアップロード

場合によっては、1 つの写真が数メガバイトのデータになることもあります。写真をアップロードするための Flickr API では、http://api.flickr.com/services/upload/ URI に対して、マルチパート HTTP POST 要求を使用します。メッセージの各パートは、アップロード アクションを示す引数の 1 つで構成されています。アクションを認証および承認するため、API キーおよびアクセス トークンを、これらから生成されたメッセージ ダイジェストと共に指定する必要があります。メッセージ ダイジェスト (Flickr では署名と呼ばれます) は、メソッド パラメーターと、Flickr アプリケーションおよび Flickr だけが知るシークレットから生成されたハッシュです。Flickr では、このダイジェストを使用してパラメーターの整合性を確認し、要求が途中で、悪意によってまたは偶然に変更されていないかどうかを検出することができます。

メッセージの最後の部分は、写真を表すバイナリ データです。リスト 2 に、一般的な形式の POST 要求 (大かっこで囲まれた項目は実際のデータに置き換えられます) を示します。

リスト 2 写真をアップロードするための POST メッセージの例

POST /services/upload/ HTTP/1.1
Content-Type: multipart/form-data; boundary=--EBA799EB-D9A2-472B-AE86-568D4645707E
Host: api.flickr.com
Content-Length: [data_length]

--EBA799EB-D9A2-472B-AE86-568D4645707E
Content-Disposition: form-data; name="api_key"

[api_key_value]

--EBA799EB-D9A2-472B-AE86-568D4645707E
Content-Disposition: form-data; name="auth_token"

[token_value]

--EBA799EB-D9A2-472B-AE86-568D4645707E
Content-Disposition: form-data; name="api_sig"

[api_sig_value]

--EBA799EB-D9A2-472B-AE86-568D4645707E
Content-Disposition: form-data; name="photo"; filename="[filename]"

[image_binary_data]

--EBA799EB-D9A2-472B-AE86-568D4645707E

Windows HTTP サービスの使用

Flickr へのネットワーク アクセスは、FlickrUploader クラスのメソッドによって実行されます。このクラスは 2 種類の呼び出しを行います。SOAP プロトコルに従って形式が指定されたデータを送信する Web サービス メソッドの呼び出しと、HTTP を通じた Flickr Web サイトへの低レベルの呼び出しです。次の第 16 章では、Hilo で使用される Windows 7 Web サービス API のコードについて説明します。Windows HTTP サービスは HTTP プロトコルによる Web サーバーへのアクセスを可能にします。Hilo ではこの API を使用して、リスト 2 に示されている POST 要求を送信し、Flickr に写真をアップロードします。この API を使用するには、Winhttp.h ヘッダー ファイルをインクルードし、Winhttp.lib ライブラリにリンクする必要があります。

HTTP サービスの関数を呼び出す前に、WinHttpOpen 関数を呼び出す必要があります。この関数に、以降の Web 要求で送信されるユーザー エージェントの名前、プロキシ (使用する場合) に関する情報、および Web 呼び出しが同期的か非同期的かという情報を渡します。WinHttpOpen 関数はセッション ハンドル (HINTERNET) を返しますが、このハンドルはすべての HTTP サービス ハンドルと同様に、処理が終了したら WinHttpCloseHandle 関数を使用して閉じる必要があります。次に、WinHttpConnect 関数を呼び出して接続ハンドルを取得します。この関数に対して、セッション ハンドル、サーバー名、およびサーバー ポートを指定します。この関数は、ネットワーク接続を実行するのではなく、内部接続設定を準備するだけです。次に、要求を作成するため、WinHttpOpenRequest 関数を呼び出して、接続ハンドルと、実行する要求に関する情報を渡します。WinHttpOpenRequest 関数は、要求の一部として送信される RFC822、MIME、および HTTP のすべてのヘッダーを格納するために使用される要求ハンドルを返します。要求の実際のヘッダーを追加するには、WinHttpAddRequestHeaders 関数を呼び出して、復帰/改行のペアで区切られたすべてのヘッダーを含む文字列を渡します。

実際のネットワーク呼び出しは、WinHttpSendRequest 関数を呼び出して要求ハンドルを渡したときに行われます。この関数を使用して、WinHtttpAddRequestHeaders の呼び出しで指定されていない追加の HTTP ヘッダー、および PUT または POST 要求のための呼び出しに必要な追加データを指定できます。サーバーが要求に応答したら、要求ハンドルを指定して WinHttpReceiveResponse 関数を呼び出し、システムがサーバーからの応答ヘッダーを読み取ることができるようにします (応答ヘッダーは、WinHttpQueryHeaders 関数を呼び出すことにより取得できます)。

サーバーが応答したら、サーバーからデータを受信できます。そのためには、要求ハンドルを指定して WinHttpQueryDataAvailable 関数を呼び出し、ダウンロード可能なバイト数を取得した後、WinHttpReadData 関数を呼び出してデータを読み取ります。要求が同期的に行われる場合、データが利用可能になるまで、WinHttpQueryDataAvailable 関数はブロックされます。

Hilo での HTTP サービスの使用

Hilo では、Flickr に写真をアップロードするときに HTTP Web サービスを使用します。リスト 2 に、写真をアップロードするために行われる POST 要求の一般的な形式を示します。リスト 3 には、この呼び出しを行う FlickrUploader::UploadPhotos メソッドを示します。最初の数行は単純です。このコードは、WinHttpOpen 関数を呼び出して、ユーザー エージェントが Hilo/1.0 であり、この時点でプロキシが指定されていないことを示します。次に、WinHttpConnect 関数を呼び出して、ホスト名が api.flickr.com であることと、アクセスが既定の HTTP ポートである TCP ポート 80 で行われることを指定します。その次に WinHttpOpenRequest 関数を呼び出して、要求が /Services/Upload/ リソースに対する POST 呼び出しであることを指定します。

リスト 3 Flickr への写真のアップロード

std::wstring FlickrUploader::UploadPhotos(
   const std::wstring& token, const std::wstring& fileName, bool* errorFound)
{
   std::wstring outputString;
   HINTERNET session = nullptr;
   HINTERNET connect = nullptr;
   HINTERNET request = nullptr;

   WINHTTP_AUTOPROXY OPTIONS  autoProxyOptions;
   WINHTTP_PROXY_INFO proxyInfo;
   unsigned long proxyInfoSize = sizeof(proxyInfo);
   ZeroMemory(&autoProxyOptions, sizeof(autoProxyOptions));
   ZeroMemory(&proxyInfo, sizeof(proxyInfo));

// WinHTTP セッションを作成する。
   session = ::WinHttpOpen( 
      L"Hilo/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, 
      WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
   connect = ::WinHttpConnect(session, L"api.flickr.com", INTERNET_DEFAULT_HTTP_PORT, 0);
   request = ::WinHttpOpenRequest(
      connect, L"POST", L"/services/upload/", L"HTTP/1.1", 
      WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
   autoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT;
// DHCP および DNS ベースの自動検出を使用する。
   autoProxyOptions.dwAutoDetectFlags = 
      WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
// PAC スクリプトを取得するために NTLM/ネゴシエート認証が必要である場合は、
// 自動的にクライアント ドメイン資格情報を提供する。
   autoProxyOptions.fAutoLogonIfChallenged = true;

   if (FALSE != ::WinHttpGetProxyForUrl(
         session, L"http://api.flickr.com/services/upload/", &autoProxyOptions, &proxyInfo))
   {
// プロキシ構成がある場合は、要求ハンドルでプロキシを設定する。
      ::WinHttpSetOption(request, WINHTTP_OPTION_PROXY, &proxyInfo, proxyInfoSize);
   }

   SendWebRequest(&request, token, fileName);
   outputString = GetPhotoId(&request, errorFound);

// クリーンアップ
   if (proxyInfo.lpszProxy)
   {
      GlobalFree(proxyInfo.lpszProxy);
   }
   if (proxyInfo.lpszProxyBypass)
   {
      GlobalFree(proxyInfo.lpszProxyBypass);
   }

   WinHttpCloseHandle(request);
   WinHttpCloseHandle(connect);
   WinHttpCloseHandle(session);
   return outputString;
}

FlickrUploader クラスは Web 呼び出しを使用して写真をアップロードします。Web 呼び出しは、ローカル ネットワークにプロキシが設定されている場合、プロキシを通じて行われます。そのために、UploadPhotos メソッドは WinHttpGetProxyForUrl 関数を呼び出し、DHCP および DNS クエリにより検出されたプロキシの自動構成 (PAC) ファイルの内容を受信します。結果は、システム グローバル ヒープに割り当てられた proxyInfo 変数 (プロキシ サーバー一覧とプロキシ バイパス一覧を含む) の形で返されます。これらのフィールドは、メソッドが完了したときに GlobalFree 関数の呼び出しによって解放されます。プロキシ情報は、WinHttpSetOption メソッドの呼び出しにより、要求に関連付けられます。この時点で、このメソッドはリスト 4 に示されているように、FlickrUploader::SendWebRequest メソッドを呼び出して Web 要求を実行することができます。

SendWebRequest メソッドの大半は、HTTP POST 要求のヘッダーを構築するためのものです。アップロード パラメーターは、Flickr API シークレットで署名されている必要があるので、SendWebRequest メソッドの最初の部分では、シークレット、API キー、およびトークンを連結します。次に、FlickrUploader::CreateMD5Hash メソッド (後述) を呼び出すことによって、この文字列の MD5 ハッシュを生成します。このヘッダーは、WinHttpAddRequestHeaders の呼び出しにより、要求に追加されます。

リスト 4 Web 要求の作成

int FlickrUploader::SendWebRequest(
   const HINTERNET *request, const std::wstring& token, const std::wstring& fileName)
{
   static const char* mimeBoundary = "EBA799EB-D9A2-472B-AE86-568D4645707E";
   static const wchar_t* contentType = 
      L"Content-Type: multipart/form-data; boundary=EBA799EB-D9A2-472B-AE86-568D4645707E\r\n";

// api_sig を生成するため、パラメーターがアルファベット順に配置される
   std::wstring params = flickr_secret;
   params += L"api_key";
   params += flickr_api_key;
   params += L"auth_token";
   params += token;

   std::wstring api_sig = CalculateMD5Hash(params);
   int result = ::WinHttpAddRequestHeaders(
      *request, contentType, (unsigned long)-1, WINHTTP_ADDREQ_FLAG_ADD);
   if (result)
   {
      std::wostringstream sb;

      sb << L"--" << mimeBoundary << L"\r\n";
      sb << L"Content-Disposition: form-data; name=\"api_key\"\r\n";
      sb << L"\r\n" << flickr_api_key << L"\r\n";

      sb << L"--" << mimeBoundary << L"\r\n";
      sb << L"Content-Disposition: form-data; name=\"auth_token\"\r\n";
      sb << L"\r\n" << token << L"\r\n";

      sb << L"--" << mimeBoundary << L"\r\n";
      sb << L"Content-Disposition: form-data; name=\"api_sig\"\r\n";
      sb << L"\r\n" << api_sig.c_str() << L"\r\n";

      sb << L"--" << mimeBoundary << L"\r\n";
      sb << L"Content-Disposition: form-data; name=\"photo\"; filename=\"" << fileName << L"\"\r\n\r\n";

// wstring を string に変換する
      std::wstring wideString = sb.str();
      int stringSize = WideCharToMultiByte(CP_ACP, 0, wideString.c_str(), -1, nullptr, 0, nullptr, nullptr);
      char* temp = new char[stringSize];
      WideCharToMultiByte(CP_ACP, 0, wideString.c_str(), -1, temp, stringSize, nullptr, nullptr);
      std::string str = temp;
      delete [] temp;

// 写真をストリームに追加する
      std::ifstream f(fileName, std::ios::binary);
      std::ostringstream sb_ascii;
      sb_ascii << str;
      sb_ascii << f.rdbuf();
      sb_ascii << "\r\n--" << mimeBoundary << "\r\n";
      str = sb_ascii.str();
      result = WinHttpSendRequest(
          *request, WINHTTP_NO_ADDITIONAL_HEADERS,  0, (void*)str.c_str(),
          static_cast<unsigned long>(str.length()), static_cast<unsigned long>(str.length()), 0);
   }
   return result;
}

このメソッドの残りの部分では、要求にパラメーターを追加し、マルチパート MIME メッセージを ASCII 文字列として構築します。まず API キー、トークン、および署名を指定する部分が、Unicode バッファーを使用して追加され、ASCII バッファーに変換された後、写真の内容がバイナリ データとして追加されます。最後に、WinHttpSendRequest 関数を呼び出すことにより、サーバーに対する実際の要求が行われます。

FlickrUploader::UploadPhotos メソッドは、サーバーに対して要求を行った後、リスト 5 に示されているように、FlickrUploader::GetPhotoId を呼び出してサーバーから応答を読み取ります。最初のアクションは WinHttpReceiveResponse メソッドを呼び出すことであり、このメソッドはサーバーが応答を送信するまでブロックされています。GetPhotoId メソッドは、次に、WinHttpQueryHeaders 関数を呼び出してすべての応答ヘッダーを取得します。これらのヘッダーは単一のバッファーに読み取られます。必要となものは、これらのヘッダーではなく、応答ヘッダーに続くデータであり、このデータは WinHttpReadData 関数を呼び出すことによって取得されます。

リスト 5 Web 要求からのデータの取得

std::wstring FlickrUploader::GetPhotoId(const HINTERNET *request, bool* errorFound)
{
   std::wstring outputString;
   int result = ::WinHttpReceiveResponse(*request, nullptr);
   unsigned long dwSize = sizeof(unsigned long);
   if (result)
   {
      wchar_t headers[1024];
      dwSize = ARRAYSIZE(headers) * sizeof(wchar_t);
      result = ::WinHttpQueryHeaders(
         *request, WINHTTP_QUERY_RAW_HEADERS, nullptr, headers, &dwSize, nullptr);
   }
   if (result)
   {
      char resultText[1024] = {0};
      unsigned long bytesRead;
      dwSize = ARRAYSIZE(resultText) * sizeof(char);
      result =::WinHttpReadData(*request, resultText, dwSize, &bytesRead);
      if (result)
      {
// string を wstring に変換する
         int wideSize = MultiByteToWideChar(CP_UTF8, 0, resultText, -1, 0, 0);
         wchar_t* wideString = new wchar_t[wideSize];
         result = MultiByteToWideChar(CP_UTF8, 0, resultText, -1, wideString, wideSize);
         if (result)
         {
            std::wstring photoId = GetXmlElementValueByName(wideString, L"photoid", errorFound);
            if (!(*errorFound))
            {
               outputString = photoId;
            }
         }
         delete [] wideString;
      }
   }
   return outputString;
}

サーバーからの応答は XML 文字列になります。FlickrUploader::GetXmlElementValueByName メソッドは、XMLLite API を使用し、CreateXmlReader 関数を呼び出して XML リーダー オブジェクトを作成し、photoid 要素が見つかるまで各要素を反復処理します。こうして最終的に、アップロードした写真の ID である要素の値を返します。SharedDialog::ImageUploadThreadProc メソッドは、これらの ID を格納し、すべての写真がアップロードされたときに、アップロードされた画像を表示するページへの URL を作成します。

Cryptographic API: Next Generation の使用

Flickr サービス メソッドを呼び出すときは、その都度メソッド パラメーターと共にメッセージ ダイジェストを渡す必要があります。Flickr はこのメッセージ ダイジェストを使用して、パラメーターの整合性を確認し、要求が伝送中に、悪意によってまたは偶然に変更されていないかどうかを検出することができます。ダイジェストは、Hilo Flickr アプリケーションのシークレット、アルファベット順の名前付きパラメーター、および Web サービス メソッド名を連結した文字列から生成されます。ダイジェストは、このパラメーター文字列の MD5 ハッシュ値を 16 進文字列で表したものです。

このダイジェストはプライバシーのために使用されるわけではないため、パラメーターはメッセージ ダイジェストと共にクリアテキストとして渡されます。Flickr は要求を受信したときに、要求を行った Flickr クライアント アプリケーション (Hilo Browser) のシークレットを取得し、このシークレットとパラメーターを使用して MD5 ハッシュを作成することもできます。Flickr は作成したハッシュと要求のメッセージ ダイジェストを比較します。この 2 つが同じであれば、メッセージが途中で改ざんされたり、壊れたりしていないということになります。

MD5 ハッシュを作成するために、Hilo では Windows Vista で導入された Windows Cryptography API: Next Generation (CNG) ライブラリを使用しています。以前のバージョンの Windows では、CryptoAPI によって暗号化機能を提供していました。CNG は CryptoAPI と比べて大きく進歩しています。米国国家安全保障局 (NSA) Suite B の一部である最新アルゴリズムなど、より多くの暗号化プロバイダーが提供されると共に、API がより論理的に分解されています。CNG のすべての機能は、プロバイダーを開き、アルゴリズムのプロパティを取得または設定し、暗号化アクションを実行し、プロバイダーを閉じるという、共通の手順でアクセスされます。

プロバイダーを開くには、BCryptOpenAlgorithmProvider 関数を呼び出して、アルゴリズムの名前、プロバイダー、および適切なフラグを指定します。この関数により BCRYPT_ALG_HANDLE ハンドルが返され、これを使用してアルゴリズムのプロパティを取得または設定できます。プロバイダーでの処理が終了したら、ハンドルを BCryptCloseAlgorithmProvider 関数に渡してプロバイダーを閉じる必要があります。

また、プロパティを通じて、暗号化アルゴリズムの動作を変更したり、アルゴリズムに関する情報を取得したりすることもできます。プロパティを取得するには、BCryptGetProperty 関数を呼び出して、開いているプロバイダーのハンドル、プロパティの名前、プロパティ値を受信するために呼び出し元が割り当てたバッファーへのポインターを渡します。この関数には、バッファーのサイズについての入力/出力パラメーターも含まれます。プロパティの中には、サイズがわかっているもの (たとえば、暗号化キーを 32 ビット値として返す BCRYPT_KEY_LENGTH) と、サイズが不明なものがあります。サイズが不明な場合は、バッファーへのポインターとして NULL 値を指定して BCryptGetProperty を呼び出すと、バッファー サイズを指定するためのポインターを介して、必要なバッファー サイズが返されます。プロパティの変更も同様です。BCryptSetProperty 関数を呼び出して、プロバイダー ハンドル、プロパティの名前、および新しいプロパティ値を格納するバッファーへのポインターを渡し、パラメーターを使用してこのバッファーのサイズを指定します。

アルゴリズムのプロパティを設定した後は、CNG 関数のいずれかを呼び出して暗号化アクションを実行できます。データを暗号化または復号化する場合は、キーを提供する必要があります。そのためには、対称 (または秘密) キーを作成する BCryptGenerateSymmetricKey 関数か、非対称 (またはパブリック/プライベート) キーのペアを作成する BCryptGenerateKeyPair 関数を呼び出してキーを作成します。これらの関数により BCRYPT_KEY_HANDLE ハンドルが返されます。ハンドルの処理が終了したら、BCryptDestroyKey 関数を呼び出して関連付けられたリソースを解放します。次にこのキーを、入出力値のバッファーと共に、BCryptEncrypt に渡してデータを暗号化するか、BCryptDecrypt に渡してデータを復号化することができます。

ハッシュの作成は暗号化処理であり、アルゴリズムで使用されるキーを指定して実行できますが、これは省略可能です。データをハッシュするには、BCryptCreateHash 関数を呼び出してハッシュ オブジェクトを作成する必要があります。この関数から返されるハッシュ オブジェクト ハンドルは、ハッシュ処理が完了したら BCryptDestroyHash 関数を呼び出して閉じる必要があります。ハッシュを実行するには、BCryptHashData 関数を呼び出して、ハッシュ オブジェクトへのハンドルと、ハッシュするデータを含むバッファーへのポインター (およびバッファーのサイズを指定するパラメーター) を渡します。この関数がデータをハッシュして返すわけではありません。関数に渡されたデータは変更されず、ハッシュ オブジェクトによってメモリ内にハッシュが保持されます。これは、何度でも BCryptHashData 関数を呼び出して、ハッシュされたデータをハッシュできるようにするためです。ハッシュされた値を取得するには、BCryptFinishHash 関数を呼び出して、ハッシュ オブジェクトへのハンドルおよびユーザーが割り当てたバッファーとそのサイズを渡します。この関数により、ハッシュ オブジェクトからバッファーにハッシュがコピーされます。バッファーのサイズは、BCRYPT_HASH_LENGTH プロパティを通じて提供されます。

Hilo によるデータのハッシュ

Hilo には、パラメーターとして渡された文字列から MD5 ハッシュを生成する FlickrUploader::CalculateMD5Hash というメソッドが用意されています。ハッシュは 2 進値であるため、CalculateMD5Hash メソッドから返される文字列は、16 進エンコードされた値です。リスト 6 に、このメソッドの前半の、ハッシュ オブジェクトを作成する部分を示します。まず、このコードは BCryptOpenAlgorithmProvider 関数を呼び出して、既定の MD5 プロバイダーを開きます。次に、このメソッドは BCryptGetProperty を 2 回呼び出します。1 回目はハッシュ オブジェクトのサイズを取得するためで、2 回目は生成されたハッシュに必要なバッファーのサイズを取得するためです。この 2 つのバッファーは、HeapAlloc 関数を呼び出すことによってプロセス ヒープ上に作成されますが、適切なメモリ アロケーターを使用して割り当てることが可能です。最後に、CalculateMD5Hash メソッドは、ハッシュ オブジェクト用に割り当てられたバッファーを初期化する BCryptCreateHash 関数を呼び出してハッシュ オブジェクトを作成します。

リスト 6 ハッシュ オブジェクトの作成

std::wstring FlickrUploader::CalculateMD5Hash(const std::wstring& buffer)
{
// wstring を string に変換する
   std::string byteString(buffer.begin(), buffer.end());

// アルゴリズム ハンドルを開く
   BCRYPT_ALG_HANDLE algorithm = nullptr;
   BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_MD5_ALGORITHM, nullptr, 0);

// ハッシュ オブジェクトを保持するためのバッファーのサイズを計算する
   unsigned long dataSize = 0;
   unsigned long hashObjectSize = 0;
   BCryptGetProperty(
      algorithm, BCRYPT_OBJECT_LENGTH, (unsigned char*)&hashObjectSize, sizeof(unsigned long), &dataSize, 0);

// ヒープにハッシュ オブジェクトを割り当てる
   unsigned char* hashObject = nullptr;
   hashObject = (unsigned char*) HeapAlloc(GetProcessHeap (), 0, hashObjectSize);

// ハッシュの長さを計算する
   unsigned long  hashSize = 0;
   BCryptGetProperty(
      algorithm, BCRYPT_HASH_LENGTH, (unsigned char*)&hashSize, sizeof(unsigned long), &dataSize, 0);

// ヒープにハッシュ バッファーを割り当てる
   unsigned char* hash = nullptr;
   hash = (unsigned char*)HeapAlloc (GetProcessHeap(), 0, hashSize);

// ハッシュを作成する
   BCRYPT_HASH_HANDLE cryptHash = nullptr;
   BCryptCreateHash(algorithm, &cryptHash, hashObject, hashObjectSize, nullptr, 0, 0);

CalculateMD5Hash メソッドの次の部分をリスト 7 に示します。このコードでは、BCryptHashData 関数を呼び出して、2 番目のパラメーターとして渡されたデータをハッシュします。ハッシュ オブジェクトには実際のハッシュが保持されており、これは BCryptFinishHash 関数を呼び出し、前にハッシュの保持用に割り当てられたバッファーを渡すことにより取得されます。最後に、この 2 進値が 16 進文字列に変換されます。

リスト 7 データのハッシュ

   // データのハッシュ
   BCryptHashData(
      cryptHash, (unsigned char*)byteString.c_str(), static_cast<unsigned long>(byteString.length()), 0);

// ハッシュを閉じてハッシュ データを取得する
   BCryptFinishHash(cryptHash, hash, hashSize, 0);

   std::wstring resultString;
// 問題がなければ、バイトを出力文字列にコピーする 
   std::wostringstream hexString;
   for (unsigned short i = 0; i < hashSize; i++)
   {
      hexString << std::setfill(L'0') << std::setw(2) << std::hex << hash[i];
   }
   resultString =  hexString.str();

CalculateMD5Hash メソッドの最後の部分をリスト 8 に示します。この部分は、ハッシュ オブジェクトと暗号化プロバイダーを解放し、以前に割り当てたメモリを解放するために必要なクリーンアップです。

リスト 8 メソッドで使用されたオブジェクトとバッファーのクリーンアップ

   // クリーンアップ
   BCryptCloseAlgorithmProvider(algorithm, 0);
   BCryptDestroyHash(cryptHash);
   HeapFree(GetProcessHeap(), 0, hashObject);
   HeapFree(GetProcessHeap(), 0, hash);

   return resultString;
}

まとめ

この章では、Hilo Browser で、HTTP サービス API を使用して HTTP POST 要求を行い、Flickr Web サイトに写真をアップロードする方法、また、HIlo がタスク ダイアログを通じて、アップロード操作に関する視覚的なフィードバックをユーザーに提供していることを示しました。また、クライアントから渡されたパラメーターの整合性を検証するために Flickr で使用される暗号化ハッシュを、 CNG 関数を使用して作成できることも説明しました。次の章では、Hilo で Windows 7 Web サービス API を使用して Flickr Web サービスを呼び出し、認証セッション キーとアクセス トークンを取得する方法を説明します。

前へ | 次へ | ホーム