C#でSHA256ハッシュ化する方法|文字列・ファイルの実装例とパスワード保存の注意点

はじめに

C#で文字列やファイルの内容をSHA256ハッシュ化したい場面はよくあります。たとえば、入力データが改ざんされていないか確認したい、ファイルの同一性をチェックしたい、API連携で署名用のハッシュ値を作りたい、といったケースです。

SHA256は、C#ではSystem.Security.Cryptography名前空間に用意されているクラスを使って簡単に実装できます。ただし、単に「SHA256でハッシュ化する」といっても、文字列のエンコーディング、ハッシュ値の16進数変換、ファイル読み込み、パスワード保存時の注意点など、押さえておくべきポイントがあります。

この記事では、C#でSHA256ハッシュ化する基本から、文字列・ファイルの実装例、ハッシュ値の比較方法、パスワード保存での注意点まで解説します。

1. C#でSHA256ハッシュ化する前に知っておきたい基礎

1-1. SHA256とは何か

SHA256とは、任意のデータから256ビットのハッシュ値を生成するハッシュ関数です。256ビットは32バイトであり、16進数文字列で表すと64文字になります。

たとえば、文字列helloをSHA256でハッシュ化すると、次のような64文字の値になります。

2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824

SHA256の特徴は、同じ入力からは常に同じハッシュ値が得られることです。一方で、入力が1文字でも変わると、まったく異なるハッシュ値になります。

1-2. ハッシュ化と暗号化の違い

ハッシュ化と暗号化は混同されがちですが、目的が異なります。

暗号化は、元のデータを復元できる形に変換する処理です。正しい鍵を使えば、暗号文から元の平文に戻せます。

一方、ハッシュ化は、元のデータを固定長のハッシュ値に変換する処理です。SHA256で生成したハッシュ値から、元の文字列やファイル内容を復元することはできません。

つまり、C#でSHA256を使うときは「データを隠す」ためではなく、「同じデータかどうかを確認する」ために使うと考えると分かりやすいです。

1-3. SHA256でできること・できないこと

SHA256でできる主なことは、データの同一性確認や改ざん検知です。

たとえば、あるファイルのSHA256ハッシュ値を事前に公開しておき、ダウンロード後に再計算したハッシュ値と比較すれば、ファイルが壊れていないか、改ざんされていないかを確認できます。

一方で、SHA256だけでデータを安全に保護できるわけではありません。特にパスワード保存において、パスワードを単純にSHA256でハッシュ化するだけの実装は推奨されません。

1-4. C#でSHA256を使う主な用途

C#でSHA256を使う主な用途には、次のようなものがあります。

・文字列のハッシュ値生成
・ファイルの改ざんチェック
・データの同一性確認
・API署名やチェックサムの生成
・ログや監査用の識別値作成

ただし、パスワード保存では、SHA256をそのまま使うのではなく、PBKDF2、bcrypt、Argon2など、パスワード保存向けのアルゴリズムを検討する必要があります。

2. C#で文字列をSHA256ハッシュ化する方法

2-1. 必要な名前空間

C#でSHA256を使うには、次の名前空間を使用します。

C#
using System;
using System.Security.Cryptography;
using System.Text;

System.Security.CryptographyはSHA256などの暗号関連クラスを使うために必要です。

System.Textは、文字列をバイト配列に変換するために使います。SHA256は文字列を直接処理するのではなく、バイト配列を入力としてハッシュ値を生成します。

2-2. 文字列をUTF-8のバイト配列に変換する

SHA256で文字列をハッシュ化するには、まず文字列をバイト配列に変換します。

一般的には、UTF-8を使います。

C#
string input = "hello";
byte[] bytes = Encoding.UTF8.GetBytes(input);

ここで重要なのは、エンコーディングが変わるとハッシュ値も変わるという点です。

たとえば、同じ日本語文字列でも、UTF-8でバイト配列にした場合とShift_JISでバイト配列にした場合では、SHA256の結果が異なります。

C#でc# sha256の実装を行う場合は、特別な理由がなければEncoding.UTF8を使うのが一般的です。

2-3. SHA256.HashDataでハッシュ値を生成する実装例

.NET 5以降では、SHA256.HashDataを使うとシンプルにSHA256ハッシュ値を生成できます。

C#
using System;
using System.Security.Cryptography;
using System.Text;

class Program
{
static void Main()
{
string input = "hello";

byte[] bytes = Encoding.UTF8.GetBytes(input);
byte[] hashBytes = SHA256.HashData(bytes);

string hash = Convert.ToHexString(hashBytes);

Console.WriteLine(hash);
}
}

出力例は次のようになります。

2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824

SHA256.HashDataは静的メソッドなので、SHA256.Create()でインスタンスを作成せずに使えます。短いコードで書けるため、新しい.NET環境では使いやすい方法です。

小文字の16進数文字列にしたい場合は、ToLowerInvariant()を使います。

C#
string hash = Convert.ToHexString(hashBytes).ToLowerInvariant();

2-4. SHA256.Createを使った従来の実装例

従来のC#では、SHA256.Create()を使ってインスタンスを生成し、ComputeHashでハッシュ値を計算する書き方がよく使われます。

C#
using System;
using System.Security.Cryptography;
using System.Text;

class Program
{
static void Main()
{
string input = "hello";

byte[] bytes = Encoding.UTF8.GetBytes(input);

using SHA256 sha256 = SHA256.Create();
byte[] hashBytes = sha256.ComputeHash(bytes);

string hash = Convert.ToHexString(hashBytes);

Console.WriteLine(hash);
}
}

SHA256.Create()を使う場合は、SHA256オブジェクトを使い終わったあとに破棄する必要があります。そのため、usingを使ってリソースを適切に解放します。

古いC#の書き方にする場合は、次のようにも書けます。

C#
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(bytes);
}

2-5. ハッシュ値を16進数文字列に変換する方法

SHA256で得られるハッシュ値はbyte[]です。画面表示や保存、比較をしやすくするため、多くの場合は16進数文字列に変換します。

.NET 5以降であれば、Convert.ToHexStringを使うのが簡単です。

C#
string hash = Convert.ToHexString(hashBytes);

ただし、Convert.ToHexStringは大文字の16進数文字列を返します。小文字に統一したい場合は次のようにします。

C#
string hash = Convert.ToHexString(hashBytes).ToLowerInvariant();

古い.NET FrameworkなどでConvert.ToHexStringが使えない場合は、StringBuilderを使って変換できます。

C#
static string ToHexString(byte[] bytes)
{
var builder = new StringBuilder();

foreach (byte b in bytes)
{
builder.Append(b.ToString("x2"));
}

return builder.ToString();
}

"x2"は、1バイトを2桁の小文字16進数で表す指定です。大文字にしたい場合は"X2"を使います。

3. C#でファイルをSHA256ハッシュ化する方法

3-1. ファイルのSHA256ハッシュを取得する基本コード

C#でファイルのSHA256ハッシュを取得する場合も、基本的な流れは文字列の場合と同じです。ただし、入力が文字列ではなくファイルのバイト列になります。

C#
using System;
using System.IO;
using System.Security.Cryptography;

class Program
{
static void Main()
{
string filePath = @"C:\temp\sample.zip";

byte[] fileBytes = File.ReadAllBytes(filePath);
byte[] hashBytes = SHA256.HashData(fileBytes);

string hash = Convert.ToHexString(hashBytes);

Console.WriteLine(hash);
}
}

このコードでは、File.ReadAllBytesでファイル全体をメモリに読み込んでからSHA256ハッシュ値を計算しています。

小さいファイルであればこの方法でも問題ありませんが、大きなファイルではメモリ使用量が大きくなるため注意が必要です。

3-2. FileStreamを使って大きなファイルを処理する方法

大きなファイルをSHA256ハッシュ化する場合は、FileStreamを使ってストリームとして処理する方法が適しています。

C#
using System;
using System.IO;
using System.Security.Cryptography;

class Program
{
static void Main()
{
string filePath = @"C:\temp\large-file.iso";

using FileStream stream = File.OpenRead(filePath);
using SHA256 sha256 = SHA256.Create();

byte[] hashBytes = sha256.ComputeHash(stream);
string hash = Convert.ToHexString(hashBytes);

Console.WriteLine(hash);
}
}

ComputeHashにはStreamを渡せます。これにより、ファイル全体を一度にメモリへ読み込む必要がなくなります。

大容量ファイルを扱う場合は、File.ReadAllBytesよりもFileStreamを使った実装を選ぶのが安全です。

3-3. ダウンロードファイルの改ざんチェックに使う方法

SHA256は、ダウンロードしたファイルが正しいかどうかを確認する用途でよく使われます。

たとえば、公式サイトに次のようなSHA256ハッシュ値が掲載されているとします。

ABCDEF123456...

ダウンロードしたファイルに対してC#でSHA256を計算し、公式サイトの値と一致するか確認します。

C#
using System;
using System.IO;
using System.Security.Cryptography;

class Program
{
static void Main()
{
string filePath = @"C:\temp\download.zip";
string expectedHash = "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890";

using FileStream stream = File.OpenRead(filePath);
using SHA256 sha256 = SHA256.Create();

byte[] hashBytes = sha256.ComputeHash(stream);
string actualHash = Convert.ToHexString(hashBytes);

if (string.Equals(actualHash, expectedHash, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("ファイルは一致しています。");
}
else
{
Console.WriteLine("ファイルが一致しません。破損または改ざんの可能性があります。");
}
}
}

比較時には、大文字・小文字の違いで不一致にならないように、StringComparison.OrdinalIgnoreCaseを使っています。

3-4. 文字列ハッシュとファイルハッシュの違い

文字列ハッシュとファイルハッシュの違いは、SHA256に渡す入力データです。

文字列の場合は、文字列をUTF-8などのエンコーディングでバイト配列に変換してからハッシュ化します。

C#
byte[] bytes = Encoding.UTF8.GetBytes("hello");

ファイルの場合は、ファイルそのもののバイト列をハッシュ化します。

C#
using FileStream stream = File.OpenRead(filePath);
byte[] hashBytes = sha256.ComputeHash(stream);

つまり、SHA256自体の処理は同じですが、入力データの作り方が異なります。

特に文字列の場合は、エンコーディングの違いでハッシュ値が変わるため注意が必要です。

4. SHA256ハッシュ値を比較・検証する方法

4-1. 入力値から再計算して一致確認する流れ

SHA256ハッシュ値を検証する基本的な流れは、元データから再度ハッシュ値を計算し、保存済みまたは期待値のハッシュ値と比較することです。

C#
static bool VerifySha256(string input, string expectedHash)
{
byte[] bytes = Encoding.UTF8.GetBytes(input);
byte[] hashBytes = SHA256.HashData(bytes);
string actualHash = Convert.ToHexString(hashBytes);

return string.Equals(actualHash, expectedHash, StringComparison.OrdinalIgnoreCase);
}

使い方は次のとおりです。

C#
bool result = VerifySha256(
"hello",
"2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824"
);

Console.WriteLine(result);

一致すればtrue、一致しなければfalseになります。

4-2. 大文字・小文字の違いに注意する

SHA256のバイト列自体に大文字・小文字の違いはありません。しかし、16進数文字列として表現するときに、大文字で出力するか小文字で出力するかの違いが出ます。

たとえば、次の2つは同じハッシュ値を表しています。

2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

比較するときは、次のように大文字・小文字を無視して比較するのが実用的です。

C#
string.Equals(actualHash, expectedHash, StringComparison.OrdinalIgnoreCase);

または、保存時・比較時に小文字へ統一しても構いません。

C#
string normalizedHash = hash.ToLowerInvariant();

4-3. エンコーディング違いで結果が変わる原因

C#で日本語文字列をSHA256ハッシュ化するときに、想定と違う結果になる原因として多いのがエンコーディングの違いです。

SHA256は文字列そのものではなく、バイト列を処理します。そのため、同じ文字列でもUTF-8、UTF-16、Shift_JISなど、どのエンコーディングでバイト列に変換したかによって結果が変わります。

たとえば、次の2つは異なるハッシュ値になります。

C#
byte[] utf8Bytes = Encoding.UTF8.GetBytes("こんにちは");
byte[] unicodeBytes = Encoding.Unicode.GetBytes("こんにちは");

外部システムやAPI仕様とハッシュ値を合わせる場合は、どのエンコーディングを使うべきかを必ず確認してください。

特に指定がない場合は、UTF-8を使う実装が一般的です。

4-4. 固定文字列の比較で避けたい実装

ハッシュ値を比較するときに、単純な文字列比較で問題ない場面もありますが、認証やセキュリティに関わる比較では注意が必要です。

避けたい例は次のような実装です。

C#
if (actualHash == expectedHash)
{
Console.WriteLine("一致");
}

一般的なファイル検証であれば大きな問題にならないこともありますが、パスワードや認証トークンのような機密情報に関わる比較では、比較処理の時間差を利用した攻撃を考慮する場合があります。

バイト配列同士で比較できる場合は、CryptographicOperations.FixedTimeEqualsを使う方法があります。

C#
static bool FixedTimeCompare(byte[] actual, byte[] expected)
{
return CryptographicOperations.FixedTimeEquals(actual, expected);
}

ハッシュ値を文字列ではなくバイト配列で保持できる設計であれば、固定時間比較を検討するとよいでしょう。

5. パスワード保存にSHA256を使うときの注意点

5-1. パスワードを平文保存してはいけない理由

パスワードをデータベースに平文のまま保存してはいけません。

もしデータベースが漏えいした場合、ユーザーのパスワードがそのまま第三者に知られてしまいます。多くのユーザーは複数のサービスで同じパスワードを使い回している可能性があるため、被害が他のサービスにも広がるおそれがあります。

そのため、パスワードは元に戻せない形で保存する必要があります。

ただし、「元に戻せない形にする」という理由だけで、単純なSHA256ハッシュ化を選ぶのは不十分です。

5-2. SHA256で単純にハッシュ化するだけでは不十分な理由

パスワードを次のようにSHA256でハッシュ化して保存する実装は、推奨されません。

C#
byte[] bytes = Encoding.UTF8.GetBytes(password);
byte[] hashBytes = SHA256.HashData(bytes);
string passwordHash = Convert.ToHexString(hashBytes);

理由は、SHA256が高速に計算できるハッシュ関数だからです。

SHA256はファイル検証やデータ同一性確認には便利ですが、パスワード保存では「高速であること」が弱点になります。攻撃者がハッシュ値を入手した場合、大量の候補パスワードを高速に試せるためです。

また、同じパスワードからは同じSHA256ハッシュ値が生成されるため、複数ユーザーが同じパスワードを使っている場合に同じハッシュ値として保存されてしまいます。

5-3. ソルトを付ける必要性

パスワード保存では、パスワードごとにランダムなソルトを付ける必要があります。

ソルトとは、パスワードに追加するランダムな値です。ソルトを使うことで、同じパスワードでも異なるハッシュ値になります。

悪い例は、全ユーザーで同じ方法で単純にSHA256を計算することです。

C#
hash = SHA256(password);

より安全な考え方は、ユーザーごとにランダムなソルトを生成し、パスワードと組み合わせてハッシュ化することです。

hash = Hash(password + salt)

ただし、実際のパスワード保存では、ソルト付きSHA256を自作するよりも、PBKDF2、bcrypt、Argon2などのパスワード保存向けアルゴリズムを使うべきです。

5-4. ストレッチングが必要な理由

ストレッチングとは、ハッシュ計算を多数回繰り返して、意図的に処理を遅くする仕組みです。

パスワード保存では、攻撃者が総当たり攻撃をしにくくするために、ハッシュ計算を高速にしすぎないことが重要です。

SHA256を1回だけ計算する実装は非常に高速です。そのため、漏えいしたハッシュ値に対して、攻撃者が大量のパスワード候補を試しやすくなります。

PBKDF2などのアルゴリズムでは、反復回数を指定してハッシュ計算のコストを調整できます。

5-5. PBKDF2・bcrypt・Argon2などを使うべきケース

ユーザーのパスワードを保存する場合は、単純なSHA256ではなく、パスワード保存向けのアルゴリズムを使うべきです。

代表的な選択肢には、次のようなものがあります。

・PBKDF2
・bcrypt
・scrypt
・Argon2

C#では、標準ライブラリでPBKDF2を扱うためにRfc2898DeriveBytesを利用できます。

実装例は次のとおりです。

C#
using System;
using System.Security.Cryptography;
using System.Text;

class Program
{
static void Main()
{
string password = "user-password";

byte[] salt = RandomNumberGenerator.GetBytes(16);

byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
password: password,
salt: salt,
iterations: 100_000,
hashAlgorithm: HashAlgorithmName.SHA256,
outputLength: 32
);

string saltBase64 = Convert.ToBase64String(salt);
string hashBase64 = Convert.ToBase64String(hash);

Console.WriteLine(saltBase64);
Console.WriteLine(hashBase64);
}
}

この例では、ランダムなソルトを生成し、PBKDF2でパスワードからハッシュ値を導出しています。

保存時には、ハッシュ値だけでなく、ソルト、反復回数、使用したアルゴリズムも保存しておく必要があります。検証時に同じ条件で再計算するためです。

5-6. C#でパスワード保存を実装する際の推奨方針

C#でパスワード保存を実装する場合、基本方針は次のとおりです。

・パスワードを平文保存しない
・単純なSHA256ハッシュだけで保存しない
・ユーザーごとにランダムなソルトを使う
・PBKDF2、bcrypt、Argon2などを使う
・反復回数やパラメータを保存する
・比較時には安全な比較方法を検討する

ASP.NET Core Identityを使っている場合は、自前でSHA256ハッシュ化を実装するより、標準のパスワードハッシュ機能を利用する方が安全です。

一方、ファイルの改ざんチェックやデータの同一性確認であれば、C#のSHA256実装は有効です。用途によってSHA256を使うべきか、パスワード保存向けアルゴリズムを使うべきかを分けて考えることが重要です。

6. C#のSHA256実装でよくあるエラーと対処法

6-1. using System.Security.Cryptographyがない

SHA256を使おうとして、次のようなエラーが出ることがあります。

The name 'SHA256' does not exist in the current context

この場合、必要な名前空間が不足している可能性があります。

ファイルの先頭に次のusingを追加してください。

C#
using System.Security.Cryptography;

文字列をバイト配列に変換する場合は、あわせて次も必要です。

C#
using System.Text;

ファイルを扱う場合は、次も追加します。

C#
using System.IO;

6-2. Convert.ToHexStringが使えない

Convert.ToHexStringが使えない場合、使用している.NETのバージョンが古い可能性があります。

その場合は、StringBuilderを使って16進数文字列に変換できます。

C#
using System.Text;

static string BytesToHex(byte[] bytes)
{
var builder = new StringBuilder(bytes.Length * 2);

foreach (byte b in bytes)
{
builder.Append(b.ToString("x2"));
}

return builder.ToString();
}

使い方は次のとおりです。

C#
string hash = BytesToHex(hashBytes);

大文字の16進数にしたい場合は、"x2"ではなく"X2"を使います。

C#
builder.Append(b.ToString("X2"));

6-3. 日本語文字列のハッシュ値が想定と違う

日本語文字列のSHA256ハッシュ値が他のツールやシステムと一致しない場合、まず確認すべきなのはエンコーディングです。

C#で次のように書くと、UTF-8でバイト配列に変換されます。

C#
byte[] bytes = Encoding.UTF8.GetBytes("こんにちは");

一方、外部システムがShift_JISやUTF-16で計算している場合、同じ文字列でもハッシュ値は一致しません。

また、末尾の改行や空白もハッシュ値に影響します。

"hello"
"hello "
"hello\n"

これらはすべて異なる入力として扱われるため、SHA256の結果も異なります。

想定と違う場合は、次の点を確認してください。

・エンコーディングは一致しているか
・末尾に改行が含まれていないか
・余分な空白が含まれていないか
・大文字・小文字を区別して比較していないか

6-4. ファイル読み込み時に例外が発生する

ファイルのSHA256ハッシュ値を計算するときに、ファイル読み込みで例外が発生することがあります。

主な原因は次のとおりです。

・ファイルパスが間違っている
・ファイルが存在しない
・アクセス権限がない
・別のプロセスがファイルを使用している
・パス文字列のエスケープが正しくない

ファイルが存在するか確認するには、File.Existsを使います。

C#
string filePath = @"C:\temp\sample.zip";

if (!File.Exists(filePath))
{
Console.WriteLine("ファイルが存在しません。");
return;
}

例外処理を含めた実装例は次のとおりです。

C#
try
{
using FileStream stream = File.OpenRead(filePath);
using SHA256 sha256 = SHA256.Create();

byte[] hashBytes = sha256.ComputeHash(stream);
string hash = Convert.ToHexString(hashBytes);

Console.WriteLine(hash);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルへのアクセス権限がありません。");
}
catch (IOException ex)
{
Console.WriteLine($"ファイル読み込み中にエラーが発生しました: {ex.Message}");
}

Windowsのパスを書く場合は、バックスラッシュをエスケープする必要があります。

C#
string path1 = "C:\\temp\\sample.zip";

または、逐語的文字列リテラルを使います。

C#
string path2 = @"C:\temp\sample.zip";

6-5. Disposeやusingを忘れた場合の注意点

SHA256.Create()FileStreamを使う場合は、使い終わった後にリソースを解放する必要があります。

特にファイルを開いたままにすると、他の処理でファイルを削除・移動・上書きできないことがあります。

推奨される書き方は、usingを使う方法です。

C#
using FileStream stream = File.OpenRead(filePath);
using SHA256 sha256 = SHA256.Create();

byte[] hashBytes = sha256.ComputeHash(stream);

古い構文では次のように書けます。

C#
using (FileStream stream = File.OpenRead(filePath))
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(stream);
}

usingを使うことで、処理が正常終了した場合だけでなく、例外が発生した場合でもリソースが適切に解放されます。

SHA256.HashDataを使う場合は、インスタンスを生成しないため、SHA256オブジェクトのDisposeを意識する必要はありません。ただし、ファイルを扱う場合はFileStreamの解放が必要です。

まとめ

C#でSHA256ハッシュ化するには、System.Security.Cryptography名前空間のSHA256クラスを使います。

文字列をハッシュ化する場合は、まずEncoding.UTF8.GetBytesでバイト配列に変換し、SHA256.HashDataまたはSHA256.Create().ComputeHashでハッシュ値を生成します。生成されたbyte[]は、Convert.ToHexStringStringBuilderを使って16進数文字列に変換できます。

ファイルをハッシュ化する場合は、小さいファイルならFile.ReadAllBytesでも実装できますが、大きなファイルではFileStreamを使う方法が適しています。

SHA256は、ファイルの改ざんチェックやデータの同一性確認には便利です。しかし、パスワード保存に単純なSHA256ハッシュ化を使うのは推奨されません。パスワード保存では、ソルトやストレッチングを備えたPBKDF2、bcrypt、Argon2などを使うべきです。

C#でc# sha256の実装を行う際は、用途に応じて正しい方法を選び、エンコーディング、16進数変換、大文字・小文字の比較、ファイル読み込み時の例外処理にも注意しましょう。