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