diff --git a/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java b/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java
index c395aa7b..8d7d4af8 100644
--- a/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java
+++ b/src/main/java/com/github/shyiko/mysql/binlog/BinaryLogClient.java
@@ -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;
@@ -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 */) {
@@ -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] + ")");
}
diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java b/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java
index c744d5a9..d81e2e1a 100644
--- a/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java
+++ b/src/main/java/com/github/shyiko/mysql/binlog/network/ClientCapabilities.java
@@ -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;
diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java
index f98eced0..25711af0 100644
--- a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java
+++ b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateNativePasswordCommand.java
@@ -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);
}
}
diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSHA2Command.java b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSHA2Command.java
new file mode 100644
index 00000000..c8afca7e
--- /dev/null
+++ b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSHA2Command.java
@@ -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 dingxiaobo
+ */
+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++;
+ }
+ }
+
+}
diff --git a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateCommand.java b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSecurityPasswordCommand.java
similarity index 93%
rename from src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateCommand.java
rename to src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSecurityPasswordCommand.java
index a045fe24..d99d6317 100644
--- a/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateCommand.java
+++ b/src/main/java/com/github/shyiko/mysql/binlog/network/protocol/command/AuthenticateSecurityPasswordCommand.java
@@ -25,7 +25,7 @@
/**
* @author Stanley Shyiko
*/
-public class AuthenticateCommand implements Command {
+public class AuthenticateSecurityPasswordCommand implements Command {
private String schema;
private String username;
@@ -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) {