Skip to content

support "caching_sha2_password" (mysql 8.0 default) #299

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@
import com.github.shyiko.mysql.binlog.network.protocol.Packet;
import com.github.shyiko.mysql.binlog.network.protocol.PacketChannel;
import com.github.shyiko.mysql.binlog.network.protocol.ResultSetRowPacket;
import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateNativePasswordCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateSHA2Command;
import com.github.shyiko.mysql.binlog.network.protocol.command.AuthenticateSecurityPasswordCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.Command;
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogCommand;
import com.github.shyiko.mysql.binlog.network.protocol.command.DumpBinaryLogGtidCommand;
Expand Down Expand Up @@ -715,9 +716,11 @@ private void authenticate(GreetingPacket greetingPacket) throws IOException {
usingSSLSocket = true;
}
}
AuthenticateCommand authenticateCommand = new AuthenticateCommand(schema, username, password,
greetingPacket.getScramble());
authenticateCommand.setCollation(collation);

Command authenticateCommand = "caching_sha2_password".equals(greetingPacket.getPluginProvidedData()) ?
new AuthenticateSHA2Command(schema, username, password, greetingPacket.getScramble(), collation) :
new AuthenticateSecurityPasswordCommand(schema, username, password, greetingPacket.getScramble(), collation);

channel.write(authenticateCommand, packetNumber);
byte[] authenticationResult = channel.read();
if (authenticationResult[0] != (byte) 0x00 /* ok */) {
Expand All @@ -728,6 +731,14 @@ private void authenticate(GreetingPacket greetingPacket) throws IOException {
errorPacket.getSqlState());
} else if (authenticationResult[0] == (byte) 0xFE) {
switchAuthentication(authenticationResult, usingSSLSocket);
} else if (authenticationResult[0] == (byte) 0x01) {
if (authenticationResult.length >= 2 && (authenticationResult[1] == 3) || (authenticationResult[1] == 4)) {
// 8.0 auth ok
byte[] authenticationResultSha2 = channel.read();
logger.log(Level.FINEST, "SHA2 auth result {0}", authenticationResultSha2);
} else {
throw new AuthenticationException("Unexpected authentication result (" + authenticationResult[0] + "&" + authenticationResult[1] + ")");
}
} else {
throw new AuthenticationException("Unexpected authentication result (" + authenticationResult[0] + ")");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public final class ClientCapabilities {
public static final int MULTI_RESULTS = 1 << 17; /* enable/disable multi-results */
public static final int PS_MULTI_RESULTS = 1 << 18; /* multi-results in ps-protocol */
public static final int PLUGIN_AUTH = 1 << 19; /* client supports plugin authentication */
public static final int PLUGIN_AUTH_LENENC_CLIENT_DATA = 1 << 21;
public static final int SSL_VERIFY_SERVER_CERT = 1 << 30;
public static final int REMEMBER_OPTIONS = 1 << 31;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ public AuthenticateNativePasswordCommand(String scramble, String password) {
}
@Override
public byte[] toByteArray() throws IOException {
return AuthenticateCommand.passwordCompatibleWithMySQL411(password, scramble);
return AuthenticateSecurityPasswordCommand.passwordCompatibleWithMySQL411(password, scramble);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2018 dingxiaobo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.shyiko.mysql.binlog.network.protocol.command;

import com.github.shyiko.mysql.binlog.io.ByteArrayOutputStream;
import com.github.shyiko.mysql.binlog.network.ClientCapabilities;

import java.io.IOException;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
* @author <a href="mailto:[email protected]">dingxiaobo</a>
*/
public class AuthenticateSHA2Command implements Command {

private String schema;
private String username;
private String password;
private String salt;
private int clientCapabilities;
private int collation;

public AuthenticateSHA2Command(String schema, String username, String password, String salt, int collation) {
this.schema = schema;
this.username = username;
this.password = password;
this.salt = salt;
this.collation = collation;
}

public void setClientCapabilities(int clientCapabilities) {
this.clientCapabilities = clientCapabilities;
}

@Override
public byte[] toByteArray() throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int clientCapabilities = this.clientCapabilities;
if (clientCapabilities == 0) {
clientCapabilities |= ClientCapabilities.LONG_FLAG;
clientCapabilities |= ClientCapabilities.PROTOCOL_41;
clientCapabilities |= ClientCapabilities.SECURE_CONNECTION;
clientCapabilities |= ClientCapabilities.PLUGIN_AUTH;
clientCapabilities |= ClientCapabilities.PLUGIN_AUTH_LENENC_CLIENT_DATA;

if (schema != null) {
clientCapabilities |= ClientCapabilities.CONNECT_WITH_DB;
}
}
buffer.writeInteger(clientCapabilities, 4);
buffer.writeInteger(0, 4); // maximum packet length
buffer.writeInteger(collation, 1);
for (int i = 0; i < 23; i++) {
buffer.write(0);
}
buffer.writeZeroTerminatedString(username);
byte[] passwordSHA1 = encodePassword();
buffer.writeInteger(passwordSHA1.length, 1);
buffer.write(passwordSHA1);
if (schema != null) {
buffer.writeZeroTerminatedString(schema);
}
buffer.writeZeroTerminatedString("caching_sha2_password");

return buffer.toByteArray();
}

private byte[] encodePassword() {
if (password == null || "".equals(password)) {
return new byte[0];
}
// caching_sha2_password
/*
* Server does it in 4 steps (see sql/auth/sha2_password_common.cc Generate_scramble::scramble method):
*
* SHA2(src) => digest_stage1
* SHA2(digest_stage1) => digest_stage2
* SHA2(digest_stage2, m_rnd) => scramble_stage1
* XOR(digest_stage1, scramble_stage1) => scramble
*/
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");

int CACHING_SHA2_DIGEST_LENGTH = 32;
byte[] dig1 = new byte[CACHING_SHA2_DIGEST_LENGTH];
byte[] dig2 = new byte[CACHING_SHA2_DIGEST_LENGTH];
byte[] scramble1 = new byte[CACHING_SHA2_DIGEST_LENGTH];

// SHA2(src) => digest_stage1
md.update(password.getBytes(), 0, password.getBytes().length);
md.digest(dig1, 0, CACHING_SHA2_DIGEST_LENGTH);
md.reset();

// SHA2(digest_stage1) => digest_stage2
md.update(dig1, 0, dig1.length);
md.digest(dig2, 0, CACHING_SHA2_DIGEST_LENGTH);
md.reset();

// SHA2(digest_stage2, m_rnd) => scramble_stage1
md.update(dig2, 0, dig1.length);
md.update(salt.getBytes(), 0, salt.getBytes().length);
md.digest(scramble1, 0, CACHING_SHA2_DIGEST_LENGTH);

// XOR(digest_stage1, scramble_stage1) => scramble
byte[] mysqlScrambleBuff = new byte[CACHING_SHA2_DIGEST_LENGTH];
xor(dig1, mysqlScrambleBuff, scramble1, CACHING_SHA2_DIGEST_LENGTH);

return mysqlScrambleBuff;
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
} catch (DigestException e) {
throw new RuntimeException(e);
}
}

private void xor(byte[] from, byte[] to, byte[] scramble, int length) {
int pos = 0;
int scrambleLength = scramble.length;

while (pos < length) {
to[pos] = (byte) (from[pos] ^ scramble[pos % scrambleLength]);
pos++;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
/**
* @author <a href="mailto:[email protected]">Stanley Shyiko</a>
*/
public class AuthenticateCommand implements Command {
public class AuthenticateSecurityPasswordCommand implements Command {

private String schema;
private String username;
Expand All @@ -34,11 +34,12 @@ public class AuthenticateCommand implements Command {
private int clientCapabilities;
private int collation;

public AuthenticateCommand(String schema, String username, String password, String salt) {
public AuthenticateSecurityPasswordCommand(String schema, String username, String password, String salt, int collation) {
this.schema = schema;
this.username = username;
this.password = password;
this.salt = salt;
this.collation = collation;
}

public void setClientCapabilities(int clientCapabilities) {
Expand Down