simplecrypter 0.1.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: simplecrypter
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Small, password-based authenticated encryption for Python strings.
5
5
  Author: Muck
6
6
  License: MIT
@@ -39,7 +39,9 @@ encrypted = simplecrypter.ez_e("phrase", "password")
39
39
  original = simplecrypter.ez_d(encrypted, "password")
40
40
  ```
41
41
 
42
- Each encryption uses a new random salt. The encrypted text includes that salt, so only the encrypted value and the password are needed to decrypt it. `easy_decrypt` returns `"incorrect password"` when the password does not decrypt a correctly formed value, or `"invalid encrypted string"` when the value is malformed.
42
+ Each encryption uses a new random salt. The encrypted text includes that salt, so only the encrypted value and the password are needed to decrypt it. `easy_decrypt` returns `"incorrect passwordㅤ"` when the password does not decrypt a correctly formed value, or `"invalid encrypted stringㅤ"` when the value is malformed. Both messages end in the invisible U+3164 character so they can be distinguished from normal text.
43
+
44
+ The marked error strings are reserved. Attempting to encrypt one returns `"invalid passwordㅤ"` instead.
43
45
 
44
46
  ## Notes
45
47
 
@@ -24,7 +24,9 @@ encrypted = simplecrypter.ez_e("phrase", "password")
24
24
  original = simplecrypter.ez_d(encrypted, "password")
25
25
  ```
26
26
 
27
- Each encryption uses a new random salt. The encrypted text includes that salt, so only the encrypted value and the password are needed to decrypt it. `easy_decrypt` returns `"incorrect password"` when the password does not decrypt a correctly formed value, or `"invalid encrypted string"` when the value is malformed.
27
+ Each encryption uses a new random salt. The encrypted text includes that salt, so only the encrypted value and the password are needed to decrypt it. `easy_decrypt` returns `"incorrect passwordㅤ"` when the password does not decrypt a correctly formed value, or `"invalid encrypted stringㅤ"` when the value is malformed. Both messages end in the invisible U+3164 character so they can be distinguished from normal text.
28
+
29
+ The marked error strings are reserved. Attempting to encrypt one returns `"invalid passwordㅤ"` instead.
28
30
 
29
31
  ## Notes
30
32
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "simplecrypter"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "Small, password-based authenticated encryption for Python strings."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -9,6 +9,11 @@ from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
9
9
 
10
10
  _PREFIX = "simplecrypter:v1:"
11
11
  _SALT_BYTES = 16
12
+ _ERROR_SUFFIX = "\u3164"
13
+ _INCORRECT_PASSWORD = f"incorrect password{_ERROR_SUFFIX}"
14
+ _INVALID_CIPHERTEXT = f"invalid encrypted string{_ERROR_SUFFIX}"
15
+ _INVALID_PASSWORD = f"invalid password{_ERROR_SUFFIX}"
16
+ _RESERVED_ERROR_VALUES = {_INCORRECT_PASSWORD, _INVALID_CIPHERTEXT, _INVALID_PASSWORD}
12
17
 
13
18
 
14
19
  def _require_text(value: str, name: str) -> None:
@@ -27,6 +32,8 @@ def _key_from_password(password: str, salt: bytes) -> bytes:
27
32
  def easy_encrypt(phrase: str, password: str) -> str:
28
33
  """Encrypt a text phrase with a password and return a self-contained string."""
29
34
  _require_text(phrase, "phrase")
35
+ if phrase in _RESERVED_ERROR_VALUES:
36
+ return _INVALID_PASSWORD
30
37
  salt = os.urandom(_SALT_BYTES)
31
38
  encrypted = Fernet(_key_from_password(password, salt)).encrypt(phrase.encode("utf-8"))
32
39
  encoded_salt = base64.urlsafe_b64encode(salt).decode("ascii")
@@ -36,25 +43,25 @@ def easy_encrypt(phrase: str, password: str) -> str:
36
43
  def easy_decrypt(ciphertext: str, password: str) -> str:
37
44
  """Decrypt a value, returning a friendly message instead of crypto errors.
38
45
 
39
- A malformed value returns ``"invalid encrypted string"``. A wrong password
46
+ A malformed value returns ``"invalid encrypted stringㅤ"``. A wrong password
40
47
  (or an altered encrypted payload, which cannot safely be distinguished from
41
- a wrong password) returns ``"incorrect password"``.
48
+ a wrong password) returns ``"incorrect passwordㅤ"``.
42
49
  """
43
50
  if not isinstance(ciphertext, str) or not ciphertext:
44
- return "invalid encrypted string"
51
+ return _INVALID_CIPHERTEXT
45
52
  _require_text(password, "password")
46
53
 
47
54
  try:
48
55
  if not ciphertext.startswith(_PREFIX):
49
- return "invalid encrypted string"
56
+ return _INVALID_CIPHERTEXT
50
57
  encoded_salt, encrypted = ciphertext.removeprefix(_PREFIX).split(":", 1)
51
58
  if not encrypted:
52
- return "invalid encrypted string"
59
+ return _INVALID_CIPHERTEXT
53
60
  salt = base64.urlsafe_b64decode(encoded_salt.encode("ascii"))
54
61
  if len(salt) != _SALT_BYTES:
55
- return "invalid encrypted string"
62
+ return _INVALID_CIPHERTEXT
56
63
  return Fernet(_key_from_password(password, salt)).decrypt(encrypted.encode("ascii")).decode("utf-8")
57
64
  except InvalidToken:
58
- return "incorrect password"
65
+ return _INCORRECT_PASSWORD
59
66
  except (ValueError, UnicodeError, binascii.Error):
60
- return "invalid encrypted string"
67
+ return _INVALID_CIPHERTEXT
@@ -17,8 +17,15 @@ def test_short_aliases_match_main_api():
17
17
  def test_wrong_password_returns_a_friendly_message():
18
18
  encrypted = simplecrypter.easy_encrypt("hello", "correct password")
19
19
 
20
- assert simplecrypter.easy_decrypt(encrypted, "wrong password") == "incorrect password"
20
+ assert simplecrypter.easy_decrypt(encrypted, "wrong password") == "incorrect passwordㅤ"
21
21
 
22
22
 
23
23
  def test_invalid_ciphertext_returns_a_friendly_message():
24
- assert simplecrypter.easy_decrypt("not encrypted", "a secure password") == "invalid encrypted string"
24
+ assert simplecrypter.easy_decrypt("not encrypted", "a secure password") == "invalid encrypted stringㅤ"
25
+
26
+
27
+ def test_plain_error_text_can_be_encrypted_but_marked_error_text_is_reserved():
28
+ encrypted = simplecrypter.easy_encrypt("incorrect password", "a secure password")
29
+
30
+ assert simplecrypter.easy_decrypt(encrypted, "a secure password") == "incorrect password"
31
+ assert simplecrypter.easy_encrypt("incorrect passwordㅤ", "a secure password") == "invalid passwordㅤ"
File without changes