Skip to content

Instantly share code, notes, and snippets.

@teramako
Last active January 28, 2024 09:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teramako/9d2d62aeb60b720e7e40acdbaac366ae to your computer and use it in GitHub Desktop.
Save teramako/9d2d62aeb60b720e7e40acdbaac366ae to your computer and use it in GitHub Desktop.
MySQL caching_sha2_password fast-path Test
import hashlib
import secrets
import sys
from termcolor import colored
USAGE = '''
UASAGE:
python3 thisScript.py arg1 arg2
arg1: Server password
arg2: Client password
See:
- Cache Operation for SHA-2 Pluggable Authentication
https://dev.mysql.com/doc/refman/8.0/en/caching-sha2-pluggable-authentication.html#caching-sha2-pluggable-authentication-cache-operation
- Generate_scramble::scramble()
https://github.com/mysql/mysql-server/blob/trunk/sql/auth/sha2_password_common.cc#L205-L263
- Validate_scramble::validate()
https://github.com/mysql/mysql-server/blob/trunk/sql/auth/sha2_password_common.cc#L316-L361
'''
def hex_dump(data: bytes) -> str:
return ''.join(format(c, "02x") for c in data)
def sha2(msg: bytes) -> bytes:
return hashlib.sha256(msg).digest()
def xor(x:bytes, y:bytes) -> bytes:
return bytes(a ^ b for a, b in zip(x, y))
class MyClient:
prefix = colored("Client", "magenta")
def __init__(self, password:str):
print("%s: Input Password : %s" % (self.prefix, password))
self.password = password.encode()
self._nonce = b''
def set_nonce(self, nonce:bytes):
print("%s: set nonce : %s" % (self.prefix, hex_dump(nonce)))
self._nonce = nonce
def get_scramble(self) -> bytes:
'''
Generate scramble data from password and nonce.
scramble = xor(sha2(client_password), sha2(sha2(client_password) + nonce))
'''
print("%s: Generate scramble data from inputed PASSWORD and NONCE" % self.prefix)
p1 = sha2(self.password)
result = xor(p1, sha2(sha2(p1) + self._nonce))
print("%s: Send Scrambled data: %s" % (self.prefix, colored(hex_dump(result), "yellow")))
return result
class MyServer:
prefix = colored("Server", "cyan")
def __init__(self, password:str):
self.cache = sha2(sha2(password.encode()))
self.nonce = b''
def generate_nonce(self) -> bytes:
self.nonce = secrets.token_bytes(8)
print("%s: Generate nonce : %s" % (self.prefix, hex_dump(self.nonce)))
return self.nonce
def validate(self, scramble:bytes) -> bool:
'''
scramble = xor(sha2(client_password), sha2(sha2(client_password) + nonce))
cashe = sha2(sha2(server_password))
result = sha2(xor(scramble, sha2(cache + nonce)))
= sha2(xor(xor(sha2(client_password), sha2(sha2(sha2(client_password)) + nonce)), sha2(cache + nonce)))
= sha2(xor(xor(sha2(client_password), sha2(sha2(sha2(client_password)) + nonce)), sha2(sha2(sha2(server_password)) + nonce)))
If client_password == server_password:
= sha2(xor(xor(sha2(server_password), sha2(sha2(sha2(server_password)) + nonce)), sha2(sha2(sha2(server_password)) + nonce)))
// ~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// xor(xor(A , B ), B ) = A
= sha2(sha2(server_password))
= cache
=> Validate OK
'''
if not self.cache:
print("%s: has not cache" % self.prefx)
return False
print("%s: Compare client's scrambled data and server's cache" % self.prefix)
result = sha2(xor(scramble, sha2(self.cache + self.nonce)))
print("%s: cache : %s" % (self.prefix, colored(hex_dump(self.cache), "green")))
res = (result == self.cache)
if res:
print("%s: calced result : %s" % (self.prefix, colored(hex_dump(result), "green")))
print("%s: %s" % (self.prefix, colored("Validate Success", "green")))
else:
print("%s: calced result : %s" % (self.prefix, colored(hex_dump(result), "red")))
print("%s: %s" % (self.prefix, colored("Validate Failed", "red")))
return res
def main():
header = '''
-----------------------------------------------------------------
MySQL caching_sha2_password fast-path Test
-----------------------------------------------------------------
'''.strip()
print(colored(header, "yellow"))
if len(sys.argv) < 3:
print(USAGE)
sys.exit(1)
server_password = sys.argv[1]
client_password = sys.argv[2]
my_server = MyServer(server_password)
my_client = MyClient(client_password)
# Server generate a random data(Nonce) and set to a client
my_client.set_nonce(my_server.generate_nonce())
if my_server.validate(my_client.get_scramble()):
sys.exit(0)
else:
sys.exit(1)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment