login.py 3.46 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017  Gabriel Détraz
# Copyright © 2017  Goulven Kermarec
# Copyright © 2017  Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

chirac's avatar
chirac committed
23 24 25 26
# -*- coding: utf-8 -*-
# Module d'authentification
# David Sinquin, Gabriel Détraz, Goulven Kermarec

lhark's avatar
lhark committed
27 28 29

import hashlib
import binascii
chirac's avatar
chirac committed
30
import os
lhark's avatar
lhark committed
31 32 33 34 35 36 37 38 39 40 41
from base64 import encodestring
from base64 import decodestring
from collections import OrderedDict

from django.contrib.auth import hashers


ALGO_NAME = "{SSHA}"
ALGO_LEN = len(ALGO_NAME + "$")
DIGEST_LEN = 20

chirac's avatar
chirac committed
42 43 44 45 46

def makeSecret(password):
    salt = os.urandom(4)
    h = hashlib.sha1(password.encode())
    h.update(salt)
lhark's avatar
lhark committed
47 48
    return ALGO_NAME + "$" + encodestring(h.digest() + salt).decode()[:-1]

chirac's avatar
chirac committed
49 50

def hashNT(password):
Gabriel Detraz's avatar
Gabriel Detraz committed
51
    hash = hashlib.new('md4', password.encode('utf-16le')).digest()
Gabriel Detraz's avatar
Gabriel Detraz committed
52
    return binascii.hexlify(hash).upper()
chirac's avatar
chirac committed
53

lhark's avatar
lhark committed
54

chirac's avatar
chirac committed
55
def checkPassword(challenge_password, password):
lhark's avatar
lhark committed
56 57 58
    challenge_bytes = decodestring(challenge_password[ALGO_LEN:].encode())
    digest = challenge_bytes[:DIGEST_LEN]
    salt = challenge_bytes[DIGEST_LEN:]
chirac's avatar
chirac committed
59 60 61
    hr = hashlib.sha1(password.encode())
    hr.update(salt)
    valid_password = True
lhark's avatar
lhark committed
62 63
    # La comparaison est volontairement en temps constant
    # (pour éviter les timing-attacks)
chirac's avatar
chirac committed
64 65 66
    for i, j in zip(digest, hr.digest()):
        valid_password &= i == j
    return valid_password
lhark's avatar
lhark committed
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


class SSHAPasswordHasher(hashers.BasePasswordHasher):
    """
    SSHA password hashing to allow for LDAP auth compatibility
    """

    algorithm = ALGO_NAME

    def encode(self, password, salt, iterations=None):
        """
        Hash and salt the given password using SSHA algorithm

        salt is overridden
        """
        assert password is not None
        return makeSecret(password)

    def verify(self, password, encoded):
        """
        Check password against encoded using SSHA algorithm
        """
        assert encoded.startswith(self.algorithm)
        return checkPassword(encoded, password)

    def safe_summary(self, encoded):
        """
        Provides a safe summary ofthe password
        """
        assert encoded.startswith(self.algorithm)
        hash = encoded[ALGO_LEN:]
        hash = binascii.hexlify(decodestring(hash.encode())).decode()
        return OrderedDict([
            ('algorithm', self.algorithm),
            ('iterations', 0),
            ('salt', hashers.mask_hash(hash[2*DIGEST_LEN:], show=2)),
            ('hash', hashers.mask_hash(hash[:2*DIGEST_LEN])),
        ])

    def harden_runtime(self, password, encoded):
        """
        Method implemented to shut up BasePasswordHasher warning

        As we are not using multiple iterations the method is pretty useless
        """
        pass