---
# System prepended metadata

title: Week11 7 、 明文密碼
tags: [2020十一月第二周, Lidemy心得, 進度筆記, BE101, PHP]

---

# Week11 7 、 明文密碼
###### tags: `BE101` `PHP` `2020十一月第二周` `進度筆記` `Lidemy心得`

***

資料加密:
在資料庫內把 user 的密碼儲存後，存明碼後容易被竊取，因此要避免直接把密碼存在資料庫上，有很多網站要求重設密碼，其實網站本身也不知道 user 密碼。  

***

# Week11 7-1 、 加密與 hash 區別  
###### tags: `BE101` `PHP` `2020十一月第二周` `進度筆記` `Lidemy心得`

***

## 編碼（Encoding）  
- 把一團資料各自表述，使用不同方法來解釋，不用金鑰就能解碼的不安全方式。  

## 加密（Encryption）  
- 一對一的方式。  
- 用金鑰來保護重要資料的安全方法，且加密和解密過程都需要用到金鑰。 
- 如果被知道加密演算法，和金鑰，那被存在資料庫的密碼都會被解開。
```
明文(ex. aaa) → 加密 → 密文(ex. bbb)
密文(ex. bbb) → 解密 → 明文(ex. aaa)
```   

## 雜湊（Hash） 
- 多對一的方式，會有很多種字串對應到有限的結果。  
- 把一堆欄位的資料丟進某種 function (Hash function)進行運算，且得到的結果無法用來反推回原來的資料。  
```
明文(ex. aaa) → Hash → 文字(ex. j1o1h7n)
```
- 如果有兩不同東西產生同樣 Hash 的結果時被稱之為"碰撞"。 
- 知名的演算法發生碰撞機率超級低，幾乎不太可能。 

## 例如，取餘數
```
n%999

1 => hash =>1
2 => hash =>2

但 1001 => hash => 2
2000 => hash => 2
```
知道結果是 2 ， 但是無法知道 hash 前輸入的值是多少，因此是單向的。  

- sha256 > MD5(不安全) 。  
- https://emn178.github.io/online-tools/sha256.html  
- 使用者在註冊的時候，把其密碼經過 hash 後存到資料庫。  
- 其 user 在登入時輸入的密碼一樣也透過 hash 後，與資料庫經 hash 的 user 密碼做比對，如果出來的結果一樣，代表輸入的值一樣，因此可以應用。  

***
參考資料:  
[\[資訊安全\] 密碼存明碼，怎麼不直接去裸奔算了？淺談 Hash , 用雜湊保護密碼](https://medium.com/@brad61517/%E8%B3%87%E8%A8%8A%E5%AE%89%E5%85%A8-%E5%AF%86%E7%A2%BC%E5%AD%98%E6%98%8E%E7%A2%BC-%E6%80%8E%E9%BA%BC%E4%B8%8D%E7%9B%B4%E6%8E%A5%E5%8E%BB%E8%A3%B8%E5%A5%94%E7%AE%97%E4%BA%86-%E6%B7%BA%E8%AB%87-hash-%E7%94%A8%E9%9B%9C%E6%B9%8A%E4%BF%9D%E8%AD%B7%E5%AF%86%E7%A2%BC-d561ad2a7d84)

[一次搞懂密碼學中的三兄弟 — Encode、Encrypt 跟 Hash](https://medium.com/starbugs/what-are-encoding-encrypt-and-hashing-4b03d40e7b0c)

[帳戶密碼的加密與雜湊 \- 翻轉工作室](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwim5camzrHtAhWZKqYKHcAYBhsQFjAJegQIChAC&url=http%3A%2F%2Fwww.tsnien.idv.tw%2FSecurity_WebBook%2Fchap13%2F13-3%2520%25E5%25B8%25B3%25E6%2588%25B6%25E5%25AF%2586%25E7%25A2%25BC%25E5%258A%25A0%25E5%25AF%2586%25E8%2588%2587%25E9%259B%259C%25E6%25B9%258A.html&usg=AOvVaw3kylZj_7pCDeiss_Ph6t2Z)

[加密和雜湊有什麼不一樣？ | Just for noting](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwim5camzrHtAhWZKqYKHcAYBhsQFjAAegQIBRAC&url=https%3A%2F%2Fblog.m157q.tw%2Fposts%2F2017%2F12%2F25%2Fdifferences-between-encryption-and-hashing%2F&usg=AOvVaw1xPGuTJXQKAkSvniNuFwiQ)

[\[淺談\] 編碼(encoding) vs 加解密vs 雜湊(Hash) | 石頭的coding ...](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwim5camzrHtAhWZKqYKHcAYBhsQFjAEegQIBBAC&url=https%3A%2F%2Fdotblogs.com.tw%2Fdaniel%2F2019%2F05%2F06%2F223004&usg=AOvVaw3yPB4lVFHPoqlANRIbRqFw)

***

# Week11 7-2 、 使用 PHP 內建 hash 函式 
###### tags: `BE101` `PHP` `2020十一月第二周` `進度筆記` `Lidemy心得`

***

- 用 PHP 內建函式 `password_hash()` 。 
- 有些舊的版本不支援(5.5.0 以上版本) 。 
- 現在回傳的字串是 60 字元，但以後可能會變長，建議預留 255 的空間。 

***

## 註冊處理頁面  
handle_register.php 檔案:  
```
<?php
  session_start();
  require_once('conn.php');

  if (
    empty($_POST['nickname']) ||
    empty($_POST['username']) ||
    empty($_POST['password']) 
  ) {
    header('Location: register.php?errCode=1');
    die();
  }

  $nickname = $_POST['nickname'];
  $username = $_POST['username'];

  $password = password_hash($_POST['password'], PASSWORD_DEFAULT); 
  // 註冊時，密碼存到資料庫時就會是經過 hash 的結果

  $sql = sprintf(
    "insert into users(nickname, username, password) values('%s', '%s', '%s')",
    $nickname,
    $username,
    $password
  );
  $result = $conn->query($sql);
  if (!$result) {
    $code = $conn->errno;
    if ($code === 1062) {
      header('Location: register.php?errCode=2');
    }
    die($conn->error);
  }

  $_SESSION['username'] = $username;
  header("Location: index.php");
?>
```
- 會把經過 hash 演算的字串結果存到資料庫。  
- 會存經過 hash 演算法的相關資訊、到這個字串。

***

## 註冊登入頁面  
- 使用 `password_verify()` 去驗證登入時的 hash 與存在資料庫的 hash 結果是否相同。 

handle_login.php 檔案:  
```
<?php
  session_start();
  require_once('conn.php');
  require_once('utils.php');

  if (
    empty($_POST['username']) ||
    empty($_POST['password']) 
  ) {
    header('Location: login.php?errCode=1');
    die();
  }

  $username = $_POST['username'];
  $password = $_POST['password'];

  $sql = sprintf(
    "select * from users where username='%s'",
    $username
  ); // 原本是看帳號密碼是否相同，但經 hash 前要先把密碼 select 出來
  $result = $conn->query($sql);
  if (!$result) {
    die($conn->error);
  }

  if ($result->num_rows === 0) {
    header("Location: login.php?errCode=2");
    exit(); // 如果沒有東西就是查無 user ， 先加 exit 可以避免在資料庫查詢前先執行下面比對 hash 的動作
  }

  // 有查到使用者
  $row = $result->fetch_assoc(); //拿出結果
  if (password_verify($password, $row['password'])) {
    // 第一個是登入成功($password)，第二個是資料庫的($row['password'])，比對兩者
    /*
      1. 產生 session id (token)
      2. 把 username 寫入檔案
      3. set-cookie: session-id
    */
    $_SESSION['username'] = $username; // 指定註冊後直接成為登入狀態
    header("Location: index.php");
  } else {
    header("Location: login.php?errCode=2"); // 沒有登入成功的話顯示登入失敗
  }

?>
```
- 註冊時，密碼存到資料庫時就會是經過 hash 的結果。
```
$password = password_hash($_POST['password'], PASSWORD_DEFAULT);
```
- 登入時，先透過 username 把 密碼挖出來。 
```
  $sql = sprintf(
    "select * from users where username='%s'",
    $username
  ); 
```
- 並且要先檢查有沒有 user ， 這個使用者存在與否，沒有的話就錯誤返回。  
```
  if ($result->num_rows === 0) {
    header("Location: login.php?errCode=2");
    exit(); 
  }
```
- 如果有查到使用者。  
```
$row = $result->fetch_assoc(); //拿出結果
  if (password_verify($password, $row['password'])) {
    // 第一個是登入成功($password)，第二個是資料庫的($row['password'])，比對兩者
    /*
      1. 產生 session id (token)
      2. 把 username 寫入檔案
      3. set-cookie: session-id
    */
    $_SESSION['username'] = $username; // 指定註冊後直接成為登入狀態
    header("Location: index.php");
  } else {
    header("Location: login.php?errCode=2"); // 沒有登入成功的話顯示登入失敗
  }

```
- 這可以保護使用者密碼，這很重要，千萬不能在資料庫內存明碼。 


***