src/eric7/Utilities/crypto/__init__.py

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

eric ide

mercurial