From b786c4c140434d26b0bacbd8d79bae1bf28b7ec5 Mon Sep 17 00:00:00 2001 From: Cyril Jouve Date: Thu, 31 Jan 2019 15:57:27 +0100 Subject: [PATCH] Replace the PyCrypto module with the cryptography module. Fixes #3272 --- Make.bat | 11 ------- docs/en_US/release_notes_4_2.rst | 1 + requirements.txt | 1 - web/pgadmin/utils/crypto.py | 53 +++++++++++++++++--------------- 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/Make.bat b/Make.bat index 8dd44a5c3..e916ba037 100644 --- a/Make.bat +++ b/Make.bat @@ -181,11 +181,6 @@ REM Main build sequence Ends ECHO Creating virtual environment... IF NOT EXIST "%PGBUILDPATH%" MKDIR "%PGBUILDPATH%" - REM If we're using VC++, and this is Python 3.6+, we need a hack for PyCrypto - IF "%MAKE%" == "nmake" ( - IF %PYTHON_VERSION% GEQ 36 SET CL=-FI"%VCINSTALLDIR%\INCLUDE\stdint.h" - ) - CD "%PGBUILDPATH%" "%PYTHON_HOME%\Scripts\virtualenv.exe" "%VIRTUALENV%" @@ -255,12 +250,6 @@ REM Main build sequence Ends ECHO Removing Sphinx CALL pip uninstall -y sphinx Pygments alabaster colorama docutils imagesize requests snowballstemmer - IF %PYTHON_MAJOR% == 3 ( - ECHO Fixing PyCrypto module for Python 3... - CALL "%PYTHON_HOME%\python" "%WD%\pkg\win32\replace.py" "-i" "%PGBUILDPATH%\%VIRTUALENV%\Lib\site-packages\Crypto\Random\OSRNG\nt.py" "-o" "%PGBUILDPATH%\%VIRTUALENV%\Lib\site-packages\Crypto\Random\OSRNG\nt.py.new" "-s" "import winrandom" -r "from . import winrandom" - MOVE /Y "%PGBUILDPATH%\%VIRTUALENV%\Lib\site-packages\Crypto\Random\OSRNG\nt.py.new" "%PGBUILDPATH%\%VIRTUALENV%\Lib\site-packages\Crypto\Random\OSRNG\nt.py" - ) - ECHO Assembling runtime environment... CD "%WD%\runtime" diff --git a/docs/en_US/release_notes_4_2.rst b/docs/en_US/release_notes_4_2.rst index adb4576ad..ff51e420c 100644 --- a/docs/en_US/release_notes_4_2.rst +++ b/docs/en_US/release_notes_4_2.rst @@ -14,6 +14,7 @@ Features Bug fixes ********* +| `Bug #3272 `_ - Replace the PyCrypto module with the cryptography module. | `Bug #3453 `_ - Fixed SQL for foreign table options. | `Bug #3475 `_ - Fixed execution time to show Hours part for long running queries in Query Tool. | `Bug #3608 `_ - Messages tab of query tool should be clear on subsequent execution of table/view using View/Edit Data. diff --git a/requirements.txt b/requirements.txt index e4232191f..0140ab4da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,7 +18,6 @@ html5lib==1.0.1 linecache2==1.0.0 passlib==1.7.1 pbr==3.1.1 -pycrypto>=2.6.1 pyrsistent==0.14.2 python-mimeparse==1.6.0 pytz==2018.3 diff --git a/web/pgadmin/utils/crypto.py b/web/pgadmin/utils/crypto.py index 2538c71b6..3dc218951 100644 --- a/web/pgadmin/utils/crypto.py +++ b/web/pgadmin/utils/crypto.py @@ -9,13 +9,21 @@ """This File Provides Cryptography.""" +from __future__ import division + import base64 import hashlib +import os -from Crypto import Random -from Crypto.Cipher import AES +import six + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.ciphers.modes import CFB8 padding_string = b'}' +iv_size = AES.block_size // 8 def encrypt(plaintext, key): @@ -27,15 +35,17 @@ def encrypt(plaintext, key): key -- Key for encryption. """ - iv = Random.new().read(AES.block_size) - cipher = AES.new(pad(key), AES.MODE_CFB, iv) + iv = os.urandom(iv_size) + cipher = Cipher(AES(pad(key)), CFB8(iv), default_backend()) + encryptor = cipher.encryptor() + # If user has entered non ascii password (Python2) # we have to encode it first - if hasattr(str, 'decode'): - plaintext = plaintext.encode('utf-8') - encrypted = base64.b64encode(iv + cipher.encrypt(plaintext)) + if isinstance(plaintext, six.text_type): + plaintext = plaintext.encode() - return encrypted + return base64.b64encode(iv + encryptor.update(plaintext) + + encryptor.finalize()) def decrypt(ciphertext, key): @@ -47,36 +57,29 @@ def decrypt(ciphertext, key): key -- key to decrypt the encrypted string. """ - global padding_string - ciphertext = base64.b64decode(ciphertext) - iv = ciphertext[:AES.block_size] - cipher = AES.new(pad(key), AES.MODE_CFB, iv) - decrypted = cipher.decrypt(ciphertext[AES.block_size:]) + iv = ciphertext[:iv_size] - return decrypted + cipher = Cipher(AES(pad(key)), CFB8(iv), default_backend()) + decryptor = cipher.decryptor() + return decryptor.update(ciphertext[iv_size:]) + decryptor.finalize() def pad(key): """Add padding to the key.""" - global padding_string - str_len = len(key) + if isinstance(key, six.text_type): + key = key.encode() # Key must be maximum 32 bytes long, so take first 32 bytes - if str_len > 32: - return key[:32] + key = key[:32] - # If key size id 16, 24 or 32 bytes then padding not require - if str_len == 16 or str_len == 24 or str_len == 32: + # If key size is 16, 24 or 32 bytes then padding is not required + if len(key) in (16, 24, 32): return key - # Convert bytes to string (python3) - if not hasattr(str, 'decode'): - padding_string = padding_string.decode() - # Add padding to make key 32 bytes long - return key + ((32 - str_len % 32) * padding_string) + return key.ljust(32, padding_string) def pqencryptpassword(password, user):