はじめに 「文字列ハッシュ化」は“中身を見せずに、同じかどうかだけ知る”技
文字列ハッシュ化は、一言でいうと、
「文字列の中身を、一定長の“指紋”に変換すること」
です。
元の文字列がどれだけ長くても、
ハッシュ値は決まった長さのバイト列(や16進文字列)になります。
業務では例えば、こういう場面で使われます。
同じデータかどうかを高速に判定したい
ログやIDをそのまま残したくないが、同一性だけは追跡したい
キャッシュのキーを短く・固定長にしたい
ここで大事なのは、
「.NETの GetHashCode() は使ってはいけない場面が多い」
「用途によって“軽いハッシュ”と“暗号学的ハッシュ”を使い分ける」
ということです。
ここから、C#での文字列ハッシュ化を、初心者向けに丁寧に分解していきます。
まず押さえるべきこと:GetHashCode() は“使いどころが違う”
GetHashCode() は「辞書用のハッシュ」であって「永続用・比較用」ではない
C#の string には GetHashCode() メソッドがあります。
string s = "Hello";
int h = s.GetHashCode();
C#これを見ると、「これでハッシュ取れるじゃん」と思いがちですが、
業務で「ファイルに保存する」「他システムと比較する」「ログに残す」といった用途には 使ってはいけません。
理由は主に2つあります。
同じ文字列でも、.NETのバージョンやプロセスごとに GetHashCode() の結果が変わる可能性がある
セキュリティ目的(改ざん検知・匿名化など)には全く向いていない
GetHashCode() は、Dictionary や HashSet の内部で「同じプロセス内での高速な検索」のために使うものです。
「永続化」「外部連携」「セキュリティ」を意識する場面では、
必ず System.Security.Cryptography のハッシュアルゴリズムを使います。
ここを最初にしっかり区別しておくのが、とても重要です。
実務でよく使うのは「SHA-256」などの暗号学的ハッシュ
SHA-256 のイメージ
暗号学的ハッシュ関数(SHA-256 など)は、
同じ入力 → 必ず同じハッシュ値
少しでも違う入力 → 全く違うハッシュ値
ハッシュ値から元の入力を復元するのは現実的に不可能
という性質を持っています。
例えば、次の2つの文字列は似ていますが、
SHA-256 の結果はまったく違う値になります。
"Hello""hello"
この「ちょっと違うだけで、ハッシュは全然違う」という性質が、
改ざん検知や同一性チェックに向いています。
C#での基本:SHA-256で文字列をハッシュ化するユーティリティ
バイト列にしてからハッシュを取り、16進文字列にする
文字列ハッシュ化の基本パターンは、
文字列 → エンコーディングでバイト配列に変換
バイト配列 → ハッシュアルゴリズムでハッシュ値(バイト配列)に変換
ハッシュ値(バイト配列) → 表示用の16進文字列に変換
という3ステップです。
まずは、SHA-256 を使ったユーティリティを作ってみます。
using System;
using System.Security.Cryptography;
using System.Text;
public static class HashUtil
{
public static string ComputeSha256Hex(string? text)
{
if (string.IsNullOrEmpty(text))
{
return string.Empty;
}
byte[] bytes = Encoding.UTF8.GetBytes(text);
using var sha = SHA256.Create();
byte[] hashBytes = sha.ComputeHash(bytes);
return ConvertToHex(hashBytes);
}
private static string ConvertToHex(byte[] bytes)
{
var sb = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
C#ここでの重要ポイントを整理します。
文字列を Encoding.UTF8.GetBytes でバイト配列にしているSHA256.Create() でハッシュアルゴリズムのインスタンスを作り、ComputeHash でハッシュを計算している
結果のバイト配列を、"x2" 形式で16進文字列にしている(よく見るハッシュ表現)
動作例
Console.WriteLine(HashUtil.ComputeSha256Hex("Hello"));
Console.WriteLine(HashUtil.ComputeSha256Hex("hello"));
Console.WriteLine(HashUtil.ComputeSha256Hex("こんにちは"));
C#出力されるのは、こんな感じの長い16進文字列です(実際の値は環境に依存しません)。
Hello と hello で、まったく違う値になることが確認できます。
エンコーディングを固定することの大事さ
「同じ文字列」でもエンコーディングが違うとハッシュも変わる
ハッシュは「バイト列」に対して計算されます。
つまり、同じ "こんにちは" でも、
UTF-8 でバイト列にした場合
UTF-16(Encoding.Unicode)でバイト列にした場合
では、ハッシュ値が変わります。
業務で「ハッシュ値をファイルに保存する」「他システムと比較する」といった用途では、
エンコーディングを必ず固定することが重要です。
一般的には、UTF-8 を使っておけばまず困りません。
byte[] bytes = Encoding.UTF8.GetBytes(text);
C#この1行を、ユーティリティの中で“固定ルール”としてしまうことで、
呼び出し側がエンコーディングを意識しなくて済むようになります。
SHA-1 や MD5 はどうなのか
もう新規では使わないほうがいい
昔は MD5 や SHA1 がよく使われていましたが、
今は「衝突耐性が弱い(安全性が足りない)」とされていて、
新規の設計では基本的に SHA-256 以上 を使うのが推奨です。
C#では、次のようなクラスがあります。
MD5SHA1SHA256SHA384SHA512
業務で「とりあえず安全側に寄せたい」なら、SHA256 を選んでおけばまず問題ありません。
注意:パスワードには「そのままハッシュ」は使わない
パスワードは専用の仕組み(ストレッチング・ソルト)が必要
ここで一つ、かなり重要な注意点です。
ユーザーのパスワードを扱うときに、
var hash = HashUtil.ComputeSha256Hex(password);
C#のように「そのままSHA-256にかける」のは、
セキュリティ的には不十分です。
パスワードには、
ソルト(salt)を加える
ストレッチング(何度もハッシュを繰り返す)
専用のアルゴリズム(PBKDF2, bcrypt, Argon2 など)
といった仕組みが必要になります。
ここは「文字列ハッシュ化ユーティリティ」の範囲を超えるので深掘りしませんが、
「パスワードは“ただのハッシュ”では扱わない」
ということだけは、強く意識しておいてください。
文字列ハッシュ化ユーティリティは、
ログID、トラッキング用の匿名化キー、改ざん検知用のチェックサムなど、
「パスワード以外」の用途に使うのが基本です。
バイト配列のまま扱いたい場合のユーティリティ
16進文字列ではなく、バイト配列で返す版
用途によっては、「16進文字列にせず、バイト配列のまま扱いたい」こともあります。
その場合は、こういうユーティリティも用意できます。
public static byte[] ComputeSha256Bytes(string? text)
{
if (string.IsNullOrEmpty(text))
{
return Array.Empty<byte>();
}
byte[] bytes = Encoding.UTF8.GetBytes(text);
using var sha = SHA256.Create();
return sha.ComputeHash(bytes);
}
C#これを使えば、
「ハッシュ値をそのままバイナリとしてファイルに書く」
「さらに別の処理に渡す」
といったことがしやすくなります。
業務ユーティリティとしてどうまとめるか
「アルゴリズム固定」と「表現形式」を決めておく
プロジェクト全体で使うユーティリティとしては、
アルゴリズム(例:SHA-256)を固定する
エンコーディング(例:UTF-8)を固定する
返り値の形式(16進文字列か、バイト配列か)を決める
という3点を、ユーティリティ側で“ルール化”しておくと、
呼び出し側が迷わなくて済みます。
例えば、こんな感じのまとめ方が考えられます。
public static class TextHash
{
public static string Sha256Hex(string? text)
=> HashUtil.ComputeSha256Hex(text);
public static byte[] Sha256Bytes(string? text)
=> HashUtil.ComputeSha256Bytes(text);
}
C#呼び出し側は、
ログ用のIDを作りたい → TextHash.Sha256Hex(rawId)
バイナリでチェックサムを持ちたい → TextHash.Sha256Bytes(content)
というように、意図に応じて選べます。
null や空文字の扱いを決めておく
今回の実装では、
null や空文字 → 空文字(または空配列)を返す
という方針にしています。
これにより、呼び出し側で毎回
if (text != null) { ... }
C#といったガードを書く必要がなくなり、
業務コードがすっきりします。
「nullは例外にしたい」などの方針があるなら、
そこもユーティリティ側で統一しておくとよいです。
まとめ 「文字列ハッシュ化ユーティリティ」は“中身を隠しつつ、同一性を追える安全な指紋”
文字列ハッシュ化は、
「中身をそのまま持ちたくないけれど、同じものかどうかは知りたい」
というニーズに応えるための、実務でとても使えるテクニックです。
押さえておきたいポイントは、
GetHashCode() は辞書用であり、永続化・外部連携・セキュリティ用途には使わない
暗号学的ハッシュ(SHA-256など)を使うのが基本
文字列 → UTF-8バイト列 → ハッシュ → 16進文字列、という流れをユーティリティ化しておく
エンコーディングとアルゴリズムを固定しておくことで、結果が安定する
パスワードには“ただのハッシュ”を使わず、専用の仕組みが必要
ここまで理解できれば、「なんとなくハッシュを取っている」段階から一歩進んで、
“用途と安全性を意識した、業務で使える文字列ハッシュ化ユーティリティ”を、
自分のC#コードの中にしっかり組み込めるようになっていきます。
