2222
2323"""Utilities dealing with encryption and randomness."""
2424
25- import binascii
26- import random
25+ import secrets
2726from string import ascii_lowercase
2827
2928import bcrypt
30- from Cryptodome import Random
31- from Cryptodome .Cipher import AES
32-
33- from cmscommon .binary import bin_to_hex , hex_to_bin , bin_to_b64 , b64_to_bin
3429
3530
3631__all__ = [
3732 "get_random_key" , "get_hex_random_key" ,
3833
39- "encrypt_binary" , "decrypt_binary" ,
40- "encrypt_number" , "decrypt_number" ,
41-
4234 "generate_random_password" ,
4335
4436 "validate_password" , "build_password" , "hash_password" ,
4537 "parse_authentication" ,
4638 ]
4739
4840
49- _RANDOM = Random .new ()
50-
5141# bcrypt difficulty parameter. This is here so that it can be set to a lower
5242# value when running unit tests. It seems that the lowest accepted value is 4.
5343BCRYPT_ROUNDS = 12
@@ -56,96 +46,15 @@ def get_random_key() -> bytes:
5646 """Generate 16 random bytes, safe to be used as AES key.
5747
5848 """
59- return _RANDOM . read (16 )
49+ return secrets . token_bytes (16 )
6050
6151
6252def get_hex_random_key () -> str :
6353 """Generate 16 random bytes, safe to be used as AES key.
6454 Return it encoded in hexadecimal.
6555
6656 """
67- return bin_to_hex (get_random_key ())
68-
69-
70- def encrypt_binary (pt : bytes , key_hex : str ) -> str :
71- """Encrypt the plaintext with the 16-bytes key.
72-
73- A random salt is added to avoid having the same input being
74- encrypted to the same output.
75-
76- pt: the "plaintext" to encode.
77- key_hex: a 16-bytes key in hex (a string of 32 hex chars).
78-
79- return: pt encrypted using the key, in a format URL-safe
80- (more precisely, base64-encoded with alphabet "a-zA-Z0-9.-_").
81-
82- """
83- key = hex_to_bin (key_hex )
84- # Pad the plaintext to make its length become a multiple of the block size
85- # (that is, for AES, 16 bytes), using a byte 0x01 followed by as many bytes
86- # 0x00 as needed. If the length of the message is already a multiple of 16
87- # bytes, add a new block.
88- pt_pad = pt + b'\01 ' + b'\00 ' * (16 - (len (pt ) + 1 ) % 16 )
89- # The IV is a random block used to differentiate messages encrypted with
90- # the same key. An IV should never be used more than once in the lifetime
91- # of the key. In this way encrypting the same plaintext twice will produce
92- # different ciphertexts.
93- iv = get_random_key ()
94- # Initialize the AES cipher with the given key and IV.
95- aes = AES .new (key , AES .MODE_CBC , iv )
96- ct = aes .encrypt (pt_pad )
97- # Convert the ciphertext in a URL-safe base64 encoding
98- ct_b64 = bin_to_b64 (iv + ct )\
99- .replace ('+' , '-' ).replace ('/' , '_' ).replace ('=' , '.' )
100- return ct_b64
101-
102-
103- def decrypt_binary (ct_b64 : str , key_hex : str ) -> bytes :
104- """Decrypt a ciphertext generated by encrypt_binary.
105-
106- ct_b64: the ciphertext as produced by encrypt_binary.
107- key_hex: the 16-bytes key in hex format used to encrypt.
108-
109- return: the plaintext.
110-
111- raise (ValueError): if the ciphertext is invalid.
112-
113- """
114- key = hex_to_bin (key_hex )
115- try :
116- # Convert the ciphertext from a URL-safe base64 encoding to a
117- # bytestring, which contains both the IV (the first 16 bytes) as well
118- # as the encrypted padded plaintext.
119- iv_ct = b64_to_bin (
120- ct_b64 .replace ('-' , '+' ).replace ('_' , '/' ).replace ('.' , '=' ))
121- aes = AES .new (key , AES .MODE_CBC , iv_ct [:16 ])
122- # Get the padded plaintext.
123- pt_pad = aes .decrypt (iv_ct [16 :])
124- # Remove the padding.
125- # TODO check that the padding is correct, i.e. that it contains at most
126- # 15 bytes 0x00 preceded by a byte 0x01.
127- pt = pt_pad .rstrip (b'\x00 ' )[:- 1 ]
128- return pt
129- except (TypeError , binascii .Error ):
130- raise ValueError ('Could not decode from base64.' )
131- except ValueError :
132- raise ValueError ('Wrong AES cryptogram length.' )
133-
134-
135- def encrypt_number (num : int , key_hex : str ) -> str :
136- """Encrypt an integer number, with the same properties as
137- encrypt_binary().
138-
139- """
140- hexnum = b"%x" % num
141- return encrypt_binary (hexnum , key_hex )
142-
143-
144- def decrypt_number (enc : str , key_hex : str ) -> int :
145- """Decrypt an integer number encrypted with encrypt_number().
146-
147- """
148- return int (decrypt_binary (enc , key_hex ), 16 )
57+ return get_random_key ().hex ()
14958
15059
15160def generate_random_password () -> str :
@@ -154,7 +63,7 @@ def generate_random_password() -> str:
15463 return: a random string.
15564
15665 """
157- return "" .join ((random .choice (ascii_lowercase ) for _ in range (6 )))
66+ return "" .join ((secrets .choice (ascii_lowercase ) for _ in range (6 )))
15867
15968
16069def parse_authentication (authentication : str ) -> tuple [str , str ]:
0 commit comments