Skip to content

Commit 9ac4d54

Browse files
authored
Merge pull request #104 from cmason3/dev
update crypto stuff
2 parents 0f3fe89 + 00976ab commit 9ac4d54

File tree

3 files changed

+67
-60
lines changed

3 files changed

+67
-60
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## CHANGELOG
22

3+
### [1.23.1] - December 12, 2024
4+
- Renamed class `Vault` to `AnsibleVault`
5+
- Moved class `Vaulty` from `ext_jinjafx.py` into `jinjafx.py`
6+
37
### [1.23.0] - December 10, 2024
48
- Added support for multiple templates within a DataTemplate to allow templates to be nested
59

@@ -548,6 +552,7 @@ Would result in the following:
548552
- Initial release
549553

550554

555+
[1.23.1]: https://github.com/cmason3/jinjafx/compare/v1.23.0...v1.23.1
551556
[1.23.0]: https://github.com/cmason3/jinjafx/compare/v1.22.2...v1.23.0
552557
[1.22.2]: https://github.com/cmason3/jinjafx/compare/v1.22.1...v1.22.2
553558
[1.22.1]: https://github.com/cmason3/jinjafx/compare/v1.22.0...v1.22.1

extensions/ext_jinjafx.py

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818
from cryptography.hazmat.primitives import hashes
1919
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
2020
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
21-
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
22-
from cryptography.exceptions import InvalidTag
23-
from jinjafx import JinjaFx
21+
from jinjafx import JinjaFx, Vaulty
2422

2523
import os, base64, random, re, hashlib, ipaddress
2624

@@ -314,51 +312,3 @@ def __xpath(self, s_xml, s_path):
314312

315313
else:
316314
raise JinjaFx.TemplateError("'xpath' filter requires the 'lxml' python module")
317-
318-
class Vaulty():
319-
def __init__(self):
320-
self.__prefix = '$VAULTY;'
321-
self.__kcache = {}
322-
323-
def __derive_key(self, password, salt=None):
324-
if (ckey := (password, salt)) in self.__kcache:
325-
e = self.__kcache[ckey]
326-
self.__kcache[ckey] = e[0], e[1], e[2] + 1
327-
return self.__kcache[ckey]
328-
329-
if salt is None:
330-
salt = os.urandom(16)
331-
332-
key = Scrypt(salt, 32, 2**16, 8, 1).derive(password.encode('utf-8'))
333-
self.__kcache[ckey] = [salt, key, 0]
334-
return [salt, key, 0]
335-
336-
def encrypt(self, plaintext, password, cols=None):
337-
version = b'\x01'
338-
salt, key, uc = self.__derive_key(password)
339-
nonce = os.urandom(12)[:-4] + uc.to_bytes(4, 'big')
340-
ciphertext = ChaCha20Poly1305(key).encrypt(nonce, plaintext.encode('utf-8'), None)
341-
342-
r = self.__prefix + base64.b64encode(version + salt + nonce + ciphertext).decode('utf-8')
343-
344-
if cols is not None:
345-
r = '\n'.join([r[i:i + cols] for i in range(0, len(r), cols)])
346-
347-
return r
348-
349-
def decrypt(self, ciphertext, password):
350-
if ciphertext.lstrip().startswith(self.__prefix):
351-
try:
352-
nciphertext = base64.b64decode(ciphertext.strip()[len(self.__prefix):])
353-
354-
if len(nciphertext) > 29 and nciphertext.startswith(b'\x01'):
355-
key = self.__derive_key(password, nciphertext[1:17])[1]
356-
return ChaCha20Poly1305(key).decrypt(nciphertext[17:29], nciphertext[29:], None).decode('utf-8')
357-
358-
except Exception:
359-
pass
360-
361-
raise JinjaFx.TemplateError('invalid vaulty password or ciphertext malformed')
362-
363-
raise JinjaFx.TemplateError('data not encrypted with vaulty')
364-

jinjafx.py

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,24 @@
2020
sys.exit('Requires Python >= 3.9')
2121

2222
import os, io, importlib.util, argparse, re, getpass, datetime, traceback, copy
23-
import jinja2, jinja2.sandbox, yaml, zoneinfo
23+
import jinja2, jinja2.sandbox, yaml, zoneinfo, base64
2424

2525
from cryptography.hazmat.primitives import hashes
2626
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
27+
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
2728
from cryptography.hazmat.primitives.hmac import HMAC
2829
from cryptography.hazmat.primitives.padding import PKCS7
2930
from cryptography.hazmat.primitives.ciphers import Cipher
3031
from cryptography.hazmat.primitives.ciphers.algorithms import AES
3132
from cryptography.hazmat.primitives.ciphers.modes import CTR
33+
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
34+
3235
from cryptography.exceptions import InvalidSignature
36+
from cryptography.exceptions import InvalidTag
3337

34-
__version__ = '1.23.0'
38+
__version__ = '1.23.1'
3539

36-
__all__ = ['JinjaFx', 'Vault']
40+
__all__ = ['JinjaFx', 'AnsibleVault', 'Vaulty']
3741

3842
def main():
3943
exc_source = None
@@ -105,7 +109,7 @@ def main():
105109
raise Exception('multiline stings not permitted')
106110

107111
__get_vault_credentials(vpw, True)
108-
vtext = Vault().encrypt(b_string, vpw[0])
112+
vtext = AnsibleVault().encrypt(b_string, vpw[0])
109113
print('!vault |\n' + re.sub(r'^', ' ' * 10, vtext, flags=re.MULTILINE))
110114

111115
else:
@@ -117,7 +121,7 @@ def main():
117121

118122
try:
119123
with open(f, 'rb') as fh:
120-
vtext = Vault().encrypt(fh.read(), vpw[0])
124+
vtext = AnsibleVault().encrypt(fh.read(), vpw[0])
121125

122126
with open(f, 'wb') as fh:
123127
fh.write(vtext.encode('utf-8'))
@@ -137,7 +141,7 @@ def main():
137141
if not args.decrypt:
138142
b_vtext = sys.stdin.buffer.read()
139143
__get_vault_credentials(vpw)
140-
print(Vault().decrypt(b_vtext, vpw[0]).decode('utf-8'))
144+
print(AnsibleVault().decrypt(b_vtext, vpw[0]).decode('utf-8'))
141145

142146
else:
143147
__get_vault_credentials(vpw)
@@ -148,7 +152,7 @@ def main():
148152

149153
try:
150154
with open(f, 'rb') as fh:
151-
plaintext = Vault().decrypt(fh.read(), vpw[0])
155+
plaintext = AnsibleVault().decrypt(fh.read(), vpw[0])
152156

153157
with open(f, 'wb') as fh:
154158
fh.write(plaintext)
@@ -387,7 +391,7 @@ def main():
387391
def __decrypt_vault(vpw, string):
388392
if string.lstrip().startswith('$ANSIBLE_VAULT;'):
389393
__get_vault_credentials(vpw)
390-
return Vault().decrypt(string.encode('utf-8'), vpw[0])
394+
return AnsibleVault().decrypt(string.encode('utf-8'), vpw[0])
391395
return string.encode('utf-8')
392396

393397

@@ -1324,7 +1328,7 @@ def __jfx_now(self, fmt=None, tz='UTC'):
13241328
return str(datetime.datetime.now(tz=zoneinfo.ZoneInfo(tz)))
13251329

13261330

1327-
class Vault():
1331+
class AnsibleVault():
13281332
def __derive_key(self, b_password, b_salt=None):
13291333
if b_salt is None:
13301334
b_salt = os.urandom(32)
@@ -1386,5 +1390,53 @@ def decrypt(self, b_string, password):
13861390
raise Exception('data isn\'t ansible vault encrypted')
13871391

13881392

1393+
class Vaulty():
1394+
def __init__(self):
1395+
self.__prefix = '$VAULTY;'
1396+
self.__kcache = {}
1397+
1398+
def __derive_key(self, password, salt=None):
1399+
if (ckey := (password, salt)) in self.__kcache:
1400+
e = self.__kcache[ckey]
1401+
self.__kcache[ckey] = e[0], e[1], e[2] + 1
1402+
return self.__kcache[ckey]
1403+
1404+
if salt is None:
1405+
salt = os.urandom(16)
1406+
1407+
key = Scrypt(salt, 32, 2**16, 8, 1).derive(password.encode('utf-8'))
1408+
self.__kcache[ckey] = [salt, key, 0]
1409+
return [salt, key, 0]
1410+
1411+
def encrypt(self, plaintext, password, cols=None):
1412+
version = b'\x01'
1413+
salt, key, uc = self.__derive_key(password)
1414+
nonce = os.urandom(12)[:-4] + uc.to_bytes(4, 'big')
1415+
ciphertext = ChaCha20Poly1305(key).encrypt(nonce, plaintext.encode('utf-8'), None)
1416+
1417+
r = self.__prefix + base64.b64encode(version + salt + nonce + ciphertext).decode('utf-8')
1418+
1419+
if cols is not None:
1420+
r = '\n'.join([r[i:i + cols] for i in range(0, len(r), cols)])
1421+
1422+
return r
1423+
1424+
def decrypt(self, ciphertext, password):
1425+
if ciphertext.lstrip().startswith(self.__prefix):
1426+
try:
1427+
nciphertext = base64.b64decode(ciphertext.strip()[len(self.__prefix):])
1428+
1429+
if len(nciphertext) > 29 and nciphertext.startswith(b'\x01'):
1430+
key = self.__derive_key(password, nciphertext[1:17])[1]
1431+
return ChaCha20Poly1305(key).decrypt(nciphertext[17:29], nciphertext[29:], None).decode('utf-8')
1432+
1433+
except Exception:
1434+
pass
1435+
1436+
raise JinjaFx.TemplateError('invalid vaulty password or ciphertext malformed')
1437+
1438+
raise JinjaFx.TemplateError('data not encrypted with vaulty')
1439+
1440+
13891441
if __name__ == '__main__':
13901442
main()

0 commit comments

Comments
 (0)