/usr/share/pyshared/acct_mgr/pwhash.py is in trac-accountmanager 0.4.3-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | # -*- coding: utf-8 -*-
#
# Copyright (C) 2007 Matthew Good <trac@matt-good.net>
# Copyright (C) 2011 Steffen Hoffmann <hoff.st@web.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
#
# Author: Matthew Good <trac@matt-good.net>
from binascii import hexlify
from os import urandom
from trac.core import Component, Interface, implements
from trac.config import Option
from acct_mgr.api import AccountManager, _, N_
from acct_mgr.hashlib_compat import md5, sha1
from acct_mgr.md5crypt import md5crypt
try:
from passlib.apps import custom_app_context as passlib_ctxt
except ImportError:
# not available
# Hint: Python2.5 is required too
passlib_ctxt = None
class IPasswordHashMethod(Interface):
def generate_hash(user, password):
pass
def check_hash(user, password, hash):
pass
class HtPasswdHashMethod(Component):
implements(IPasswordHashMethod)
hash_type = Option('account-manager', 'db_htpasswd_hash_type', 'crypt',
doc = N_("Default hash type of new/updated passwords"))
def generate_hash(self, user, password):
password = password.encode('utf-8')
return mkhtpasswd(password, self.hash_type)
def check_hash(self, user, password, hash):
password = password.encode('utf-8')
hash2 = htpasswd(password, hash)
return hash == hash2
class HtDigestHashMethod(Component):
implements(IPasswordHashMethod)
realm = Option('account-manager', 'db_htdigest_realm', '',
doc = N_("Realm to select relevant htdigest db entries"))
def generate_hash(self, user, password):
user,password,realm = _encode(user, password, self.realm)
return ':'.join([realm, htdigest(user, realm, password)])
def check_hash(self, user, password, hash):
return hash == self.generate_hash(user, password)
def _encode(*args):
return [a.encode('utf-8') for a in args]
# check for the availability of the "crypt" module for checking passwords on
# Unix-like platforms
# MD5 is still used when adding/updating passwords with htdigest
try:
from crypt import crypt
except ImportError:
crypt = None
def salt(salt_char_count=8):
s = ''
v = long(hexlify(urandom(int(salt_char_count/8*6))), 16)
itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
for i in range(int(salt_char_count)):
s += itoa64[v & 0x3f]; v >>= 6
return s
def hash_prefix(hash_type):
"""Map hash type to salt prefix."""
if hash_type == 'md5':
return '$apr1$'
elif hash_type == 'sha':
return '{SHA}'
elif hash_type == 'sha256':
return '$5$'
elif hash_type == 'sha512':
return '$6$'
else:
# use 'crypt' hash by default anyway
return ''
def htpasswd(password, hash):
if hash.startswith('$apr1$'):
return md5crypt(password, hash[6:].split('$')[0], '$apr1$')
elif hash.startswith('{SHA}'):
return '{SHA}' + sha1(password).digest().encode('base64')[:-1]
elif passlib_ctxt is not None and hash.startswith('$5$') and \
'sha256_crypt' in passlib_ctxt.policy.schemes():
return passlib_ctxt.encrypt(password, scheme="sha256_crypt",
rounds=5000, salt=hash[3:].split('$')[0])
elif passlib_ctxt is not None and hash.startswith('$6$') and \
'sha512_crypt' in passlib_ctxt.policy.schemes():
return passlib_ctxt.encrypt(password, scheme="sha512_crypt",
rounds=5000, salt=hash[3:].split('$')[0])
elif crypt is None:
# crypt passwords are only supported on Unix-like systems
raise NotImplementedError(_("""The \"crypt\" module is unavailable
on this platform."""))
else:
if hash.startswith('$5$') or hash.startswith('$6$'):
# Import of passlib failed, now check, if crypt is capable.
if not crypt(password, hash).startswith(hash):
# No, so bail out.
raise NotImplementedError(_(
"""Neither are \"sha2\" hash algorithms supported by the
\"crypt\" module on this platform nor is \"passlib\"
available."""))
return crypt(password, hash)
def mkhtpasswd(password, hash_type=''):
hash_prefix_ = hash_prefix(hash_type)
if hash_type.startswith('sha') and len(hash_type) > 3:
salt_ = salt(16)
else:
# Don't waste entropy to older hash types.
salt_ = salt()
if hash_prefix_ == '':
if crypt is None:
salt_ = '$apr1$' + salt_
else:
salt_ = hash_prefix_ + salt_
return htpasswd(password, salt_)
def htdigest(user, realm, password):
p = ':'.join([user, realm, password])
return md5(p).hexdigest()
|