Webセキュリティの小部屋

Twitter のフォローはこちらから Facebook ページはこちら RSSフィードのご登録はこちらから
公開日:2013年2月27日
最終更新日:2020年8月13日

安全なパスワードの保存方法(ASP.NET,C#,VB.NET編)

以下の記事の内容を受け、ASP.NET(C#,VB.NET) で salt(ソルト)を使用したパスワードと、salt にストレッチングを組み合わせた方法の具体的なコードを示します。

salt にストレッチングを組み合わせる方法ですが、その実装方法としては専用のハッシュ関数を使用することが現在は推奨されます。

なお、サンプルコードは C# のみとなります。

BCrypt

.NET で BCrypt の実装を行うには、BCrypt.Net (MIT ライセンス) を使用することが簡単です。

以下の実装は、.NET Core 2.1 のコンソールアプリケーションに、Nuget から BCrypt.Net-Next をインストールしたものになります。

using System;
using BCrypt.Net;

namespace BCryptSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // パスワードを設定
            string password = "password";

            // ハッシュ値取得
            string hash = GetHashedPassword(password);

            // 新規登録時はハッシュ値を保存する。

            // パスワードと保存してあったハッシュ値をチェック
            CheckHashedPassword(password, hash);

            Console.ReadKey();
        }

        // ハッシュ値取得
        private static string GetHashedPassword(string password)
        {
            string hash = BCrypt.Net.BCrypt.HashPassword(password);

            Console.WriteLine($"ハッシュ値:{hash}");

            return hash;
        }

        // パスワードチェック
        private static void CheckHashedPassword(string password, string hash)
        {
            if (BCrypt.Net.BCrypt.Verify(password, hash))
            {
                Console.WriteLine("パスワードが一致しました。");
            }
            else
            {
                Console.WriteLine("パスワードが一致しません。");
            }

        }
    }
}

PBKDF2 の実装

.NET で PBKDF2 を実装するには、System.Security.Cryptography.Rfc2898DeriveBytes を使用します。

.NET の標準機能で実装できますが、若干手間がかかります。

ソルトとハッシュ値の両方を保存する必要があります。

以下の実装は、.NET Core 2.1 のコンソールアプリケーションになります。

using System;
using System.Text;
using System.Security.Cryptography;

namespace PBKDF2Sample
{
    class Program
    {
        // ソルトのサイズ
        private const int SALT_SIZE = 32;

        // ハッシュのサイズ
        private const int HASH_SIZE = 32;

        //ストレッチング回数
        private const int STRETCH_COUNT = 1000;

        static void Main(string[] args)
        {
            // パスワードを設定
            string password = "password";

            // ソルトを取得
            string salt = GetSalt(SALT_SIZE);

            // ハッシュ値を取得
            string hash = GetHash(password, salt, HASH_SIZE, STRETCH_COUNT);

            // 新規登録時はソルトとハッシュ値を保存する

            // 保存してあったハッシュ値(これはダミー)
            string savedHash = hash;

            // 保存してあったハッシュ値と計算したハッシュ値が同じならOK
            CheckHashedPassword(hash, savedHash);

            Console.ReadKey();
        }

        // ソルトを取得
        private static string GetSalt(int size)
        {
            var bytes = new byte[size];
            using (var rngCryptoServiceProvider = new RNGCryptoServiceProvider())
            {
                rngCryptoServiceProvider.GetBytes(bytes);
            }

            string salt = Convert.ToBase64String(bytes);

            Console.WriteLine($"ソルト:{salt}");

            return salt;
        }

        // ハッシュ値を取得
        private static string GetHash(string password, string salt, int size, int cnt)
        {
            byte[] bytes = Encoding.ASCII.GetBytes(salt);
            byte[] bytesSalt;

            using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, bytes, cnt))
            { 
                bytesSalt = rfc2898DeriveBytes.GetBytes(size);
            }

            string hash = Convert.ToBase64String(bytesSalt);

            Console.WriteLine($"ハッシュ値:{hash}");

            return hash;
        }

        // ハッシュ値を比較
        private static void CheckHashedPassword(string hash, string savedHash)
        {
            if (hash == savedHash)
            {
                Console.WriteLine("パスワードが一致しました。");
            }
            else
            {
                Console.WriteLine("パスワードが一致しません。");
            }

        }
    }
}

実行結果は以下のようになります。
ソルト:cPDY1AORTFWWIz3gBalSa0tyMLsKwU90yXEDHXVJAgs=
ハッシュ値:Ap+0VtZ550vABI6lX8mj5w6CfRedwro1PjKbkuShfXs=
パスワードが一致しました。

推奨されなくなった方法

2019年1月現在、パスワードのハッシュ化は独自実装するのではなく、ハッシュ関数を使用することが推奨されています。

そのため、以下のコードは現在では推奨されないコードになります。

・C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Cryptography;
using System.Text;

namespace TestCS
{
public class SafePassword
{
private static int STRETCH_COUNT = 1000;

///

/// salt+ハッシュ化したパスワードを取得
///

public static string GetSaltedPassword(string password, string userId)
{
string salt = GetSha256(userId);
return GetSha256(salt + password);
}

///

/// salt + ストレッチングしたパスワードを取得(推奨)
///

public static string GetStretchedPassword(string password, string userId)
{
string salt = GetSha256(userId);
string hash = “”;

for (int i = 0; i < STRETCH_COUNT; i++)
{
hash = GetSha256(hash + salt + password);
}

return hash;

}

///

/// 文字列から SHA256 のハッシュ値を取得
///

private static String GetSha256(string target)
{
SHA256 mySHA256 = SHA256Managed.Create();
byte[] byteValue = Encoding.UTF8.GetBytes(target);
byte[] hash = mySHA256.ComputeHash(byteValue);

StringBuilder buf = new StringBuilder();

for (int i = 0; i < hash.Length; i++)
{
buf.AppendFormat(“{0:x2}”, hash[i]);
}

return buf.ToString();
}
}
}

・VB.NET

Imports System.Security.Cryptography

Public Class SafePassword
Private Shared STRETCH_COUNT As Integer = 1000

'''

'
''' salt +ハッシュ化したパスワードを取得'
'''

'
Public Shared Function GetSaltedPassword(password As String, userId As String) As String
Dim salt As String = GetSha256(userId)
Return GetSha256(salt + password)
End Function

'''

'
''' salt + ストレッチングしたパスワードを取得(推奨)'
'''

'
Public Shared Function GetStretchedPassword(password As String, userId As String) As String
Dim salt As String = GetSha256(userId)
Dim hash As String = “”

For i = 1 To STRETCH_COUNT
hash = GetSha256(hash + salt + password)
Next

Return hash
End Function

'''

'
''' 文字列から SHA256 のハッシュ値を取得'
'''

'
Private Shared Function GetSha256(ByVal target As String) As String
Dim mySHA256 As SHA256 = SHA256Managed.Create()
Dim byteValue As Byte() = Encoding.UTF8.GetBytes(target)
Dim hash As Byte() = mySHA256.ComputeHash(byteValue)

Dim buf As StringBuilder = New StringBuilder()

For i = 0 To hash.Length – 1
buf.AppendFormat(“{0:x2}”, hash(i))
Next

Return buf.ToString()
End Function

End Class

参考

Webアプリケーションセキュリティに関する記事は、以下のページにまとまっています。ぜひご確認ください。


スポンサーリンク





カテゴリー:Webアプリケーションセキュリティ対策

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA