SwiftGUI_Encryption 0.0.3__tar.gz → 0.0.5__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.4
2
2
  Name: SwiftGUI_Encryption
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Useful encryption-features for SwiftGUI-applications based on PyCryptoDome
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "SwiftGUI_Encryption"
3
- version = "0.0.3"
3
+ version = "0.0.5"
4
4
  packages = [
5
5
  { include = "SwiftGUI_Encryption", from = "src" }
6
6
  ]
@@ -1,3 +1,3 @@
1
1
 
2
- from .low_level import encrypt, decrypt, readable_hash, make_hash, random_key, argon2_key_derivation
2
+ from .low_level import encrypt, decrypt, readable_hash, make_hash, random_key, argon2_key_derivation, encrypt_CTR, decrypt_CTR
3
3
 
@@ -45,6 +45,35 @@ def argon2_key_derivation(derive_from: bytes, salt: bytes, multiplier: int = 1,
45
45
 
46
46
  return argon2pure.argon2(derive_from, salt, multiplier, 8 * multiplier, parallelism=1, tag_length=n)
47
47
 
48
+ def encrypt_CTR(data: bytes, key: bytes, nonce: bytes) -> bytes:
49
+ """
50
+ Encrypt some data in AES-256-CTR mode.
51
+
52
+ :param data:
53
+ :param key:
54
+ :param nonce: A 12-byte random number, which you should definetly remember
55
+ :return: Encrypted
56
+ """
57
+ crypter = AES.new(key, AES.MODE_CTR, nonce=nonce)
58
+ enc_data = crypter.encrypt(data)
59
+
60
+ return enc_data
61
+
62
+ def decrypt_CTR(enc_data:bytes, key:bytes, nonce:bytes) -> bytes:
63
+ """
64
+ Decrypt some data in AES-256-CTR mode.
65
+ The decrypted data is not verified, since this isn't a feature of AES-CTR
66
+
67
+ :param enc_data: Encrypted data
68
+ :param key: This needs to be the same as with the encryption
69
+ :param nonce: This needs to be the same as with the encryption
70
+ :return:
71
+ """
72
+ crypter = AES.new(key, AES.MODE_CTR, nonce=nonce)
73
+ data = crypter.decrypt(enc_data)
74
+
75
+ return data
76
+
48
77
  def encrypt(data: bytes, key: bytes, nonce: bytes, mac_len: int = 8) -> bytes:
49
78
  """
50
79
  Encrypt some data.
@@ -2,7 +2,7 @@
2
2
  from . import Advanced
3
3
  random_key = Advanced.random_key
4
4
 
5
- from .basics import decrypt_full, encrypt_full, encrypt_with_password, decrypt_with_password, password_to_key
5
+ from .basics import decrypt_full, encrypt_full, encrypt_with_password, decrypt_with_password, password_to_key, encrypt_multilayer, decrypt_multilayer
6
6
  from .key_files import KeyFile, KeyHandler, BaseKeyFile
7
7
 
8
8
  try:
@@ -1,8 +1,3 @@
1
- import argon2pure
2
- from Crypto.Cipher import AES
3
- import os
4
- import hashlib
5
-
6
1
  from SwiftGUI_Encryption import Advanced as adv
7
2
 
8
3
  # This should not be chanced, it just doesn't feel right to add magic numbers...
@@ -34,6 +29,62 @@ def decrypt_full(data: bytes, key: bytes) -> bytes:
34
29
 
35
30
  return adv.decrypt(data, key, nonce)
36
31
 
32
+ def encrypt_multilayer(data: bytes, *keys: bytes) -> bytes:
33
+ """
34
+ Encrypt some data multiple times.
35
+ Pass a key for every layer of encryption.
36
+
37
+ This is overkill for most applications.
38
+ A single AES-256-GCM-encryption is already very secure, even against quantumcomputers.
39
+
40
+ VERY IMPORTANT:
41
+ To attack even a 3-layer-encryption is basically impossible.
42
+ So the attack will be on the keys, not the encryption.
43
+ Make sure the keys are secure, they are the weakest link.
44
+
45
+ Also, don't correlate the keys in any way.
46
+ If you can calculate key2 from key1, it defies the whole reason for this multilayer-encryption.
47
+
48
+ KINDA IMPORTANT:
49
+ Using two keys is only a little better than one key, because someone could do a "meet-in-the-middle-attack".
50
+ As a general rule, you should use an odd number of keys.
51
+
52
+ TECHNICALITIES:
53
+ Only the innermost encryption is using AES-GCM Mode. All other layers are AES-CTR.
54
+ That's because full AES-GCM allows guessing, if the decryption was successful.
55
+ Especially for short data, you could brute-force through layer by layer, leaving only a few possible keys per layer.
56
+
57
+ So, this function disables that verification-step for the outer layers.
58
+ You can only check if the full decryption was a success, but not separate layers.
59
+
60
+ :return:
61
+ """
62
+ # GCM encryption
63
+ data = encrypt_full(data, keys[0])
64
+
65
+ # CTR encryptions
66
+ for key in keys[1:]:
67
+ nonce = adv.random_key(12)
68
+ data = nonce + adv.encrypt_CTR(data, key, nonce)
69
+
70
+ return data
71
+
72
+ def decrypt_multilayer(data: bytes, *keys: bytes) -> bytes:
73
+ """
74
+ Read the description of encrypt_multilayer.
75
+
76
+ The keys have to be in the same order as with the encryption.
77
+
78
+ :return:
79
+ """
80
+ # CTR encryptions
81
+ for key in keys[1:][::-1]:
82
+ nonce = data[:12]
83
+ data = adv.decrypt_CTR(data[12:], key, nonce)
84
+
85
+ # GCM encryption
86
+ return decrypt_full(data, keys[0])
87
+
37
88
  def encrypt_with_password(data: bytes, password: str, security_multiplier: int = 1) -> bytes:
38
89
  """
39
90
  IMPORTANT:
@@ -107,8 +107,10 @@ class PasswordJSONDictFile(EncryptedJSONDictFile):
107
107
  **kwargs
108
108
  )
109
109
 
110
- def _regenerate_key(self):
111
- self._salt = random_key(SALT_LEN)
110
+ def _regenerate_key(self, new_salt = True):
111
+ if new_salt:
112
+ self._salt = random_key(SALT_LEN)
113
+
112
114
  self._filekey = adv.argon2_key_derivation(self._password.encode(), self._salt)
113
115
 
114
116
  def change_key(self, new_key: bytes):
@@ -151,6 +153,11 @@ class PasswordJSONDictFile(EncryptedJSONDictFile):
151
153
  ) -> dict:
152
154
  raw = path.read_bytes()
153
155
 
156
+ salt = raw[:SALT_LEN]
157
+ if salt != self._salt:
158
+ self._salt = salt
159
+ self._regenerate_key(new_salt=False)
160
+
154
161
  raw = decrypt_full(raw[SALT_LEN:], self._filekey).decode()
155
162
  return json.loads(raw)
156
163