飞书通讯录 ↔ LDAP 同步工具的首个提交。 - internal/feishu: 飞书通讯录客户端、快照与重试 - internal/ldaplib: LDAP 读写与批量操作 - internal/mapping: 飞书部门/用户到 LDAP 条目的映射 - internal/syncer: 同步计划生成、执行与调度 - internal/store: SQLite 台账 - internal/server + internal/webui: 管理后台与内嵌前端产物 - internal/password: SSHA 口令处理 - config.example.yml / Dockerfile / Makefile / web 前端源码 真实凭据(config.yml)、运行时数据(data/、backup/)与构建产物(bin/)不入库。
81 lines
2.3 KiB
Go
81 lines
2.3 KiB
Go
// Package password 提供 OpenLDAP userPassword 的哈希编码。
|
|
//
|
|
// 为什么需要它:slapd 对客户端通过普通 Modify 写入的 userPassword **不做**
|
|
// 自动哈希(除非启用 ppolicy 的 hash_cleartext),因此如果直接写明文,
|
|
// 目录里就会以明文形式保存密码。这里在写入前自行编码成 {SSHA},
|
|
// 不依赖服务端配置。
|
|
package password
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"fmt"
|
|
)
|
|
|
|
// EncodeSSHA 把明文密码编码成 OpenLDAP 的 {SSHA} 形式。
|
|
//
|
|
// SSHA = base64( SHA1(密码 + 盐) + 盐 )。附带随机盐可以避免彩虹表,
|
|
// 并让相同密码产生不同哈希。
|
|
func EncodeSSHA(plain string) (string, error) {
|
|
salt := make([]byte, 8)
|
|
if _, err := rand.Read(salt); err != nil {
|
|
return "", fmt.Errorf("生成盐失败: %w", err)
|
|
}
|
|
|
|
h := sha1.New()
|
|
h.Write([]byte(plain))
|
|
h.Write(salt)
|
|
sum := h.Sum(nil)
|
|
|
|
// 哈希后拼接盐,一并 base64
|
|
out := append(sum, salt...)
|
|
return "{SSHA}" + base64.StdEncoding.EncodeToString(out), nil
|
|
}
|
|
|
|
// VerifySSHA 校验明文密码与 {SSHA} 哈希是否匹配。
|
|
//
|
|
// 主要用于测试,也可供将来的密码校验功能复用。
|
|
func VerifySSHA(encoded, plain string) bool {
|
|
const prefix = "{SSHA}"
|
|
if len(encoded) <= len(prefix) || encoded[:len(prefix)] != prefix {
|
|
return false
|
|
}
|
|
|
|
raw, err := base64.StdEncoding.DecodeString(encoded[len(prefix):])
|
|
if err != nil || len(raw) < sha1.Size {
|
|
return false
|
|
}
|
|
|
|
sum := raw[:sha1.Size]
|
|
salt := raw[sha1.Size:]
|
|
|
|
h := sha1.New()
|
|
h.Write([]byte(plain))
|
|
h.Write(salt)
|
|
want := h.Sum(nil)
|
|
|
|
if len(want) != len(sum) {
|
|
return false
|
|
}
|
|
// 恒定时间比较,避免时序侧信道(虽然此处主要用于测试,
|
|
// 保持与生产代码一致的比较方式更不易被误用)。
|
|
var diff byte
|
|
for i := range want {
|
|
diff |= want[i] ^ sum[i]
|
|
}
|
|
return diff == 0
|
|
}
|
|
|
|
// Random 生成一个用户不可知的随机初始密码。
|
|
//
|
|
// 用户不需要知道它:Authelia 的密码重置流程不需要旧密码,
|
|
// 因此新账号首次登录应走“忘记密码”。这比为所有人设同一个初始密码安全得多。
|
|
func Random() (string, error) {
|
|
b := make([]byte, 24)
|
|
if _, err := rand.Read(b); err != nil {
|
|
return "", fmt.Errorf("生成随机密码失败: %w", err)
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(b), nil
|
|
}
|