今回利用する「Google Authenticator」は、TOTP(Time-based One-Time Passwordの略)と呼ばれ、現在時刻を基準に一定時間ごとに変化するワンタイムパスワードを生成する認証方式です。主に 2 要素認証(2FA) で使われます。
2要素認証とは、ログイン時に2種類の異なる要素 を組み合わせて本人確認を行う認証方式です。
認証の「3 要素」
2FAでは、次のうち 異なる2つ を使用します。
| 知識情報
(知っているもの) |
所持情報
(持っているもの) |
生体情報
(本人そのもの) |
| ・パスワード
・PINコード |
・スマートフォン
・ワンタイムパスワード(OTP) ・ハードウェアトークン ・ICカード |
・指紋
・顔認証 ・虹彩認証 |
今回利用する「Google Authenticator」は
- パスワード:知識情報
- スマホ+OTP:所持情報となり
2FAが成立します。
2FA を使うメリット
- パスワード漏えい時でも不正ログインを防止
- フィッシング攻撃への耐性向上
- クラウドサービス・社内システムのセキュリティ強化
TOTP の仕組み(概念)
TOTP は次の 3 つの要素 から 6 桁(または 8 桁)のコードを生成します。
- 共有シークレットキー(登録時にサーバーとユーザーが共有)
- 現在時刻(通常 30 秒単位)
- ハッシュ関数(HMAC-SHA1 が標準)
なぜ「ワンタイム」なのか
- 一度使ったコードは 次の 30 秒で無効
- パスワードのように再利用できない
- フィッシング耐性が高い
全体構成(流れ)
- 2FA 有効化
- シークレットキー発行
- QRコード表示
- ログイン時のチェック
事前準備
「Google Authenticator」をc#で利用するための「OtpNet」と、QRコードを表示させるための「QRCoder」パッケージをNugetでインストールしておきます
1-1.シークレットキー発行
ユーザー登録時に、シークレットキー発行しておきます。登録後、QRコード表示し、
スマホで登録できるようにします。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class UserEntity { [DisplayName("利用者ID"), Key, Column("user_id", Order = 0), DatabaseGenerated(DatabaseGeneratedOption.None)] public string UserId { get; set; } [DisplayName("利用者名"), Column("user_name")] public string UserName { get; set; } [DisplayName("パスワード"), Column("user_password")] public string UserPassword { get; set; } [DisplayName("利用者Eメール"), Column("user_email")] public string UserEmail { get; set; } [DisplayName("暗号化キー"), Column("totp_secret")] public string TotpSecret { get; set; } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[Route("[controller]")] [ApiController] public class UserController : ControllerBase { //ユーザー登録時 [HttpPost("PostUser")] public ActionResult PostUser([FromBody] UserEntity data) { var secretKey = KeyGeneration.GenerateRandomKey(20); var base32Secret = Base32Encoding.ToString(secretKey); user.TotpSecret = base32Secret; // DBに保存以下省略 } } |
1-2.QRコード表示
ユーザー登録後、QRコード表示し、スマホで登録できるようにします。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[Route("[controller]")] [ApiController] public class UserController : ControllerBase { [Authorize] [HttpGet("GetQrCode")] public IActionResult GetQrCode(string account) { var issuer = "XXXXシステム"; var otpAuthUrl = $"otpauth://totp/{Uri.EscapeDataString(issuer)}:{Uri.EscapeDataString(account)}" + $"?secret={user.TotpSecret}&issuer={Uri.EscapeDataString(issuer)}"; // QRコード生成 var qrGenerator = new QRCodeGenerator(); var qrData = qrGenerator.CreateQrCode(otpAuthUrl, QRCodeGenerator.ECCLevel.Q); var qrCode = new PngByteQRCode(qrData); byte[] qrCodeBytes = qrCode.GetGraphic(20); return File(qrCodeBytes, "image/png"); } } |
2.ログイン時のチェック
ユーザーが入力したワンタイムパスワードをチェックします。許容時間ずれも考慮します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[Route("[controller]")] [ApiController] public class AuthController : ControllerBase { private bool VerifyTotp(UserEntity user, string inputCode) { // Base32 → byte[] byte[] secretKey = Base32Encoding.ToBytes(user.TotpSecret); var totp = new Totp(secretKey); // 現在の6桁コード var code = totp.ComputeTotp(); // 許容時間ずれ ±30秒(1ステップ) bool isValid = totp.VerifyTotp( inputCode, out long timeStepMatched, new VerificationWindow(previous: 1, future: 1)); return isValid; } } |
まとめ
「Google Authenticator」を利用して、簡単に2要素認証 (2FA) が実装出来ました。
ただ、暗号化キーの保存方法や、連続入力エラー時の対応など、対応すべき機能が
まだまだあります。この辺の機能は、ASP.NET Core Identityで対応できそうなので、
また試してみます。