comparison sat/memory/crypto.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 003b8b4b56a7
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT: a jabber client 4 # SAT: a jabber client
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org) 6 # Copyright (C) 2013-2016 Adrien Cossa (souliane@mailoo.org)
90 d.addCallback(lambda text: BlockCipher.unpad(text)) 90 d.addCallback(lambda text: BlockCipher.unpad(text))
91 # XXX: cipher.decrypt gives no way to make the distinction between 91 # XXX: cipher.decrypt gives no way to make the distinction between
92 # a decrypted empty value and a decryption failure... both return 92 # a decrypted empty value and a decryption failure... both return
93 # the empty value. Fortunately, we detect empty passwords beforehand 93 # the empty value. Fortunately, we detect empty passwords beforehand
94 # thanks to the "leave_empty" parameter which is used by default. 94 # thanks to the "leave_empty" parameter which is used by default.
95 d.addCallback(lambda text: text.decode("utf-8") if text else None) 95 d.addCallback(lambda text: text if text else None)
96 return d 96 return d
97 97
98 @classmethod 98 @classmethod
99 def getRandomKey(cls, size=None, base64=False): 99 def getRandomKey(cls, size=None, base64=False):
100 """Return a random key suitable for block cipher encryption. 100 """Return a random key suitable for block cipher encryption.
112 112
113 @classmethod 113 @classmethod
114 def pad(self, s): 114 def pad(self, s):
115 """Method from http://stackoverflow.com/a/12525165""" 115 """Method from http://stackoverflow.com/a/12525165"""
116 bs = BlockCipher.BLOCK_SIZE 116 bs = BlockCipher.BLOCK_SIZE
117 return s + (bs - len(s) % bs) * chr(bs - len(s) % bs) 117 return s + (bs - len(s) % bs) * (chr(bs - len(s) % bs)).encode('utf-8')
118 118
119 @classmethod 119 @classmethod
120 def unpad(self, s): 120 def unpad(self, s):
121 """Method from http://stackoverflow.com/a/12525165""" 121 """Method from http://stackoverflow.com/a/12525165"""
122 s = s.decode('utf-8')
122 return s[0 : -ord(s[-1])] 123 return s[0 : -ord(s[-1])]
123 124
124 125
125 class PasswordHasher(object): 126 class PasswordHasher(object):
126 127
134 @param salt (base-64 encoded str): if not None, use the given salt instead of a random value 135 @param salt (base-64 encoded str): if not None, use the given salt instead of a random value
135 @param leave_empty (bool): if True, empty password will be returned "as is" 136 @param leave_empty (bool): if True, empty password will be returned "as is"
136 @return: Deferred: base-64 encoded str 137 @return: Deferred: base-64 encoded str
137 """ 138 """
138 if leave_empty and password == "": 139 if leave_empty and password == "":
139 return succeed(password) 140 return succeed(b"")
140 salt = ( 141 salt = (
141 b64decode(salt)[: PasswordHasher.SALT_LEN] 142 b64decode(salt)[: PasswordHasher.SALT_LEN]
142 if salt 143 if salt
143 else urandom(PasswordHasher.SALT_LEN) 144 else urandom(PasswordHasher.SALT_LEN)
144 ) 145 )
145 d = deferToThread(PBKDF2, password, salt) 146 d = deferToThread(PBKDF2, password, salt)
146 d.addCallback(lambda hashed: b64encode(salt + hashed)) 147 d.addCallback(lambda hashed: b64encode(salt + hashed))
147 return d 148 return d
149
150 @classmethod
151 def compare_hash(cls, hashed_attempt, hashed):
152 assert isinstance(hashed, bytes)
153 return hashed_attempt == hashed
148 154
149 @classmethod 155 @classmethod
150 def verify(cls, attempt, hashed): 156 def verify(cls, attempt, hashed):
151 """Verify a password attempt. 157 """Verify a password attempt.
152 158
154 @param hashed (str): the hash of the password 160 @param hashed (str): the hash of the password
155 @return: Deferred: boolean 161 @return: Deferred: boolean
156 """ 162 """
157 leave_empty = hashed == "" 163 leave_empty = hashed == ""
158 d = PasswordHasher.hash(attempt, hashed, leave_empty) 164 d = PasswordHasher.hash(attempt, hashed, leave_empty)
159 d.addCallback(lambda hashed_attempt: hashed_attempt == hashed) 165 d.addCallback(cls.compare_hash, hashed=hashed.encode('utf-8'))
160 return d 166 return d