Skip to content

Add support for SHA256 auth plugin #583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed

Add support for SHA256 auth plugin #583

wants to merge 3 commits into from

Conversation

elemount
Copy link
Contributor

@elemount elemount commented Jun 23, 2017

For issue #532
SHA256 authentication plugin is the recommended auth way for the MySQL now, for security reason.
This covers all the SHA256 auth code pass.

  1. mysqld with default_auth_plugin=sha256_password, and use SSL, server say sha256, client and server get SSL, and then client response sha256 password directly.
  2. mysqld with default_auth_plugin=sha256_password, and do not use SSL, server say sha256, client response sha256, server (1) auth switch (2) tell the RSA public key directly, then client response the encrypted password.
  3. mysqld with default_auth_plugin=mysql_native_password(By default), and use SSL, server say native, client and server get SSL, and then client response native, and server say AuthSwith, the client reponse the password directly.
  4. mysqld with default_auth_plugin=mysql_native_password(By default), and do not use SSL, server say native, client say native, server say AuthSwith, and the client get RSA(already have or request '1' then server response public key) and response the encrypted password.

I test code in both 4 scenario. But can not add a proper unit test for it now. May need some help.

@methane
Copy link
Member

methane commented Jun 23, 2017

I don't want to add dependency

@elemount
Copy link
Contributor Author

@methane I use

try:
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import serialization, hashes
    from cryptography.hazmat.primitives.asymmetric import padding
    HAVE_CRYPTOGRAPHY = True
except ImportError:
    HAVE_CRYPTOGRAPHY = False

to make sure this module is not required.

@methane
Copy link
Member

methane commented Jun 27, 2017

Can you use hashlib's sha256?

@elemount
Copy link
Contributor Author

Sure, let me check this lib.

@elemount
Copy link
Contributor Author

elemount commented Jun 28, 2017

@methane , I'm afraid of that I can not use hashlib instead of cryptography.
The sha256_password auth method is not a hash algorithm to encrypt secret, it actually use RSA to encrypted password, in detail, it need to use RSA_PKCS1_OAEP_PADDING algorithm to encrypt the password.
In hashlib lib, its all available algorithm is

In [2]: hashlib.algorithms_available
Out[2]: 
{'DSA',
 'DSA-SHA',
 'MD4',
 'MD5',
 'RIPEMD160',
 'SHA',
 'SHA1',
 'SHA224',
 'SHA256',
 'SHA384',
 'SHA512',
 'dsaEncryption',
 'dsaWithSHA',
 'ecdsa-with-SHA1',
 'md4',
 'md5',
 'ripemd160',
 'sha',
 'sha1',
 'sha224',
 'sha256',
 'sha384',
 'sha512',
 'whirlpool'}

which do not have any algorithm which is related with RSA.
If you do not like cryptography, I can use PyCrypto instead of cryptography, or any other lib which you propose.

@methane
Copy link
Member

methane commented Jun 29, 2017

If you do not like cryptography, I can use PyCrypto instead of cryptography, or any other lib which you propose.
View changes

I don't dislike cryptography. I just don't want any dependency.

@methane
Copy link
Member

methane commented Jun 29, 2017

As far as I read the manual, RSA is not required when tls is used.
And binary distribution of MySQL Community (GPL) Edition doesn't support RSA.

@methane
Copy link
Member

methane commented Jun 29, 2017

And I don't want to add any new code to connections.py. It's toooo large already.
Please move your code to pymysql/auth/sha256password.py.

@elemount
Copy link
Contributor Author

@methane You are right, RSA works when the connection is not using SSL. And only built with OpenSSL but not YaSSL(which is default), this mechanism will works. I'll make sure that when using SSL, the exception will not thrown, and when the MySQL server communicate with RSA, it will throw the module needed, or you have any other suggestion?

For the connection.py code is too heavy, sure, let me move it to pymysql/auth/sha256password.py.


def _sha256_rsa_crypt(password, salt, public_key):
if not HAVE_CRYPTOGRAPHY:
raise NotImplementedError("cryptography module not found")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use OperationalError instead of NotImplementedError.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

def _xor_password(password, salt):
password_bytes = bytearray(password, 'ascii')
for i in range(len(password_bytes)):
password_bytes[i] ^= ord(salt[i % len(salt)])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write salt_len = len(salt) before loop and use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

password_bytes = bytearray(password, 'ascii')
for i in range(len(password_bytes)):
password_bytes[i] ^= ord(salt[i % len(salt)])
return password_bytes.decode('ascii')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may cause UnicodeDecodeError. Keep bytearray type as-is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

@@ -374,6 +396,9 @@ def is_auth_switch_request(self):
# http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest
return self._data[0:1] == b'\xfe'

def is_rsa_public_key(self):
return self._data[0:1] == b'\x01'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is \x01 really RSA_PUBLIC_KEY packet?
Isn't it a more general purpose packet which has other semantics in other context?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is more general purpose packet, but I can not find a proper name, I'll fix it by the next commit for a proper packet name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the real meaning of \x01, it means Extra Authentication Data.

@methane
Copy link
Member

methane commented Jun 29, 2017

Since Oracle removes server-client protocol document, it's hard to review protocol...
Why Oracle remove such an very important document....

@elemount
Copy link
Contributor Author

@methane
Copy link
Member

methane commented Jun 29, 2017

I know webarchive, but all images (including important sequence diagram) gone.

@elemount
Copy link
Contributor Author

elemount commented Jul 4, 2017

@methane Could you help to review it again?

@elemount
Copy link
Contributor Author

Hi @methane ,I squashed this PR and fixed up the tests. Could you help to review it?

@sscherfke
Copy link

sscherfke commented Nov 9, 2017

You can use extras_require in setup.py to declare cryptography as optional dependency, e.g.,

setup(
    name="PyMySQL",
    # ...
    extras_require={
        'auth_sha256': ['cryptography>=2.1'],
    },
    classifiers=[
        # ...
    ],
)

Users can then pip install pymysql w/o dependenies and pip install pymysql[auth_sha256] with dependencies.

@elemount elemount closed this Nov 21, 2017
@methane
Copy link
Member

methane commented Mar 19, 2018

@elemount Why did you close this? May I continue your work?

@elemount
Copy link
Contributor Author

elemount commented Apr 12, 2018

I closed it because I abandoned using SHA256 in my MySQL server.
You can continue my work with current code. I believe these code can work.

@elemount elemount reopened this Apr 12, 2018
@grooverdan
Copy link
Contributor

@elemount, thanks for your work. I've started to look at this. Overall looks pretty good and I've only added a couple of Py3 compatibility fixes so far.
server_public_key looks like raw bytes read from the server but a filename serialization.load_pem_public_key when passed from to the connection. I haven't thought yet how to resolve this.

Hope the auth plugin interface wasn't too bad/buggy.

WIP:

grooverdan/PyMySQL@travis-docker-mysql-8.0...grooverdan:sha256_auth_plugin

Next steps:
reenable existing plugin tests (on tcp, unix_socket tests appear disabled)
write more tests (thankfully MySQL-8.0 seems to generate and configure client/server ssl keys as well).
implement the caching mechanism and check all the paths are implemented: https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html

@methane
Copy link
Member

methane commented Apr 30, 2018

FYI, I'll release a few bugfix (0.8.x) in May.
Large changes including this should be merged after that. (Maybe, June)

@methane methane closed this Jun 8, 2018
@zzzeek zzzeek mentioned this pull request Mar 14, 2019
4 tasks
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 1, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants