simplecrypter 0.1.0__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.
- {simplecrypter-0.1.0 → simplecrypter-0.1.2}/PKG-INFO +4 -2
- {simplecrypter-0.1.0 → simplecrypter-0.1.2}/README.md +3 -1
- {simplecrypter-0.1.0 → simplecrypter-0.1.2}/pyproject.toml +1 -1
- {simplecrypter-0.1.0 → simplecrypter-0.1.2}/src/simplecrypter/core.py +22 -7
- simplecrypter-0.1.2/tests/test_core.py +31 -0
- simplecrypter-0.1.0/tests/test_core.py +0 -23
- {simplecrypter-0.1.0 → simplecrypter-0.1.2}/LICENSE +0 -0
- {simplecrypter-0.1.0 → simplecrypter-0.1.2}/src/simplecrypter/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simplecrypter
|
|
3
|
-
Version: 0.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.
|
|
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.
|
|
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
|
|
|
@@ -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")
|
|
@@ -34,19 +41,27 @@ def easy_encrypt(phrase: str, password: str) -> str:
|
|
|
34
41
|
|
|
35
42
|
|
|
36
43
|
def easy_decrypt(ciphertext: str, password: str) -> str:
|
|
37
|
-
"""Decrypt
|
|
38
|
-
|
|
44
|
+
"""Decrypt a value, returning a friendly message instead of crypto errors.
|
|
45
|
+
|
|
46
|
+
A malformed value returns ``"invalid encrypted stringㅤ"``. A wrong password
|
|
47
|
+
(or an altered encrypted payload, which cannot safely be distinguished from
|
|
48
|
+
a wrong password) returns ``"incorrect passwordㅤ"``.
|
|
49
|
+
"""
|
|
50
|
+
if not isinstance(ciphertext, str) or not ciphertext:
|
|
51
|
+
return _INVALID_CIPHERTEXT
|
|
39
52
|
_require_text(password, "password")
|
|
40
53
|
|
|
41
54
|
try:
|
|
42
55
|
if not ciphertext.startswith(_PREFIX):
|
|
43
|
-
|
|
56
|
+
return _INVALID_CIPHERTEXT
|
|
44
57
|
encoded_salt, encrypted = ciphertext.removeprefix(_PREFIX).split(":", 1)
|
|
45
58
|
if not encrypted:
|
|
46
|
-
|
|
59
|
+
return _INVALID_CIPHERTEXT
|
|
47
60
|
salt = base64.urlsafe_b64decode(encoded_salt.encode("ascii"))
|
|
48
61
|
if len(salt) != _SALT_BYTES:
|
|
49
|
-
|
|
62
|
+
return _INVALID_CIPHERTEXT
|
|
50
63
|
return Fernet(_key_from_password(password, salt)).decrypt(encrypted.encode("ascii")).decode("utf-8")
|
|
51
|
-
except
|
|
52
|
-
|
|
64
|
+
except InvalidToken:
|
|
65
|
+
return _INCORRECT_PASSWORD
|
|
66
|
+
except (ValueError, UnicodeError, binascii.Error):
|
|
67
|
+
return _INVALID_CIPHERTEXT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import simplecrypter
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_encrypt_then_decrypt():
|
|
5
|
+
encrypted = simplecrypter.easy_encrypt("hello", "a secure password")
|
|
6
|
+
|
|
7
|
+
assert encrypted.startswith("simplecrypter:v1:")
|
|
8
|
+
assert simplecrypter.easy_decrypt(encrypted, "a secure password") == "hello"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_short_aliases_match_main_api():
|
|
12
|
+
encrypted = simplecrypter.ez_e("hello", "a secure password")
|
|
13
|
+
|
|
14
|
+
assert simplecrypter.ez_d(encrypted, "a secure password") == "hello"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_wrong_password_returns_a_friendly_message():
|
|
18
|
+
encrypted = simplecrypter.easy_encrypt("hello", "correct password")
|
|
19
|
+
|
|
20
|
+
assert simplecrypter.easy_decrypt(encrypted, "wrong password") == "incorrect passwordㅤ"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_invalid_ciphertext_returns_a_friendly_message():
|
|
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ㅤ"
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
import simplecrypter
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_encrypt_then_decrypt():
|
|
7
|
-
encrypted = simplecrypter.easy_encrypt("hello", "a secure password")
|
|
8
|
-
|
|
9
|
-
assert encrypted.startswith("simplecrypter:v1:")
|
|
10
|
-
assert simplecrypter.easy_decrypt(encrypted, "a secure password") == "hello"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_short_aliases_match_main_api():
|
|
14
|
-
encrypted = simplecrypter.ez_e("hello", "a secure password")
|
|
15
|
-
|
|
16
|
-
assert simplecrypter.ez_d(encrypted, "a secure password") == "hello"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_wrong_password_is_rejected():
|
|
20
|
-
encrypted = simplecrypter.easy_encrypt("hello", "correct password")
|
|
21
|
-
|
|
22
|
-
with pytest.raises(ValueError, match="invalid or the password is incorrect"):
|
|
23
|
-
simplecrypter.easy_decrypt(encrypted, "wrong password")
|
|
File without changes
|
|
File without changes
|