得物技術淺談MySQL 8.0:新的身份驗證外掛(caching_sha2_password)

語言: CN / TW / HK

從 MySQL 8.0.4 開始,預設身份驗證外掛從 mysql_native_password 更改為 caching_sha2_password。相應地,現在的 libmysqlclient 將使用 caching_sha2_password 作為預設的驗證機制。

為什麼這樣做呢?

MySQL 5.6/5.7 的預設密碼外掛一直以來都是 mysql_native_password。其優點是它支援 challenge-response 機制,這是非常快的驗證機制,無需在網路中傳送實際密碼,並且不需要加密的連線。然而,mysql_native_password 依賴於 SHA1 演算法,但 NIST(美國國家標準與技術研究院)已建議停止使用 SHA1 演算法,因為 SHA1 和其他雜湊演算法(例如 MD5)已被證明非常容易破解。

此外,由於 mysql_native_password 在 mysql.user 表中 authentication_string 欄位儲存的是兩次雜湊 SHA1(SHA1(password)) 計算的值 ,也就是說如果兩個使用者帳戶使用相同的密碼,那麼經過 mysql_native_password 轉換後在 mysql.user 表得到的雜湊值相同。儘管有 hash 值也無法得到實際密碼資訊,但它仍然告訴這兩個使用者使用了相同的密碼。為了避免這種情況,應該給密碼加鹽(salt),salt 基本上是被用作輸入,用於轉換使用者密碼的加密雜湊函式。由於 salt 是隨機的,即使兩個使用者使用相同的密碼,轉換後的最終結果將發生較大的變化。

從 MySQL 5.6 開始支援 sha256_password 認證外掛。它使用一個加鹽密碼(salted password)進行多輪 SHA256 雜湊(數千輪雜湊,暴力破解更難),以確保雜湊值轉換更安全。然而,它需要要麼在安全連線或密碼使用 RSA 祕鑰對加密。所以,雖然密碼的安全性更強,但安全連線和多輪 hash 轉換需要在認證過程中的時間更長。

為了克服這些限制,從 MySQL 8.0.3 開始,引入了一個新的身份驗證外掛 caching_sha2_password。從 MySQL 8.0.4 開始,此外掛成為 MySQL 伺服器的新預設身份驗證外掛。caching_sha2_password 嘗試一個兩全其美的結合,既解決安全性問題又解決效能問題。

首先,是 caching_sha2_password 對使用者密碼的處理,其實主要是 sha256_password 的機制:

  • 使用 SHA2 演算法來轉換密碼。具體來說,它使用 SHA256 演算法。
  • 儲存在 authentication_string 列中的雜湊值為加鹽後的值,由於鹽是一個 20-byte 的隨機數,即使兩個使用者使用相同的密碼,轉換後的最終結果也將完全不同。
  • 為了使使用暴力破解機制更難以猜測密碼,在將最終轉換儲存在 mysql.user 表中之前,對密碼和鹽進行了 5000 輪 SHA2 雜湊。

為了實現加鹽機制,列 authentication_string 需儲存儲存鹽值,因此 authentication_string 值的長度變為了 70 個位元組:

mysql> select user, host, authentication_string, length(authentication_string), plugin from mysql.user limit 1;
+------+------+------------------------------------------------------------------------+-------------------------------+-----------------------+
| user | host | authentication_string                                                  | length(authentication_string) | plugin                |
+------+------+------------------------------------------------------------------------+-------------------------------+-----------------------+
| root | %    | $A$005$1%h5f1OdZ0'46}M[uz5Di5wW2WWg8eeLWynsg2h3xnzHwQLmm39bEqLBxB0   |                            70 | caching_sha2_password |
+------+------+------------------------------------------------------------------------+-------------------------------+-----------------------+
1 row in set (0.00 sec)

新 caching_sha2_password 認證機制下,authentication_string 中的位元組,例如上面的字串$A$005$1%h5f1OdZ0'46}M[uz5Di5wW2WWg8eeLWynsg2h3xnzHwQLmm39bEqLBxB0,其中分別儲存如下內容:

內容 位元組數 說明
雜湊演算法 2位元組 目前僅為 $A,表示 SHA256 演算法
雜湊輪轉次數 4位元組 目前僅為 $005,表示 5*1000=5000 次
鹽(salt) 21位元組 用於解決相同密碼相同雜湊值問題
雜湊值 43位元組

從 MySQL 8.0.24 開始,提供了 caching_sha2_password_digest_rounds 系統變數,預設值和最小值是 5000,最大值 4095000;用於 caching_sha2_password 認證外掛密碼儲存的雜湊輪轉次數。

其次,caching_sha2_password 是在伺服器端通過快取解決效能問題。caching_sha2_password 外掛使用記憶體快取來為曾經連線過的客戶端進行快速驗證。記憶體快取條目由username/SHA256(SHA256(user_password))對組成。快取的工作原理是這樣的:

  1. 當客戶端連線,caching_sha2_password 檢查 username/SHA256(SHA256(user_password)) 是否匹配了快取條目。如果匹配,驗證成功。
  2. 如果沒有匹配的快取條目,外掛會繼續與客戶端交換資料包,嘗試使用 mysql.user 系統表的憑證驗證客戶端。如果成功,caching_sha2_password 增加對客戶端的雜湊條目。否則,認證失敗,連線被拒絕。

這樣,當客戶端第一次連線,使用 mysql.user 系統表的憑據進行認證。當客戶端連線之後,使用快取進行快速認證。

對於大多數的連線嘗試,當記憶體快取中存在於的密碼雜湊的副本時,它採用了基於 SHA256 的 challenge-response 機制認證客戶端(mysql_native_password 是基於 SHA1 的 challenge-response 機制)。這樣會更快,並且允許通過未加密的通道進行安全認證。

下面總結基於 challenge-response 的認證模式(也稱之為 Fast authentication 模式):

  1. 客戶端連線服務端
  2. 服務端給客戶端傳送 Nonce(20 位元組長的隨機資料)
  3. 客戶端使用 XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce)) 生成 Scramble 傳送給服務端
  4. 服務端檢查 username/SHA256(SHA256(user_password)) 是否在記憶體快取條目中存在,存在則證明合法;傳送 fast_auth_success 包到客戶端
  5. 服務端傳送 OK 包到客戶端
  6. 進入命令階段

Note

在資訊保安中,Nonce 是一個在加密通訊只能使用一次的數字。在認證協議中,它往往是一個隨機或偽隨機數(salt),以避免暴力攻擊。

由於 caching_sha2_password 外掛在使用快取的情況下可以快速認證,但在以下情況下是無效的,對於某些或所有使用者:

  • 當用戶的密碼被更改時,使用者快取的密碼雜湊值都被從記憶體中刪除。密碼可以通過 ALTER USER/SET PASSWORD/GRANT 改變。
  • 當用戶被刪除時,或證書、或認證外掛改變;使用者快取的密碼雜湊值都被從記憶體中刪除。
  • 當用戶使用 RENAME USER 重新命名時,使用者快取的密碼雜湊值都被從記憶體中刪除。
  • 當執行 FLUSH PRIVILEGES 時,所有快取的密碼雜湊值都被從記憶體中刪除,影響所有使用者。\
    伺服器關閉時會清空快取。

在快取失效的情況下會影響後續的客戶端連線驗證要求。caching_sha2_password 需要使用者第一客戶端連線必須使用安全連線(TCP 連線使用 TLS、Unix 套接字檔案、或共享記憶體)或使用 RSA 加密密碼進行交換。

考慮到使用者的密碼變化和 FLUSH PRIVILEGES 是不經常執行的操作,所以在大多數情況下,基於 challenge-response 認證就足夠了。下面總結了基於完整認證模式(perform_full_authentication)的機制(也稱之為 Complete authentication 模式)。

  1. 客戶端連線服務端
  2. 服務端給客戶端傳送 Nonce(20 位元組長的隨機資料)
  3. 客戶端使用 XOR(SHA256(password), SHA256(SHA256(SHA256(password)), nonce)) 生成 Scramble 傳送給服務端
  4. 服務端檢查 username/SHA256(SHA256(user_password)) 是否在記憶體快取條目中存在,不存在則傳送 perform_full_authentication 包到客戶端繼續認證
  5. 客戶端收到 perform_full_authentication 包,可以進行如下處理
  6. 如果連線已經建立基於 SSL 的安全通道,則可以直接傳送明文密碼到服務端\
    向服務端發起獲取公鑰的請求(或者指定服務端公鑰檔案),使用公鑰+Nonce加密密碼,傳送加密後的密碼到服務端\
    伺服器通過 SHA256 演算法計算得到雜湊值,判斷是否使用者認證通過,通過則傳送 OK 包到客戶端\
    進入命令階段

總結來說 caching_sha2_password 外掛工作機制分為兩個階段,Complete authentication 和 Fast authentication。各自認證機制上面已經闡述了。更加詳細的過程,以及服務端和客戶端狀態的轉換,官方在原始碼文件裡面提供有一張圖和說明可以參考,地址在文末。

改變了什麼呢?

在 MySQL 8.0.4 之後建立的所有新使用者將預設使用 caching_sha2_password 作為他們的身份驗證外掛。

mysql> CREATE USER 'sha2user'@'localhost' IDENTIFIED BY '42';
Query OK, 0 rows affected (0.02 sec)

mysql> SHOW CREATE USER 'arthurdent'@'localhost'\G
CREATE USER for [email protected]: CREATE USER 'sha2user'@'localhost' IDENTIFIED WITH 'caching_sha2_password' AS '$Afnka//BGe\d3h\n<:MTEFNZ3U40FRyPrdT5V14x526MHPENmY5Tn0RbjwA16' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK PASSWORD HISTORY DEFAULT PASSWORD REUSE INTERVAL DEFAULT
1 row in set (0.01 sec)

libmysqlclient 使用 caching_sha2_password 作為預設選擇連線到 MySQL 伺服器。請注意,這只是在預設值的變化,libmysqlclient 能夠支援所有現有的身份驗證外掛。 MySQL 的 server-client 協議會負責切換每個使用者的帳戶所需要的認證外掛。

如果你不想使用預設的 caching_sha2_password 外掛,也可以使用一些其他的外掛建立帳戶,你必須明確指定外掛。例如,使用 mysql_native_password 外掛,使用此語句:

CREATE USER 'nativeuser'@'localhost'
IDENTIFIED WITH mysql_native_password BY 'password';

caching_sha2_password 支援安全連線傳輸,如果你按照下面給出的 RSA 配置過程,它同樣也支援在未加密的連線上使用 RSA 加密密碼進行交換。RSA 支援以下特性:

  • 在伺服器端,兩個系統變數命名 RSA 私鑰和公鑰對的檔案:caching_sha2_password_private_key_path 和 caching_sha2_password_public_key_path。如果想改變其預設值,則必須在伺服器啟動時設定變數。
  • 伺服器使用 auto_generate_certs、sha256_password_auto_generate_rsa_keys 和 caching_sha2_password_auto_generate_rsa_keys 系統變數,以確定是否自動生成 RSA 金鑰對檔案。這些變數預設情況下啟用。他們可以在伺服器啟動時啟用和檢查,但不是在執行時設定。詳情參見“Creating SSL and RSA Certificates and Keys”
  • 狀態變數 Caching_sha2_password_rsa_public_key 顯示由 caching_sha2_password 認證外掛使用的 RSA 公鑰值。
  • 客戶端持有 RSA 公鑰時可以在連線過程中執行與伺服器 RSA 金鑰對進行密碼交換,如後所述。
  • 對於使用 caching_sha2_password 和 RSA 金鑰進行身份驗證的帳戶的連線,伺服器預設是不會發送 RSA 公鑰給客戶端。客戶端可以使用所需的公鑰的副本,或從服務端發起請求公鑰。需要說明的是,本地使用受信任的公鑰的副本,使得客戶端能夠避免在 client/server 協議的往返,比從伺服器請求的公鑰更安全。在另一方面,從伺服器請求公鑰更方便(它不需要在客戶端管理檔案),在安全的網路環境是可以接受的。

對於使用 caching_sha2_password 外掛的客戶端,連線到伺服器時,密碼不會暴露為明文。密碼傳輸是如何進行的取決於是否使用安全連線或 RSA 對密碼加密:

  • 如果連線是安全的,RSA 金鑰對是不必要的。這適用於使用 TLS 加密的 TCP 連線,以及 Unix 套接字檔案和共享記憶體連線。密碼以明文格式傳送,但不能被竊聽,因為連線是安全的。
  • 如果連線不是安全的,使用了 RSA 金鑰對。這適用於未使用 TLS 加密的 TCP 連線和 named-pipe 連線。RSA 僅用於客戶端和伺服器之間的密碼交換,防止密碼被擷取。當伺服器接收到使用公鑰加密的密碼後,它使用私鑰解密。一個隨機字串用在加密中,防止重放攻擊(repeat attacks)。

要讓客戶端在連線過程中能夠使用 RSA 金鑰對進行密碼交換,請使用以下步驟(MySQL 8.0.3 以上版本預設自動完成):

  1. 建立 RSA 私鑰和公鑰對檔案
  2. 如果私鑰和公鑰檔案都位於資料目錄中,名為 private_key.pem 和 public_key.pem(是 caching_sha2_password_private_key_path 和 caching_sha2_password_public_key_path 系統變數的預設值),伺服器在啟動時自動使用它們。否則需要在配置檔案中指定私鑰和公鑰檔案的位置。
  3. 重新啟動伺服器後,檢查 Caching_sha2_password_rsa_public_key 狀態變數的值。該值將與這裡所示的不同,但應非空:
mysql> SHOW STATUS LIKE 'Caching_sha2_password_rsa_public_key'\G
Variable_name: Caching_sha2_password_rsa_public_key
Value: -----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDO9nRUDd+KvSZgY7cNBZMNpwX6
MvE1PbJFXO7u18nJ9lwc99Du/E7lw6CVXw7VKrXPeHbVQUzGyUNkf45Nz/ckaaJa
aLgJOBCIDmNVnyU54OT/1lcs2xiyfaDMe8fCJ64ZwTnKbY2gkt1IMjUAB5Ogd5kJ
g8aV7EtKwyhHb0c30QIDAQAB
-----END PUBLIC KEY-----

如果該值為空,檢查錯誤日誌中的診斷資訊。

伺服器已經配置了 RSA 金鑰檔案之後,使用者在使用 caching_sha2_password 外掛進行身份驗證,這些金鑰檔案連線到伺服器的選項。如前面提到的,這時候使用者可以使用安全連線(在這種情況下不使用 RSA)或者在未加密的連線下使用 RSA 執行密碼交換。假設使用未加密的連線時,例如:

shell> mysql --ssl-mode=DISABLED -u sha2user -p
Enter password: password

嘗試通過 sha2user 使用者連線,伺服器確認 caching_sha2_password 認證外掛對 sha2user 是合適的並呼叫它。外掛查詢到該連線未被加密,因此需要使用 RSA 加密被髮送的密碼。但是,服務端不會發送公鑰給客戶端,由於客戶端沒有提供公鑰,所以它不能加密密碼,連線失敗:

ERROR 2061 (HY000): Authentication plugin 'caching_sha2_password'
reported error: Authentication requires secure connection.

為了從伺服器請求的 RSA 公共金鑰,指定--get-server-public-key選項:

shell> mysql --ssl-mode=DISABLED -u sha2user -p --get-server-public-key
Enter password: password

在這種情況下,伺服器傳送的 RSA 公鑰給客戶端,客戶端使用它來加密密碼,並將結果返回到伺服器。該外掛使用伺服器端的 RSA 私鑰來解密密碼,並根據密碼是否正確來決定接受或拒絕連線。

另外,如果客戶端具有伺服器所需的 RSA 公鑰檔案,可以使用--server-public-key-path選項指定檔案:

shell> mysql --ssl-mode=DISABLED -u sha2user -p --server-public-key-path=file_name
Enter password: password

在這種情況下,客戶端使用公鑰來加密密碼並將結果返回到伺服器。該外掛使用伺服器端的 RSA 私鑰來解密密碼,並根據密碼是否正確來決定接受或拒絕連線。

由--server-public-key-path選項指定的檔案中的公鑰值應該與caching_sha2_password_public_key_path系統變數命名的伺服器端檔案的鍵值相同。如果金鑰檔案包含一個有效的公鑰值,但該值不正確,會出現拒絕訪問錯誤。但如果金鑰檔案不包含一個有效的公鑰值,客戶端程式不能使用它(這是因為客戶端做過公鑰正確性校驗)。

客戶端使用者可以得到 RSA 公鑰的兩種方式:

  • 資料庫管理員可以提供公鑰檔案的副本。
  • 可以連線到伺服器的客戶端使用者,可以使用SHOW STATUS LIKE "Caching_sha2_password_rsa_public_key"語句返回公鑰值,並儲存在一個檔案中。

對複製的影響?

複製本身是支援加密的連線。在 MySQL 8.0.4,複製也進行了 RSA 金鑰對的支援。

  • CHANGE MASTER 現在支援了兩個引數來啟用基於 caching_sha2_password RSA 金鑰來交換密碼

    • MASTER_PUBLIC_KEY_PATH ="key_file_path",指定 RSA 公鑰路徑
    • GET_MASTER_PUBLIC_KEY = {0 | 1},從服務端獲取 RSA 公鑰
  • Group Replication 現在支援了兩個引數來啟用基於 caching_sha2_password RSA 金鑰來交換密碼

    • ––group-replication-recovery-public-key-path,指定 RSA 公鑰路徑
    • ––group-replication-recovery-get-public-key,從服務端獲取 RSA 公鑰

Note

如果複製通道是安全的,那麼自然也就不需要使用 RSA 公鑰來交換密碼。

<參考>

https://mp.weixin.qq.com/s/He...

https://mp.weixin.qq.com/s/jf...

https://dev.mysql.com/doc/ref...

https://dev.mysql.com/doc/dev...

https://mysqlserverteam.com/m...

文/東青

關注得物技術,做最潮技術人!