最近折腾了一下OpenLDAP,密码用的哈希算法是SSHA,SSHA 其实就是加盐的SHA1:salted SHA1
,加盐增加了利用彩虹表碰撞的难度。
摘一段官网FAQ的perl代码:
#! /usr/bin/perl
#
# This small script generates an Seeded SHA1 hash of 'secret'
# (using the seed "salt") for use as a userPassword or rootpw value.
#
use Digest::SHA1;
use MIME::Base64;
$ctx = Digest::SHA1->new;
$ctx->add('secret');
$ctx->add('salt');
$hashedPasswd = '{SSHA}' . encode_base64($ctx->digest . 'salt' ,'');
print 'userPassword: ' . $hashedPasswd . "\n";
过程很简单:
'{SSHA}' + base64_encode( SHA1(password + salt) + salt )
也可以直接使用OpenLDAP 提供的slappasswd
生成:
>slappasswd -h {SSHA} -s password
'{SSHA}0c0blFTXXNuAMHECS4uxrj3ZieMoWImr'
需要注意OpenLDAP SSHA 的salt 长度默认为4个字节。
Python 可以直接用PassLib 这个库,class passlib.hash.ldap_salted_sha1,简单快捷,也有对应的 verify 函数。
🌰 :
from passlib.hash import ldap_salted_sha1 as ssha
def encrypt_password(self, password):
return ssha.encrypt(password, salt_size=4)
def verify_password(self, name, password):
password_hash = self.get_user(name).password
return ssha.verify(password, password_hash)
在给walle 添加LDAP 支持的时候,发现无论如何都是用户名和密码不一致,耐心看了下代码,发现作者写错了,似乎是直接抄的phpldapadmin 的代码🌚🌝:
public function validatePassword($password) {
$encryptionType = strstr(substr($this->_password, 1), '}', true);
return self::generate_password($password, $encryptionType) == $this->_password;
}
generate_password
里面用随机的salt 生成了一个hash,来校验,怎么可能会一样嘛。自己改了本地的代码,提了一个issue。
正确的做法应该是取出userPassword 的salt,和用户输入的密码生成hash,再做对比,参考case ssha。
case 'ssha':
# Check php mhash support before using it
if (function_exists('mhash')) {
$hash = base64_decode($cryptedpassword);
# OpenLDAP uses a 4 byte salt, SunDS uses an 8 byte salt - both from char 20.
$salt = substr($hash,20);
$new_hash = base64_encode(mhash(MHASH_SHA1,$plainpassword.$salt).$salt);
if (strcmp($cryptedpassword,$new_hash) == 0)
return true;
else
return false;
} else {
error(_('Your PHP install does not have the mhash() function. Cannot do SHA hashes.'),'error','index.php');
}
break;
新的一周又开始咯😆 。