簡介
Basic Auth用於服務端簡單的登錄認證,通常使用服務器Nginx、Apache本身即可完成。比如我們要限定某個域名或者頁面必須輸入用戶名、密碼才能登錄,但又不想使用后端開發語言,此時Basic Auth就派上用場了。
Basic Auth 使用htpasswd工具進行生成 http 基本認證的密碼文件。
流程
首先說一下Basic Auth使用流程。
創建認證文件
新建一個文件auth_basic_user_file
,例如:
# 創建目錄
sudo mkdir -p /usr/local/nginx/
# 生成文件
sudo touch /work/yphp/nginx/nginx-htpasswd
文件名就是nginx-htpasswd
。
生成密碼
使用htpasswd工具生成密碼文件:
# 安裝htpasswd
sudo apt-get install apache2-utils
# 生成密碼
sudo htpasswd -c -d /work/yphp/nginx/nginx-htpasswd yujc
New password:
Re-type new password:
Adding password for user yujc
# 查看文件內容
cat //work/yphp/nginx/nginx-htpasswd
yujc:sBoB9G5lTLvPk
這里解釋說明一下:
htpasswd 是開源 http 服務器 apache httpd 的一個命令工具,所以本機如果沒有該命令,需要先安裝。
htpasswd 命令最后一個參數是用戶名,也就是需要登錄的用戶名。命令運行后,會要求輸入該用戶登錄時的密碼,這里我輸入了123。最終我們發現會往/work/yphp/nginx/nginx-htpasswd
文件添加了一行內容。
其中冒號前面的就是用戶名,后面是加密的密碼。
如果沒有htpasswd工具怎么辦呢?也可以借助在線的工具生成:http://tool.oschina.net/htpasswd 。加密方式選擇crypt。點擊生成后,把生成結果追加到nginx-htpasswd文件里即可:
cat /work/yphp/nginx/nginx-htpasswd
yujc:sBoB9G5lTLvPk
yujc2:RF8ulInobr21M
大家會發現,相同的密碼,每次生成的結果都不一樣,沒關系,只要密碼沒變,最終都能登錄的。原因在后面再說明。
Nginx配置Basic Auth
server {
listen 80;
server_name test.com;
auth_basic "登錄認證";
auth_basic_user_file /work/yphp/nginx/nginx-htpasswd;
root /mnt/html/www;
index index.html index.php;
}
重啟Nginx服務后,訪問test.com 就會要求輸入用戶名、密碼。
備注:一定要注意auth_basic_user_file路徑,如果文件不存在,會不厭其煩的出現403。
如果只想某一個頁面支持Basic Auth,可以將auth_basic配置到location里:
location /test {
auth_basic "登錄認證";
auth_basic_user_file /work/yphp/nginx/nginx-htpasswd;
}
htpasswd加密方式
-
MD5:使用MD5加密密碼。在Windows, Netware 和TPF上,這是默認的加密方式。
-
crypt:使用crypt()加密密碼。在除了Windows, Netware和TPF的平台上,這是默認的。 雖然它在所有平台上可以為htpasswd所支持, 但是在Windows, Netware和TPF上不能為httpd服務器所支持。
-
SHA:使用SHA加密密碼。 它是為了方便轉入或移植到使用LDAP Directory Interchange Format (ldif)的Netscape而設計的。
-
plain:不加密,使用純文本的密碼。雖然在所有平台上 htpasswd 都可以建立這樣的密碼, 但是httpd后台只在Windows, Netware和TPF上支持純文本的密碼。
通常我們使用crypt加密方式。如果你使用PHP語言,內置的crypt()
函數即可實現加密。
crypt
crypt() 返回一個基於標准 UNIX DES 算法或系統上其他可用的替代算法的散列字符串。
這里以PHP的crypt為例子說明。該函數原型:
string crypt ( string $str [, string $salt ] )
salt 參數是可選的。然而,如果沒有salt的話,crypt()創建出來的會是弱密碼。 php 5.6及之后的版本會在沒有它的情況下拋出一個 E_NOTICE 級別的錯誤。為了更好的安全性,請確保指定一個足夠強度的鹽值。
我們使用該函數生成密碼的hash值,使用不同的salt值:
php > echo crypt("123", "123456");
12IbR.gJ8wcpc
php > echo crypt("123", "abcde");
abLEFxdWWYR3c
然后復制到/work/yphp/nginx/nginx-htpasswd
:
#yujc:sBoB9G5lTLvPk
yujc2:12IbR.gJ8wcpc
yujc3:abLEFxdWWYR3c
輸入123均能登錄成功。
注意:測試新用戶需要將已登錄用戶注釋掉,無需重啟nginx。
為什么驗證的時候我們並沒有告訴nginx的salt是多少,但是還能驗證?為什么同一密碼不同salt產生的hash都能驗證?
我們看下面的例子:
$ php -a
Interactive mode enabled
php > echo crypt("123", "12IbR.gJ8wcpc");
12IbR.gJ8wcpc
php > echo crypt("123", "abLEFxdWWYR3c");
abLEFxdWWYR3c
php > echo crypt("123", "12test");
12IbR.gJ8wcpc
大家應該發現了什么。我們把加密后的hash當做salt再次使用crypt函數,生成的hash竟然與傳入的salt相同。然后我們把salt前2位保持不變,后面的改成其他的,再使用crypt函數,生成的hash沒有變化。這說明crypt函數只與salt的前幾位有關系:只要前幾位不變,生成的hash是一樣的。
我們既沒有指定使用的算法,也沒有指定鹽值,crypt是怎么知道使用什么算法和鹽值的呢?其實crypt是根據$salt
參數來判斷使用哪種哈希算法。也就是說,$salt
本身就包含了算法的類型以及哈希時實際用到鹽值。
實際使用的算法判斷有下面這幾種情況:
- 標准DES(Standard DES)
在沒有匹配到其他算法的情況下,則使用標准DES算法,此時取前兩個字符為鹽值(不足兩個字符則返回*0)。鹽值的字符必須是./0-9A-Za-z里的字符。
php > echo crypt("123", "t123");
t1ZzgDe4z3qWE
php > echo crypt("123", "123");
12IbR.gJ8wcpc
php > echo crypt("123", "t");
*0
- 擴展DES(Extended DES)
以下划線_
開頭,后面緊接着4字節的迭代次數和4字節的鹽值。也就是取前9位,后面是什么值無所謂。
php > echo crypt("123", "_12345678");
_12345678VaI36zUn7Jk
php > echo crypt("123", "_12345678t");
_12345678VaI36zUn7Jk
php > echo crypt("123", "_testtest");
_testtest4v7fH1Er0Ng
php > echo crypt("123", "_testtest1");
_testtest4v7fH1Er0Ng
- MD5
以$1$
開頭,然后是12個字符以內的鹽值。只要前12位相同,生成的hash相同。
php > echo crypt("123", '$1$');
$1$$GmbL3iXOMZR57QuGDLv.L1
php > echo crypt("123", '$1$2');
$1$2$WOzAAwhejT62wplMg6rEE1
php > echo crypt("123", '$1$23');
$1$23$0ZjnChzzaj90xZQJQKHFS1
php > echo crypt("123", '$1$123456789');
$1$12345678$tRy4cXc3kmcfRZVj4iFXr/
php > echo crypt("123", '$1$1234567890');
$1$12345678$tRy4cXc3kmcfRZVj4iFXr/
注意:PHP里雙引號里面的字符串如果包含
$
會被認為是變量。
- lowfish
以$2a$
、$2x$
或者$2y$
開頭,然后是用於cost參數的兩位數字,緊接着一個$字符,最后是22位./0-9A-Za-z范圍里的字符。
php > echo crypt("123", '$2a$07$usesomesillystringforsalt$');
$2a$07$usesomesillystringforeN7/2NBfGxbAuv02IPrTFBImFJd5PJ1m
php > echo crypt("123", '$2a$07$usesomesillystringforsalt$1');
$2a$07$usesomesillystringforeN7/2NBfGxbAuv02IPrTFBImFJd5PJ1m
使用這個算法的時候,
$str
最長支持72個字符,超過會被截掉。
- SHA256
以$5$
開頭,然后是16個字符的鹽值,鹽值之前還可以使用rounds=$的格式表明哈希的循環次數(N)。
php > echo crypt("123", '$5$rounds=5000$usesomesillystringforsalt$');
$5$rounds=5000$usesomesillystri$BYJncGl82VuZ6T61c4wSpXT.xoDSuz9aF4JyE9F08U4
rounds的默認值為5000,范圍是1000到999,999,999,如果N不在這個范圍里,會被截取到最接近的范圍里。
- SHA512
以$6$
開頭,然后是16個字符的鹽值,鹽值之前還可以使用rounds=$的格式表明哈希的循環次數(N)。
php > echo crypt("123", '$6$rounds=5000$usesomesillystringforsalt$');
$6$rounds=5000$usesomesillystri$YPNvueKNHmPrzbloaqIomo1gPrVo8aLnqwrKlhlfThu2wzo73efrh/FCR4CAUf/GFe7gF6vuLWTMyFNb7jfnT1
rounds的默認值為5000,范圍是1000到999,999,999,如果N不在這個范圍里,會被截取到最接近的范圍里。
PHP里我們使用crypt函數,salt直接傳第一個參數的base64_encode即可:
$hashed_password = crypt ( 'mypassword', base64_encode('mypassword') );
參考
1、Nginx配置Basic Auth登錄認證 - 簡書
https://www.jianshu.com/p/b4a78af4e266
2、PHP筆記 —— crypt方法 - 個人文章 - SegmentFault 思否
https://segmentfault.com/a/1190000009219416
3、PHP: crypt - Manual
http://php.net/manual/zh/function.crypt.php