slappasswd 指令
===
###### tags: `LDAP`
###### tags: `LDAP`, `slappasswd`, `指令`, `slapd`, `加密`, `驗證`
<br>
[TOC]
<br>
## 安裝方式
```
$ slappasswd
-bash: slappasswd: command not found
$ apt update && apt install slapd -y
...
Please enter the password for the admin entry in your LDAP directory.
Administrator password: <-- 輸入 admin 密碼
Please enter the admin password for your LDAP directory again to verify that you have typed it correctly.
Confirm password: <-- 輸入 admin 密碼
...
```
<br>
## 對使用者的密碼`123456`做加密
```bash
# -h hash, password scheme
$ slappasswd -h {SSHA}
New password: 123456
Re-enter new password: 123456
{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3
# 等效用法/精簡用法,預設使用 {SSHA}
$ slappasswd
```
:warning: 每次執行會不一樣,因`{SSHA}` 使用 SHA1,是「**加鹽單向雜湊**」,不是可逆編碼:
```bash
slappasswd -h {SSHA}
New password: 123456
Re-enter new password: 123456
{SSHA}GFHq8IisFTFypJ8ClP8vNOLCeTlYaYdW <-- 不一樣
```
```bash
slappasswd -h {SSHA}
New password: 123456
Re-enter new password: 123456
{SSHA}kEGRdY1eR8OyUh3iSnyi+/wC2AuMpiw1 <-- 不一樣
```
<br>
## 驗證使用者的密碼`123456`,是否正確?
### Python API
```python=
import base64, hashlib
def check_ssha(stored, password: str) -> bool:
raw = base64.b64decode(stored.replace("{SSHA}", ""))
digest, salt = raw[:20], raw[20:]
return hashlib.sha1(password.encode() + salt).digest() == digest
input_passwd = '123456'
b64_stored = "{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3"
print(check_ssha(b64_stored, input_passwd))
```
<br>
### 使用者輸入 `123456` vs 資料庫儲存的雜湊值 `{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3`
```python=
import hashlib, base64
input_passwd = '123456'
b64_stored = "{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3"
stored = base64.b64decode(b64_stored.replace("{SSHA}", ""))
# b'\xf2\xc4\xf3%\x1d\x0b\x89\xc1\x08.\xb7.\xdb\xb3\x04o\xde\xe0\xc0\x1f\x175i7'
# len=24
digest, salt = stored[:20], stored[20:]
# digest = b'\xf2\xc4\xf3%\x1d\x0b\x89\xc1\x08.\xb7.\xdb\xb3\x04o\xde\xe0\xc0\x1f'
# salt = b'\x175i7'
input_digest = hashlib.sha1(input_passwd.encode() + salt).digest()
# b'\xf2\xc4\xf3%\x1d\x0b\x89\xc1\x08.\xb7.\xdb\xb3\x04o\xde\xe0\xc0\x1f'
ok = input_digest == digest
print("match?", ok) # True 代表雜湊對應 123456
```
<br>
---
## 討論
### 解釋 slappasswd 所產生的雜湊值
- ### Question
```
root@fdf3ceb85a20:/# slappasswd -h {SSHA}
New password: 123456
Re-enter new password: 123456
{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3
root@fdf3ceb85a20:/# slappasswd -h {SSHA}
New password: 123456
Re-enter new password: 123456
{SSHA}GFHq8IisFTFypJ8ClP8vNOLCeTlYaYdW
```
輸入 123456
產生的雜湊值都不同,這樣怎麼判斷 db 儲存的雜湊值就是 123456 所產生?
- ### gpt-5 (2025/09/24)
會不一樣是正常的!因為 **SSHA 是「加鹽雜湊」**:
* 格式:`{SSHA}` + base64( **SHA1(密碼 + salt)** 連同 **salt** )
* **salt 每次隨機**,所以同一個明碼(例如 `123456`)每次產生的字串都不同。
* 伺服器驗證時,會「從資料庫的字串裡取出 salt」,再用你輸入的明碼去算一次 SHA1,比對是否相同。**不需要(也不可能)用肉眼判斷字串是不是特定明碼產生的**。
你要「判斷 DB 儲存的雜湊值是否對應 `123456`」有兩條路:
---
### 方法 1:最直接——嘗試綁定登入
只要用 `-w '123456'` 能綁定成功,就代表 DB 內那串 `{SSHA}...` 確實是對應 `123456` 的合法雜湊。
```bash
ldapwhoami -x -H ldap://10.78.26.241:389 \
-D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \
-w '123456'
# 成功會回:dn:uid=tj_tsai,...
```
---
### 方法 2:離線驗證(技術檢查)
把 `{SSHA}...` 後面的 base64 解開,前 20 bytes 是 digest、後面是 salt。用同一個 salt 對 `123456` 再算一次,看 digest 是否相同。
#### 用 Python(最簡潔)
```python
import base64, hashlib
stored = "{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3"
b = base64.b64decode(stored.replace("{SSHA}", ""))
digest, salt = b[:20], b[20:]
ok = hashlib.sha1(b"123456" + salt).digest() == digest
print("match?", ok) # True 代表雜湊對應 123456
```
#### 用純 shell(偏硬核,但無需寫程式)
```bash
VAL="{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3"
B64=${VAL#"{SSHA}"}
RAW_HEX=$(echo -n "$B64" | base64 -d | xxd -p -c 999)
DIGEST_HEX=${RAW_HEX:0:40} # 前20位元組 = 40個hex字元
SALT_HEX=${RAW_HEX:40} # 剩下就是 salt
# 用同一個 salt 對 123456 重新計算 SHA1
NEW_DIGEST_HEX=$(printf '313233343536'"$SALT_HEX" | xxd -r -p | openssl dgst -sha1 -binary | xxd -p -c 999)
echo "match? $([[ "$NEW_DIGEST_HEX" = "$DIGEST_HEX" ]] && echo YES || echo NO)"
```
> 上面 `313233343536` 是字串 `123456` 的十六進位(`echo -n 123456 | xxd -p`)。
---
### 重點回覆你的疑問
* **不是一定要用 `slappasswd`**。你也可以用 `ldappasswd` 直接送明碼,伺服器會自動依 `olcPasswordHash` 轉成雜湊存入(最方便可靠):
```bash
ldappasswd -x -H ldap://10.78.26.241:389 \
-D "cn=admin,dc=www,dc=google,dc=com" -w "12" \
"uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \
-s '123456'
```
* **同一個明碼會得到不同字串是正常的**(因為 salt 隨機)。兩個字串看起來不同,但**都能讓 `123456` 綁定成功**。
* 想「看懂」某一串 `{SSHA}...` 是否對應特定明碼,就用上面的 **方法 1 綁定** 或 **方法 2 離線驗證**。
> 小提醒:`123456` 太弱了,建議換強密碼;若你在意安全性,也可以考慮改用 `{SSHA512}` 或更強的雜湊策略(視你的 slapd 模組與政策支援情況而定)。
<br>
---
### `{SSHA}...` 要如何 decode 回去 `123456`?
- ### gpt-5 (2025/09/24)
結論:**不能把 `{SSHA}...`「解碼」回原始密碼(如 `123456`)**。
`{SSHA}` 是「**加鹽單向雜湊**」,不是可逆編碼;你最多只能把 **base64** 那段解開,拿到「雜湊值+salt」,但**回不去明碼**。
---
### 你能做的只有兩件事
#### A) 驗證某個候選密碼是否吻合
把 `{SSHA}` 後面的 base64 解出來 → 前 20 bytes 是 SHA-1 雜湊、後面是 salt;用**同一個 salt** 對「候選密碼」再算一次,看雜湊是否相同。
**Python 範例(最簡潔)**
```python
import base64, hashlib
def check_ssha(stored, password: str) -> bool:
raw = base64.b64decode(stored.replace("{SSHA}", ""))
digest, salt = raw[:20], raw[20:]
return hashlib.sha1(password.encode() + salt).digest() == digest
print(check_ssha("{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3", "123456"))
```
**純 shell(openssl/xxd)**
```bash
VAL="{SSHA}8sTzJR0LicEILrcu27MEb97gwB8XNWk3"
B64=${VAL#"{SSHA}"}
RAW_HEX=$(echo -n "$B64" | base64 -d | xxd -p -c 999)
DIGEST_HEX=${RAW_HEX:0:40} # 20 bytes = 40 hex
SALT_HEX=${RAW_HEX:40}
# 對 "123456" 用同一個 salt 重算 SHA1
NEW_DIGEST_HEX=$(printf '313233343536'"$SALT_HEX" | xxd -r -p | \
openssl dgst -sha1 -binary | xxd -p -c 999)
[[ "$NEW_DIGEST_HEX" = "$DIGEST_HEX" ]] && echo "match YES" || echo "match NO"
```
或乾脆用實際 bind 測:
```bash
ldapwhoami -x -H ldap://10.78.26.241:389 \
-D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \
-w '123456'
```
#### B) 重設密碼(最實務)
不知道原密碼就**重設**:
```bash
ldappasswd -x -H ldap://10.78.26.241:389 \
-D "cn=admin,dc=www,dc=google,dc=com" -w "12" \
"uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \
-s '新的強密碼'
```
伺服器會依 `olcPasswordHash` 自動存成 `{SSHA}...`(或你設定的其他 scheme)。
---
### 補充
* 看到 `userPassword:: <base64>` 的雙冒號,**僅代表顯示時做了 base64**;解開後是「雜湊+salt」,**不是明碼**。
* 若真的存的是明碼,會是 `{CLEARTEXT}yourpassword`(或就純文字),這種才「看得見」原文;但**不建議**。
* 想要更強的演算法,可在 `cn=config` 設 `olcPasswordHash`(如 `{SSHA512}`),之後用 `ldappasswd` 重設即可生效。
<br>
{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}