eric6/Utilities/crypto/__init__.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Package implementing cryptography related functionality.
8 """
9
10 from __future__ import unicode_literals
11
12 import random
13 import base64
14
15 from PyQt5.QtCore import QCoreApplication
16 from PyQt5.QtWidgets import QLineEdit, QInputDialog
17
18 from E5Gui import E5MessageBox
19
20 import Preferences
21
22 ###############################################################################
23 ## password handling functions below
24 ###############################################################################
25
26
27 EncodeMarker = "CE4"
28 CryptoMarker = "CR5"
29
30 Delimiter = "$"
31
32 MasterPassword = None
33
34
35 def pwEncode(pw):
36 """
37 Module function to encode a password.
38
39 @param pw password to encode (string)
40 @return encoded password (string)
41 """
42 pop = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" \
43 ".,;:-_!$?*+#"
44 rpw = "".join(random.sample(pop, 32)) + pw + \
45 "".join(random.sample(pop, 32))
46 return EncodeMarker + base64.b64encode(rpw.encode("utf-8")).decode("ascii")
47
48
49 def pwDecode(epw):
50 """
51 Module function to decode a password.
52
53 @param epw encoded password to decode (string)
54 @return decoded password (string)
55 """
56 if not epw.startswith(EncodeMarker):
57 return epw # it was not encoded using pwEncode
58
59 return base64.b64decode(epw[3:].encode("ascii"))[32:-32].decode("utf-8")
60
61
62 def __getMasterPassword():
63 """
64 Private module function to get the password from the user.
65 """
66 global MasterPassword
67
68 pw, ok = QInputDialog.getText(
69 None,
70 QCoreApplication.translate("Crypto", "Master Password"),
71 QCoreApplication.translate("Crypto", "Enter the master password:"),
72 QLineEdit.Password)
73 if ok:
74 from .py3PBKDF2 import verifyPassword
75 masterPassword = Preferences.getUser("MasterPassword")
76 try:
77 if masterPassword:
78 if verifyPassword(pw, masterPassword):
79 MasterPassword = pwEncode(pw)
80 else:
81 E5MessageBox.warning(
82 None,
83 QCoreApplication.translate(
84 "Crypto", "Master Password"),
85 QCoreApplication.translate(
86 "Crypto",
87 """The given password is incorrect."""))
88 else:
89 E5MessageBox.critical(
90 None,
91 QCoreApplication.translate("Crypto", "Master Password"),
92 QCoreApplication.translate(
93 "Crypto",
94 """There is no master password registered."""))
95 except ValueError as why:
96 E5MessageBox.warning(
97 None,
98 QCoreApplication.translate("Crypto", "Master Password"),
99 QCoreApplication.translate(
100 "Crypto",
101 """<p>The given password cannot be verified.</p>"""
102 """<p>Reason: {0}""".format(str(why))))
103
104
105 def pwEncrypt(pw, masterPW=None):
106 """
107 Module function to encrypt a password.
108
109 @param pw password to encrypt (string)
110 @param masterPW password to be used for encryption (string)
111 @return encrypted password (string) and flag indicating
112 success (boolean)
113 """
114 if masterPW is None:
115 if MasterPassword is None:
116 __getMasterPassword()
117 if MasterPassword is None:
118 return "", False
119
120 masterPW = pwDecode(MasterPassword)
121
122 from .py3PBKDF2 import hashPasswordTuple
123 digestname, iterations, salt, pwHash = hashPasswordTuple(masterPW)
124 key = pwHash[:32]
125 from .py3AES import encryptData
126 try:
127 cipher = encryptData(key, pw.encode("utf-8"))
128 except ValueError:
129 return "", False
130 return CryptoMarker + Delimiter.join([
131 digestname,
132 str(iterations),
133 base64.b64encode(salt).decode("ascii"),
134 base64.b64encode(cipher).decode("ascii")
135 ]), True
136
137
138 def pwDecrypt(epw, masterPW=None):
139 """
140 Module function to decrypt a password.
141
142 @param epw hashed password to decrypt (string)
143 @param masterPW password to be used for decryption (string)
144 @return decrypted password (string) and flag indicating
145 success (boolean)
146 """
147 if not epw.startswith(CryptoMarker):
148 return epw, False # it was not encoded using pwEncrypt
149
150 if masterPW is None:
151 if MasterPassword is None:
152 __getMasterPassword()
153 if MasterPassword is None:
154 return "", False
155
156 masterPW = pwDecode(MasterPassword)
157
158 from .py3AES import decryptData
159 from .py3PBKDF2 import rehashPassword
160
161 hashParameters, epw = epw[3:].rsplit(Delimiter, 1)
162 try:
163 # recreate the key used to encrypt
164 key = rehashPassword(masterPW, hashParameters)[:32]
165 plaintext = decryptData(key, base64.b64decode(epw.encode("ascii")))
166 except ValueError:
167 return "", False
168 return plaintext.decode("utf-8"), True
169
170
171 def pwReencrypt(epw, oldPassword, newPassword):
172 """
173 Module function to re-encrypt a password.
174
175 @param epw hashed password to re-encrypt (string)
176 @param oldPassword password used to encrypt (string)
177 @param newPassword new password to be used (string)
178 @return encrypted password (string) and flag indicating
179 success (boolean)
180 """
181 plaintext, ok = pwDecrypt(epw, oldPassword)
182 if ok:
183 return pwEncrypt(plaintext, newPassword)
184 else:
185 return "", False
186
187
188 def pwRecode(epw, oldPassword, newPassword):
189 """
190 Module function to re-encode a password.
191
192 In case of an error the encoded password is returned unchanged.
193
194 @param epw encoded password to re-encode (string)
195 @param oldPassword password used to encode (string)
196 @param newPassword new password to be used (string)
197 @return encoded password (string)
198 """
199 if epw == "":
200 return epw
201
202 if newPassword == "":
203 plaintext, ok = pwDecrypt(epw)
204 return (pwEncode(plaintext) if ok else epw)
205 else:
206 if oldPassword == "":
207 plaintext = pwDecode(epw)
208 cipher, ok = pwEncrypt(plaintext, newPassword)
209 return (cipher if ok else epw)
210 else:
211 npw, ok = pwReencrypt(epw, oldPassword, newPassword)
212 return (npw if ok else epw)
213
214
215 def pwConvert(pw, encode=True):
216 """
217 Module function to convert a plaintext password to the encoded form or
218 vice versa.
219
220 If there is an error, an empty code is returned for the encode function
221 or the given encoded password for the decode function.
222
223 @param pw password to encode (string)
224 @param encode flag indicating an encode or decode function (boolean)
225 @return encoded or decoded password (string)
226 """
227 if pw == "":
228 return pw
229
230 if encode:
231 # plain text -> encoded
232 if Preferences.getUser("UseMasterPassword"):
233 epw = pwEncrypt(pw)[0]
234 else:
235 epw = pwEncode(pw)
236 return epw
237 else:
238 # encoded -> plain text
239 if Preferences.getUser("UseMasterPassword"):
240 plain, ok = pwDecrypt(pw)
241 else:
242 plain, ok = pwDecode(pw), True
243 return (plain if ok else pw)
244
245
246 def changeRememberedMaster(newPassword):
247 """
248 Module function to change the remembered master password.
249
250 @param newPassword new password to be used (string)
251 """
252 global MasterPassword
253
254 if newPassword == "":
255 MasterPassword = None
256 else:
257 MasterPassword = pwEncode(newPassword)
258
259
260 def dataEncrypt(data, password, keyLength=32, hashIterations=10000):
261 """
262 Module function to encrypt a password.
263
264 @param data data to encrypt (bytes)
265 @param password password to be used for encryption (string)
266 @keyparam keyLength length of the key to be generated for encryption
267 (16, 24 or 32)
268 @keyparam hashIterations number of hashes to be applied to the password for
269 generating the encryption key (integer)
270 @return encrypted data (bytes) and flag indicating
271 success (boolean)
272 """
273 from .py3AES import encryptData
274 from .py3PBKDF2 import hashPasswordTuple
275
276 digestname, iterations, salt, pwHash = \
277 hashPasswordTuple(password, iterations=hashIterations)
278 key = pwHash[:keyLength]
279 try:
280 cipher = encryptData(key, data)
281 except ValueError:
282 return b"", False
283 return CryptoMarker.encode("utf-8") + Delimiter.encode("utf-8").join([
284 digestname.encode("utf-8"),
285 str(iterations).encode("utf-8"),
286 base64.b64encode(salt),
287 base64.b64encode(cipher)
288 ]), True
289
290
291 def dataDecrypt(edata, password, keyLength=32):
292 """
293 Module function to decrypt a password.
294
295 @param edata hashed data to decrypt (string)
296 @param password password to be used for decryption (string)
297 @keyparam keyLength length of the key to be generated for decryption
298 (16, 24 or 32)
299 @return decrypted data (bytes) and flag indicating
300 success (boolean)
301 """
302 if not edata.startswith(CryptoMarker.encode("utf-8")):
303 return edata, False # it was not encoded using dataEncrypt
304
305 from .py3AES import decryptData
306 from .py3PBKDF2 import rehashPassword
307
308 hashParametersBytes, edata = edata[3:].rsplit(Delimiter.encode("utf-8"), 1)
309 hashParameters = hashParametersBytes.decode()
310 try:
311 # recreate the key used to encrypt
312 key = rehashPassword(password, hashParameters)[:keyLength]
313 plaintext = decryptData(key, base64.b64decode(edata))
314 except ValueError:
315 return "", False
316 return plaintext, True

eric ide

mercurial