現在ログインしていません。
新規アカウント作成
ログイン

String.GetHashCode

String.GetHashCodeは文字列ハッシュ値を生成してくれる簡単便利なメソッドです。 ハッシュ値とは値ごとに異なるもので、たとえば、"Apple"のハッシュ値は1548124622、"Banana"は-54669733、"アメンボ"は-97937524です。 ハッシュ値はコレクションなどシステム内部のアルゴリズムなどで使用されることが多く、ハッシュ値自体が直接何かの機能に役に立つということはほぼありません。

Dim st As String = "Apple"

Dim hash As Integer
hash = st.GetHashCode
■リスト1:VB ハッシュ値の取得方法

このハッシュ値自体には意味はなく「ほぼ」ランダムな無意味な値です。 ハッシュ値には次の特徴があります。

  • 元となる値(Appleなど)から、「ほぼ」一意に算出される。
  • 逆算が困難。ハッシュ値 1548124622 から元の値は「ほぼ」算出できない。
  • あるハッシュ値を元に別のハッシュ値を「ほぼ」推測できない。
たとえば、「AAA」のハッシュ値が -2048566364 だからと言って、「AAB」のハッシュ値は「ほぼ」推測できない。

このような特徴を持つものがハッシュ値であるので、ハッシュ値の計算アルゴリズムによっては元となる値が同じでもハッシュ値が違う場合があります。値自体には意味がないのでこのようなこと起こります。

だから、ハッシュ値をデータベースなど外部の値に取っておいて後で何かの利用するということはしないほうが無難です。 .NETの実装やプラットフォームの変更によっていつハッシュ値の計算アルゴリズムが変わるか保証がないからです。

.NETの文字列の既定のハッシュ値算出アルゴリズムは GetHashCodeメソッドで呼び出すことができます。 GetHashCodeメソッドは次の2つの条件が同じ場合、同じ文字列に対しては同じハッシュ値を生成するようです。

「ようです」という歯切れの悪い表現になっているのも、これが.NETの仕様で定められているわけではなく、2013年3月現在の.NET Frameworkの実装がそうだというだけだからです。つまり、この仕様は将来も変わらないという保証はありません。

条件1.プロセスが動作しているビット数

プロセスが動作しているビット数はハッシュ値に影響します。 つまり、同じ"Apple"でも32ビットプロセスが計算するハッシュ値と、64ビットプロセスが計算するハッシュ値は異なります。

これで注意が必要なのはWebアプリケーションです。WebアプリケーションはWebサーバー上で動作するため、Webサーバーが32ビットと64ビットのどちらでアプリケーションを動作させるかがハッシュ値に影響するということです。

Visual Studioに組み込まれている開発Webサーバー IIS Expressは既定では32ビットで動作するようです。 この設定は[ツール]-[オプション]-[プロジェクトおよびソリューション](この項目はオプション画面を上にスクロールすると表示されます。見つけにくいです。)-[Web プロジェクト]で「Web サイトおよびプロジェクト用 IIS Express の 64 ビット バージョンを使用する」で切り替えられます。

プロセスが動作しているビット数をプログラムで確認するには、System.Environment.Is64BitProcess を使用します。

条件2.UseRandomizedStringHashAlgorithmの設定値

UseRandomizedStringHashAlgorithmの設定はアプリケーション構成ファイルのconfiguration - runtime - UseRandomizedStringHashAlgorithm で行います。[1]既定値は無効なので、条件1のみでハッシュ値が決定します。

この設定を有効にするとアプリケーションドメインごとに異なるハッシュ値が生成されるようになります。 この設定は.NETのライブラリ内でハッシュ値を利用している箇所にも影響します。

Stringクラスにはこの有効・無効を取得できる非公開のメソッドUseRandomizedHashingがあります。非公開なので直接呼出しはダメなのですが、実験的に戻りを確認するくらいは許されるでしょう。たとえば、次のようにして確認できます。

Dim method As MethodInfo = "".GetType.GetMethod("UseRandomizedHashing", BindingFlags.NonPublic Or BindingFlags.Static)
Dim result As Boolean = CBool(method.Invoke(""Nothing))
■リスト2:

失敗事例

私はあるプロジェクトでユーザーを管理する機能を作成していました。 ユーザーのパスワードを直接データベースに保存することは危険なので、パスワード本体ではなく、パスワードのハッシュ値を保存するように設計しました。 簡単に言うとユーザーがログイン時に入力したパスワードのハッシュ値と、データベースに保存してあるハッシュ値を比較して、あっていればログイン成功という仕組みです。

またString.GetHashCodeのアルゴリズムが公開されていることも知っていたので、セキュリティ上のリスクを避けるためハッシュ値の算出には独自のアルゴリズムを実装しました。

これでパスワードの流出はありえないし、万一ハッシュ値が流出しても逆算は事実上不可能と自画自賛していたのですが、実はそのハッシュ値の計算過程の一部にString.GetHashCodeに依存するロジックがありました。

この結果、何が起こったかというと、ある環境で登録したユーザーが、別の環境ではパスワードが違うためログインできないという現象が発生してしまいました。

初期のテスト中は同じ環境でテストしているので気が付くのが遅れてしまいました。

このときは環境のビット数の問題だったのですが、GetHashCodeが生成するハッシュ値は永続性自体が保証されていないため、ビット数やアプリケーションドメインを合わせたとして、このような実装はすべきではないということです。

参考

  1. ^ UseRandomizedStringHashAlgorithm要素