🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

パスワード

パスワードは主に情報にアクセスする際に扱われます。主に、アクセス可能なユーザーを限定する手段として使われます。

ログイン

ログインは、ユーザーがコンピューターシステムにアクセスするプロセスの事を呼びます。

暗号化

ネットワークを通じてデジタルデータをやり取りする際に、第三者に解読されることのないよう、アルゴリズムを用いてデータを変換すること。

Q&A

解決済

1回答

4386閲覧

ログイン機能の実装: password_hashで暗号化したパスワードをpassword_verifyを使って照合する

forestGreen

総合スコア8

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

パスワード

パスワードは主に情報にアクセスする際に扱われます。主に、アクセス可能なユーザーを限定する手段として使われます。

ログイン

ログインは、ユーザーがコンピューターシステムにアクセスするプロセスの事を呼びます。

暗号化

ネットワークを通じてデジタルデータをやり取りする際に、第三者に解読されることのないよう、アルゴリズムを用いてデータを変換すること。

0グッド

2クリップ

投稿2021/02/14 01:42

編集2021/02/14 05:36

前提・実現したいこと

最近独学でプログラミングを始めたPHP初心者です。

現在、サーバーサイド言語としてPHPを使用してログイン機能のついた掲示板を作成しております。
password_hashとpassword_verifyを使うとパスワードを暗号化して扱えると知り、それらを用いてログイン機能を実装しようと試みましたが、うまくいきません。

周りにプログラミング(PHP)のことを相談できる人がおらず、またウェブで調べても解決法を見つけることができなかったため、質問しました。

※開発環境はXAMPP、掲示板利用時のユーザーアカウント管理用テーブル・投稿管理用テーブルはDB(MySQL)で行っております。

発生している問題・エラーメッセージ

エラーメッセージは出ていませんが、ユーザーログイン時にpassword_verifyを用いてユーザーが入力したパスワードと暗号化したパスワードの一致を確認するところがうまくいかず、困っております。
password_verifyを用いてパスワードの一致を確認する際、引数(ハッシュ値)の書き方に問題があるのではなうか?というところまでは突き止めたのですが、ではどのように記述したらよいのか、が分からない状況です。

※なお、password_hashを用いたパスワードの暗号化は出来ており、暗号化されたパスワードはDB内ユーザーアカウント管理のテーブルに問題なく登録されております。

該当のソースコード

password_verifyを使用しているソースコードはこちら↓です。
既存のアカウントに対してログインできるかチェックを行う(Logincheck.php)

PHP

1<?php 2 // session開始 3 session_start(); 4 5 // DBと繋ぐ(別ファイルに記述) 6 include "DBconnect.php"; 7 8 // 入力パスワードとDBパスワードが一致した時にログイン処理を進める 9 10 // ログインページでユーザーが入力した情報を取得 11 $user_name = $_POST["user_name"]; 12 $user_password = $_POST["user_password"]; 13 14 // DB内でユーザー名が一致するレコードを$sqlに代入 15 $sql = "SELECT * FROM stockuser WHERE user_name = '$user_name'"; 16 17 // SQLを発行し、SQL文がDBに通ったかを判別 18 if($result = mysqli_query($conn , $sql)) { 19 20  // ユーザー名が一致したレコードを取り出す 21 $row = mysqli_fetch_assoc($result); 22 23 // implodeを用いて該当レコードが取り出せているかを確認 24 echo implode($row); 25 26  // 該当レコードからパスワードが取り出せているかを確認 27 echo $row["user_password"]; 28 29 // 入力したパスワードと暗号化されたパスワードの一致が確認されれば、ログイン許可 30  // ここが間違っているのではないかと思います。 31 if(password_verify($user_password, $row["user_password"])) { 32 33    // 一致していれば"ログインOK"と画面に表示させる 34 echo "ログインOK"; 35 36 // $flagを使ってログインOK/NGでの動作を定義 37 $flag = true; 38 39 } 40 else { 41 42 // 一致していなければ"ログインNG"と画面に表示させる 43 echo "ログインNG"; 44   // ログインNGのときはfalseとする 45 $flag = false; 46 } 47 48 } 49 else { 50 echo mysqli_error($conn)."<BR>"; 51 } 52 53 // MySQLの切断 54 mysqli_close($conn); 55 56 57 // 以降、画面遷移に関するパート 58 // password_verifyの部分を動作確認する際は以降の部分をコメントアウトしております 59 60 // サーバー情報の記述 61 $host = $_SERVER['HTTP_HOST']; 62 $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\'); 63 64 // $flagがtrueなら、ログイン成功(main.phpにページ遷移) 65 if($flag){ 66 // user_idとuser_nameで連想配列を作り、sessionに載せる 67 $user = array("user_id" => $row["user_id"] , "user_name" => $row["user_name"]); 68 $_SESSION["user"] = $user; 69 $extra = "main.php"; 70 header("Location: http://$host$uri/$extra"); 71 exit(); 72 } 73 // $flagがfalseなら、login2.html(ログインできませんでした のページ)に遷移 74 else{ 75 // sessionを破壊 76 session_destroy(); 77 // session破壊後に遷移先ページへの移動を行う 78 $extra = "login2.php"; 79 header("Location: http://$host$uri/$extra"); 80 exit(); 81 } 82 83?> 84

関連ファイルの情報
DBと繋ぐためのファイル(DBconnect.php)

PHP

1<?php 2 // DBに繋ぐためのファイル 3 // サーバへの接続情報を登録 4 $DBSERVER = "localhost:3307"; 5 $DBUSER = "root"; 6 $DBPASSWORD = ""; 7 $DBNAME = "hellowordsdb"; 8 9 //MySQLへの接続 10 $conn = mysqli_connect($DBSERVER , $DBUSER , $DBPASSWORD , $DBNAME); 11 12 //接続時にエラーが発生していれば、エラーメッセージを表示 13 if(mysqli_connect_error()) { 14 echo mysqli_connect_error(); 15 exit(); 16 } else { 17 // 下のechoは後ほど削除(表示があるとページ遷移ができないのでコメントアウト) 18 // echo "Connect OK"; 19 } 20?>

新規ユーザー登録時、パスワードは暗号化してDBに登録する(registAccount.php)

PHP

1<?php 2 // DBにユーザを新規登録するためのファイル 3 4 // セッション開始 5 session_start(); 6 7 // 新規登録ボタンが押された時の確認項目 8 if(isset($_POST["register"])){ 9 10 // DB接続のためのファイルを読み込み 11 include "DBconnect.php"; 12 13  // ユーザー名、ユーザーアドレスの情報を新規登録ページから取得 14 $user_name = $_POST["user_name"]; 15 $user_address = $_POST["user_address"]; 16 17 // パスワードは暗号化して登録する 18 $hash = password_hash($_SESSION["user_password"], PASSWORD_DEFAULT); 19 20 // insert文の組み立て・sql文の発行 21 $sql = "INSERT INTO stockUser (user_name , user_address , user_password , enable) "; 22 $sql .= " VALUES ('$user_name' , '$user_address' , '$hash' , 1)"; 23 24 // 必要な情報が揃っていれば、ユーザ情報を登録 25 if($result = mysqli_query($conn , $sql) ){ 26 27 // sessionに情報を積む 28 $user = array("user_id" => mysqli_insert_id($conn) , "user_name" => $user_name); 29 $_SESSION["user"] = $user; 30 $host = $_SERVER['HTTP_HOST']; 31 $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\'); 32 $extra = "main.php"; 33 34 // 一覧画面に転送 35 header("Location: http://$host$uri/$extra"); 36 exit(); 37 } 38 // 情報入力が不足していた場合 39 else { 40 // 入力画面に戻す 41 $err_msg = mysqli_error($conn); 42 $user_name = $_POST["user_name"]; 43 $user_address = $_POST["user_address"]; 44 include "newAccount.php"; 45 } 46 } 47 // 戻るボタンが押された時は、ひとつ前の入力画面へ戻す 48 else { 49 $err_msg = ""; 50 $user_name = $_POST["user_name"]; 51 $user_address = $_POST["user_address"]; 52 include "newAccount.php"; 53 54 } 55 ?> 56

ログインフォーム(login.php)

PHP

1<!DOCTYPE html> 2<html lang="ja"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <link rel="stylesheet" type="text/css" href="login.css?v=5"> 7 <link rel="icon" href="images/wordcard.png"> 8 <title>Hello!Words ログイン</title> 9</head> 10<body> 11 <div id="container"> 12 <h2>Hello! Words ログイン画面</h2> 13 <p>ログイン情報を入力してください</p> 14 <!-- action属性で情報の送り先となるページ(Logincheck.php)を指定 --> 15 <form action="Logincheck.php" method="post"> 16 <table> 17 <tr> 18 <th>ユーザー名</th> 19 <td><input type="text" name="user_name"></td> 20 </tr> 21 <tr> 22 <th>パスワード</th> 23 <td><input type="password" name="user_password"></td> 24 </tr> 25 </table> 26 <input class="buttons" type="submit" href="main.php" value="ログイン"> 27 </form> 28 <a href="top.php">トップへ戻る</a> 29 </div> 30</body> 31</html>

ユーザー名またはパスワードが一致せずログインできなかった場合に表示するページ(login2.php)

PHP

1<!DOCTYPE html> 2<html lang="ja"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <link rel="stylesheet" type="text/css" href="login.css?v=4"> 7 <link rel="icon" href="images/wordcard.png"> 8 <title>Hello!Words ログイン</title> 9</head> 10<body> 11 <div id="container"> 12 <h2>Hello! Words ログイン画面</h2> 13 <p>ログイン情報を入力してください</p> 14 <!-- action属性で情報の送り先となるページ(Logincheck.php)を指定 --> 15 <!-- エラー表示 --> 16 <div class="err">ユーザー名またはパスワードが違います</div> 17 <form action="Logincheck.php" method="post"> 18 <table> 19 <tr> 20 <th>ユーザー名</th> 21 <td><input type="text" name="user_name"></td> 22 </tr> 23 <tr> 24 <th>パスワード</th> 25 <td><input type="password" name="user_password"></td> 26 </tr> 27 <tr> 28 <!-- <th><input type="submit" href="top.php" value="トップページに戻る"></th> --> 29 <th><input class="buttons" type="submit" href="main.php" value="ログイン"></th> 30 </tr> 31 </table> 32 </form> 33 <a href="top.php">トップへ戻る</a> 34 </div> 35</body> 36</html>

試したこと

Logincheck.php内に記載をしておりますが、レコードが問題なく取得できているかどうか、echoで画面に出力させて確認しました。

PHP

1 // implodeを用いて該当レコードが取り出せているかを確認 2 echo implode($row); 3 4  // 該当レコードからパスワードが取り出せているかを確認 5 echo $row["user_password"];

画面には以下↓のように取得したレコードの値が出力されます。

ユーザーID ユーザー名 ユーザーアドレス ユーザーパスワード(暗号化したもの)
ユーザーパスワード(暗号化したもの)
ログインNG

...この結果から、抽出したいレコード及びパスワードは取り出せているものの、"ログインNG"との表示が出るため、password_verifyがきちんと機能できていないのではないかと考えております。

補足情報(FW/ツールのバージョンなど)

データベースの情報
DB名: hellowordsdb
DBでユーザー管理を行うテーブルの情報は以下の通りです。
テーブル名: stockuser
カラム名: user_id, user_name, user_address, user_password, enable(有効なアカウントかを入力)

使用ツールについて
OS: Windows10
開発環境: XAMPP Control Panel v3.2.4
コーディングツール: Visual Studio Code

追記:

hoshi-takanori様からご指摘いただいた内容を試してみました(ご指摘の内容はコメント欄をご覧ください)。

  • まず、既存のアカウントでログインを行う際にパスワードを空文字でログインを試みたところ、以下↓の画面表示となりました。

 ユーザーID ユーザー名 ユーザーアドレス ユーザーパスワード(暗号化したもの)
ユーザーパスワード(暗号化したもの)
ログインOK

...上記 "ログインOK"との表示から、ログイン出来ており、その際のパスワードは空文字で登録されていた。ということが判明しました。

  • そこで、registAccount.phpファイル内にてパスワードハッシュ化を行う箇所のスーパーグローバル変数を以下のように変更

($_SESSION → $_POST)

PHP

1 // パスワードは暗号化して登録する 2 $hash = password_hash($_POST["user_password"], PASSWORD_DEFAULT);

上記コードに変更後、テストを行った(新規ユーザー登録を行い、一旦ログアウトした後にログインを試みた)ところ、問題なくログインすることができました。

質問した内容について解決することができ、本当に助かりました。ご回答いただいた皆様、本当にありがとうございました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2021/02/14 01:50

暗号化の処理で、パスワードは $_SESSION["user_password"] ではなく $_POST["user_password"] では。 あと、$sql .= " VALUES ('$user_name' , '$user_address' , '$hash' , 1)"; という書き方は SQL インジェクションの脆弱性があります。ちゃんとプレースホルダーを使いましょう。
phoepsilonix

2021/02/14 02:53

見たところ、password_hashの部分には問題はないかと思いました。 データベースへ格納されているデータと不整合が起きていませんか? データベースに格納されているhashとログイン時に入力したpasswordにpassowrd_hashを使ってみて、一致するか、調べてみてはどうでしょうか? 上の方のコメントにあるとおり、SQLインジェクション対策のプレースホルダーはマストです。 またmysql_ 関数は非推奨になっています。代替方法やプレースホルダーのことも含めて、こちらに詳しい解説がありますので、ご覧になってみてください。 https://liginc.co.jp/344413
m.ts10806

2021/02/14 03:14 編集

PHPマニュアルを確認された上でしょうか。 phoepsilonixさん mysql_は非推奨どころか7以降では削除されたものですが質問者さんはmysqli_を使っているので問題ないです。
forestGreen

2021/02/14 03:28

hoshi-takanori様 コメントありがとうございます。 暗号化の際(password_hash)に$_SESSIONでなく$_POSTを使用してみましたが、同様の結果でした。 2つの変数に関して自分の理解は以下の通りです。 $_SESSION: 複数のページ間で値を引き継ぐことができる $_POST: 値は1対1の画面間で受け渡すのみで、複数のページ間で値を引き継ぐことはできない 新規登録後はログイン状態を保持しなければならないため、$_SESSIONを使うのかなと思っておりました(初学者で理解不足ですので自分でもう少し調べてみます)。 またSQLインジェクションに関してご指摘くださりありがとうございます。セキュリティ面は勉強を始めたばかりで知らないことだらけですので、実装できるようによく調べるようにします。
forestGreen

2021/02/14 03:39

phoepsilonix様 コメントありがとうございます。 DBに入れたデータとの不整合は確認できておりませんでした。アドバイスいただいたように、ログイン時に入力した値をハッシュ化し、DBに格納されているデータと一致するか調べてみます。 また分かりやすい解説されたサイトをご紹介いただきありがとうございます。今回、mysqlでなくmysqliを使用するようにしておりますが、PDOの実装はできておらず(今後身に着けるつもりです)ですので勉強になりました。
forestGreen

2021/02/14 03:48

m.ts10806様 コメントありがとうございます。 password_hashとpassword_verifyの使い方含め、不明点はPHPマニュアルのdocumentationを確認した後に投稿しておりますが、コードを読む経験が足りないことにより理解不十分(間違って理解している)の点は多いと自覚しております。
hoshi-takanori

2021/02/14 04:11

「同様の結果」というのは、$_POST["user_password"] に書き換えてから登録処理を行なったアカウントでのログインを試したのでしょうか? $_SESSION["user_password"] の状態で登録したアカウントは、おそらくパスワードは空文字列として登録されていると思いますので、試しにパスワードを空文字列にしてログインをお試しください。 > $_SESSION: 複数のページ間で値を引き継ぐことができる > $_POST: 値は1対1の画面間で受け渡すのみで、複数のページ間で値を引き継ぐことはできない $_POST はブラウザの form から post で送られてきた値 (基本的には、ユーザーが入力した値) です。画面間で受け渡すというのは誤解を招く言い方だと思います。 一方、$_SESSION は php 側で明示的に $_SESSION に保存した値が、別の画面に移動しても引き継がれるものです。$_SESSION["user_password"] に値を保存しているところは見当たらないので、たぶん何も保存されてないと思います。(平文のパスワードを $_SESSION に保存してはいけないので、保存してないこと自体は正しいです。) で、$_SESSION["user_password"] に値が保存されてない状態で password_hash すると、たぶん空文字列をハッシュした結果が返ると思います。かつ、ソルトがかかってるので、毎回違う値になるはずで、ハッシュ値を見ただけではパスワードが空だったかどうかは分からないはず。
forestGreen

2021/02/14 05:08

hoshi-takanori様 > 「同様の結果」というのは、$_POST["user_password"] に書き換えてから登録処理を行なったアカウ> ントでのログインを試したのでしょうか? はい、そのようにしてログインを試しましたが、ログインできなかったため、「同様の結果」とコメント致しました。 > $_SESSION["user_password"] の状態で登録したアカウントは、おそらくパスワードは空文字列とし> て登録されていると思いますので、試しにパスワードを空文字列にしてログインをお試しください。 $_POST["user_password"]に変更後、$_SESSION["user_password"] の状態で登録したアカウントにて仰るようにパスワード空文字でログインを試みたところ、ログインすることができました。 > で、$_SESSION["user_password"] に値が保存されてない状態で password_hash すると、たぶん空> 文字列をハッシュした結果が返ると思います。かつ、ソルトがかかってるので、毎回違う値になるはず> で、ハッシュ値を見ただけではパスワードが空だったかどうかは分からないはず。 空文字でもハッシュ化されてしまうのですね。知りませんでした。 hoshi-takanori様よりいただいたご指摘を確認するべく、$_POST["user_password"]に変更後、テストを行いました。新規ユーザーを登録し、一度ログアウトした後にログインを試みたところ、パスワードが空文字ではログイン不可で、新規登録時に入力したパスワードを入力することでログインすることができました。 また$_SESSIONと$_POSTの定義について理解不足であった点、分かりやすくご教示いただきありがとうございました。大変勉強になり、お陰様で質問内容を解決することができました。ありがとうございました。
m.ts10806

2021/02/14 05:19

ひとまず、入力(フォーム)部分のコードも提示してください。
forestGreen

2021/02/14 05:44

m.ts10806様 フォーム部分の情報を追加していたら、返信が遅くなってしまいました。申し訳ありません。 またteratailで質問が解決した後はどう操作すればよいか、ご教示いただきありがとうございます。今後も利用させていただくにあたり、マナーを身に着けるように致します。ありがとうございました。
guest

回答1

0

自己解決

ご回答いただいた皆様、本当にありがとうございました。

hoshi-takanori様からご指摘いただいた内容を試したところ、無事解決することが出来ましたので、自己解決にて解決済みとさせていただきます。
(ご指摘いただいた内容はコメント欄に、解決方法は質問文の追記にも記載しております)

  • まず、既存のアカウントでログインを行う際にパスワードを空文字でログインを試みたところ、画面に以下↓の内容が表示されました(この時、画面遷移のコーディングはコメントアウトして実行)。

ユーザーID ユーザー名 ユーザーアドレス ユーザーパスワード(暗号化したもの)
ユーザーパスワード(暗号化したもの)
ログインOK

...上記 "ログインOK"との表示から、ログインが出来ており、その際のパスワードは空文字で登録されていた。ということが判明しました。

  • そこで、registAccount.phpファイル内にてパスワードハッシュ化を行う箇所のスーパーグローバル変数を以下のように変更

($_SESSION → $_POST)

PHP

1 // パスワードは暗号化して登録する 2 $hash = password_hash($_POST["user_password"], PASSWORD_DEFAULT);

上記コードに変更後、画面遷移できるようにコメントアウトを解除してテストを行った(新規ユーザー登録を行い、一旦ログアウトした後にログインを試みた)ところ、問題なくログインすることができました。

投稿2021/02/14 05:53

forestGreen

総合スコア8

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問