---
# System prepended metadata

title: slappasswd 指令
tags: [驗證, 指令, LDAP, slapd, 加密, slappasswd]

---

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 %}
