能否用Levenshtein距离防止密码滥用及旧密码复用?
密码安全校验疑问:能否用编辑距离检测旧密码变体?
背景
我正在基于Flask认证工具开发应用以学习密码安全,将所有想到的密码校验方法整合进cleanpassword()函数,后续会评估各方法的性价比。
我下载了一份英文常见弱密码列表,同时会归档用户使用过的密码并记录时间戳,以便后续考虑是否允许过期复用。归档的旧密码以哈希形式存储,避免泄露用户密码规律;而弱密码列表则以明文存储,以便使用Levenshtein距离检测用户密码是否为弱密码的轻微变体(如football、f00tball、futball!会被同等处理)。
相关代码片段
# # 是否属于10k个常见弱密码? # if BadPasswords.query.filter(BadPasswords.baddie == password).first(): # flash('Password '+ password +' is on the list of really bad passwords.','error') # return False # 是否接近10k个常见弱密码? badwords = BadPasswords.query.all() for b in badwords: if lev_distance(b.baddie, password) < levenshtein_limit: flash('Password '+ password +' is on or near the list of really bad passwords.','error') return False # 是否属于旧密码列表? if bcrypt.check_password_hashpassword in OldPasswords.query.all(): flash('Password '+ password +' is on the list of previous passwords.','error') return False
问题
现在我的问题是:能否使用类似的编辑距离测试来防止用户复用原本符合要求的旧密码?我认为不可行,但难以证明,特此请教。
完整的cleanpassword()函数及相关代码
# 安全密码检查 from password_strength import PasswordPolicy from Levenshtein import distance as lev_distance from app import bcrypt, db from models import BadPasswords, OldPasswords from extensions import environ forgive = environ.get('FORGIVE_BAD_PASSWORDS') #forgive = True levenshtein_limit = environ['LEVENSHTEIN_LIMIT'] # class BadPasswords(db.Model): # __tablename__ = 'BadPasswords' # id = db.Column(db.Integer, primary_key=True) # baddie = db.Column(db.String(25)) # class OldPasswords(db.Model): # __tablename__='OldPasswords' # id = db.Column(db.Integer, primary_key=True) # oldie = db.Column(db.String(255)) # created = db.Column(db.DateTime) policy = PasswordPolicy.from_names( length = environ['PASSWORD_LENGTH'], uppercase = environ['PASSWORD_REQUIRE_UPPERCASE'], nonletters = environ['PASSWORD_REQUIRE_NON_LETTERS'], strength =(environ['PASSWORD_REQUIRE_STRENGTH'], environ['PASSWORD_REQUIRE_ENTROPY_BITS']) ) def cleanpassword(password,verify): # 调试时跳过弱密码检查 if forgive: return True # 密码是否匹配? if not password == verify: flash ("Passwords don't match") return False # 是否属于10k个常见弱密码? # if BadPasswords.query.filter(BadPasswords.baddie == password).first(): # flash('Password '+ password +' is on the list of really bad passwords.','error') # return False # 是否接近10k个常见弱密码? badwords = BadPasswords.query.all() for b in badwords: if lev_distance(b.baddie, password) < levenshtein_limit: flash('Password '+ password +' is on or near the list of really bad passwords.','error') return False # 是否属于旧密码列表? if bcrypt.check_password_hashpassword in OldPasswords.query.all(): flash('Password '+ password +' is on the list of previous passwords.','error') return False # 密码质量测试 test = policy.test(password) if test: for t in test: fault = t.name() if fault == 'length': flash('Password needs at least '+ str(t.length) +' letters.','error') if fault == 'uppercase': flash('Password needs at least '+ str(t.count) +' upper-case letters.','error') if fault == 'nonletters': flash('Password needs at least '+ str(t.count) +' non-letter characters.','error') if fault == 'strength': flash('Password needs more entropy.') return False # 所有测试通过 return True
解答
确实不可行,核心原因有三点:
- 哈希的单向性限制:旧密码是以哈希形式存储的,哈希是不可逆的单向函数,你无法从哈希值反推出用户的旧明文密码。而编辑距离计算必须基于明文的字符对比,没有旧明文就根本没法计算新密码和旧密码的相似度。
- 哈希差异与明文差异无关联:哪怕两个明文只有一个字符的差别,它们的哈希值也会完全不同,没有任何可推导的关联。你不能通过对比哈希值的差异来判断原密码的编辑距离,两者逻辑上完全不相关。
- 安全与需求的矛盾:如果为了计算编辑距离而存储旧密码的明文,直接违反了密码存储的安全准则——一旦数据库泄露,用户所有旧密码都会直接暴露,风险极高。
替代方案
如果想要避免用户复用旧密码的轻微变体,可以采用以下安全可行的方式:
- 限制最近N次密码的复用:只禁止用户使用最近3-5次的旧密码,通过哈希完全匹配来判断,简单且安全。
- 提高密码复杂度要求:比如增加最小长度、强制更多种类的字符(大写、小写、数字、特殊字符),让用户难以通过轻微修改旧密码来满足校验要求。
- 设置密码过期时间:定期要求用户更换密码,减少旧密码变体的使用概率。
内容的提问来源于stack exchange,提问作者ColinB
相关产品推荐
相关产品推荐

