LoginSignup
26
26

More than 5 years have passed since last update.

【C#】パスワードをハッシュ化して DB に登録する

Posted at

はじめに

DB にパスワードを保存するとき、そのまま平文で保存すると、
データが漏れた場合に、パスワードが 見た瞬間わかってしまいます。

この記事では C# を使って DB にパスワードをハッシュ化して登録してみたいと思います。

ハッシュ化の実装

パスワードを暗号化する際にsaltを利用すると別々のユーザが同じパスワードを使っても別のハッシュを作ることができます。
認証の際はsaltと平文のパスワードから作成できるハッシュが DB に登録されているハッシュと同じ場合、正しいパスワードです。

Cryptography.KeyDerivationを使用しています。

dotnet add PasswordHash.Lib package "Microsoft.AspNetCore.Cryptography.KeyDerivation"
PasswordHash/PasswordHash.Lib/PasswordService.cs
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System;
using System.Security.Cryptography;

namespace PasswordHash.Lib {
  public class PasswordService : IPasswordService {

    // ハッシュ化...平文パスワードを渡すとハッシュ化パスワード、使用されたソルトが返る
    public (string hashedPassword, byte[] salt) HashPassword(string rawPassword) {
      byte[] salt = GetSalt();
      string hashed = HashPassword(rawPassword, salt);
      return (hashed, salt);
    }

    // 認証...ハッシュ化パスワード、平文パスワード・ソルトを渡すと正しいパスワードなら true が返る
    public bool VerifyPassword(string hashedPassword, string rawPassword, byte[] salt) =>
      hashedPassword == HashPassword(rawPassword, salt);

    private string HashPassword(string rawPassword, byte[] salt) =>
      Convert.ToBase64String(
        KeyDerivation.Pbkdf2(
          password: rawPassword,
          salt: salt,
          prf: KeyDerivationPrf.HMACSHA512,
          iterationCount: 10000,
          numBytesRequested: 256 / 8));

    private byte[] GetSalt() {
      using (var gen = RandomNumberGenerator.Create()) {
        var salt = new byte[128 / 8];
        gen.GetBytes(salt);
        return salt;
      }
    }
  }
}

テスト 

使用例を兼ねたテストこんな感じです。

using Xunit;

namespace PasswordHash.Lib.Test {
  public class PasswordServiceTest {
    [Fact]
    public void TestVerifyPassword() {
      // 平文パスワード
      var rawPassword = "nossa1234";
      // テスト対象のクラス
      var sut = new PasswordService();  // sut means System Under Test

      // パスワードをハッシュ化、使用したソルトを得る
      var (hashed, salt) = sut.HashPassword(rawPassword);

      // 「ハッシュ」と「パスワード・ソルトから作成したハッシュ」が一致するかテスト
      Assert.True(sut.VerifyPassword(hashed, rawPassword, salt));
    }
  }
}
PasswordHash> dotnet test PasswordHash.Lib.Test
# 中略
テストの合計数: 1。成功: 1。失敗:0。スキップ: 0。
テストの実行に成功しました。
テスト実行時間: 2.0115 秒
  • テスト時のソルト
    image.png

  • ハッシュ化されたパスワード
    image.png

サンプルのWeb API

上記のライブラリを使用して簡易的な Web API を実装しました。
ユーザー登録と認証機能が使えます。

    // UsersController の登録処理の抜粋
    [HttpPost]
    public IActionResult RegisterUser([FromBody]RegisterUserRequest request) {
      bool success = userService.Register(request.UserName, request.RawPassword);
      return success ? Ok() : (IActionResult)Conflict();
    }

    // LoginController の認証の抜粋
    [HttpPost]
    public IActionResult Authenticate([FromBody]AuthenticateRequest request) {
      bool ok = userService.Authenticate(request.UserName, request.RawPassword);
      return ok ? Ok() : (IActionResult)Unauthorized();
    }

上記のコントローラーが移譲している実際のユーザー登録と認証は以下の通りです。

    // ユーザー登録
    public bool Register(string username, string rawPassword) {
      bool duplicated = dbContext.Users.Any(u => u.Name == username);
      if (duplicated) {
        return false;
      }
      (string hashed, byte[] salt) = passwordService.HashPassword(rawPassword);
      var user = new User {
        Name = username,
        HashedPassword = hashed,
        Salt = salt
      };
      dbContext.Users.Add(user);
      dbContext.SaveChanges();
      return true;
    }

    // 認証
    public bool Authenticate(string username, string rawPassword) {
      var user = dbContext.Users.SingleOrDefault(u => u.Name == username);
      if (user is null) {
        return false;
      }
      return passwordService.VerifyPassword(user.HashedPassword, rawPassword, user.Salt);
    }

ソースは GitHub に置きました。
https://github.com/sano-suguru/PasswordHash

以下のコマンドで試せます。
DB はインメモリDBを使っていますので、ストレージは汚しません。

git clone git@github.com:sano-suguru/PasswordHash.git
cd PasswordHash
dotnet run -p PasswordHash.App
  • ユーザーを登録
    image.png

  • 認証
    image.png

  • ユーザーが重複したとき
    image.png

  • 認証失敗
    image.png

最後に

学習を兼ねて作成しましたが、実際に認証処理を開発するときは抜け漏れがあると大変なので、
ASP.NET Identity や OAuth などを使用すると良いと思います。

26
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
26