Skip to content

Commit dbe0d97

Browse files
bygaoyuanzoemak
authored andcommitted
增加对mysql的caching_sha2_password认证插件fullauth流程支持 (alibaba#4767)
1 parent 11fdf5a commit dbe0d97

File tree

2 files changed

+104
-29
lines changed

2 files changed

+104
-29
lines changed

driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
/**
2828
* 基于mysql socket协议的链接实现
29-
*
29+
*
3030
* @author jianghang 2013-2-18 下午09:22:30
3131
* @version 1.0.1
3232
*/
@@ -220,55 +220,45 @@ private void negotiate(SocketChannel channel) throws IOException {
220220
packet.fromBytes(body);
221221
authData = packet.authData;
222222
pluginName = packet.authName;
223+
logger.info("auth switch pluginName is {}.", pluginName);
223224
}
224225

225-
boolean isSha2Password = false;
226226
byte[] encryptedPassword = null;
227227
if ("mysql_clear_password".equals(pluginName)) {
228228
encryptedPassword = getPassword().getBytes();
229+
header = authSwitchAfterAuth(encryptedPassword, header);
230+
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
229231
} else if ("mysql_native_password".equals(pluginName)) {
230232
try {
231233
encryptedPassword = MySQLPasswordEncrypter.scramble411(getPassword().getBytes(), authData);
232234
} catch (NoSuchAlgorithmException e) {
233235
throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
234236
}
237+
header = authSwitchAfterAuth(encryptedPassword, header);
238+
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
235239
} else if ("caching_sha2_password".equals(pluginName)) {
236-
isSha2Password = true;
240+
byte[] scramble = authData;
237241
try {
238-
encryptedPassword = MySQLPasswordEncrypter.scrambleCachingSha2(getPassword().getBytes(), authData);
242+
encryptedPassword = MySQLPasswordEncrypter.scrambleCachingSha2(getPassword().getBytes(), scramble);
239243
} catch (DigestException e) {
240244
throw new RuntimeException("can't encrypt password that will be sent to MySQL server.", e);
241245
}
242-
}
243-
assert encryptedPassword != null;
244-
AuthSwitchResponsePacket responsePacket = new AuthSwitchResponsePacket();
245-
responsePacket.authData = encryptedPassword;
246-
byte[] auth = responsePacket.toBytes();
247-
248-
h = new HeaderPacket();
249-
h.setPacketBodyLength(auth.length);
250-
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
251-
PacketManager.writePkg(channel, h.toBytes(), auth);
252-
logger.info("auth switch response packet is sent out.");
253-
254-
header = null;
255-
header = PacketManager.readHeader(channel, 4);
256-
body = null;
257-
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
258-
assert body != null;
259-
if (isSha2Password) {
246+
header = authSwitchAfterAuth(encryptedPassword, header);
247+
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
248+
assert body != null;
260249
if (body[0] == 0x01 && body[1] == 0x04) {
261-
// password auth failed
262-
throw new IOException("caching_sha2_password Auth failed");
250+
header = cachingSha2PasswordFullAuth(channel, header, getPassword().getBytes(), scramble);
251+
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
252+
} else {
253+
header = PacketManager.readHeader(channel, 4);
254+
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
263255
}
264-
265-
header = null;
266-
header = PacketManager.readHeader(channel, 4);
267-
body = null;
256+
} else {
257+
header = authSwitchAfterAuth(encryptedPassword, header);
268258
body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
269259
}
270260
}
271-
261+
assert body != null;
272262
if (body[0] < 0) {
273263
if (body[0] == -1) {
274264
ErrorPacket err = new ErrorPacket();
@@ -280,6 +270,62 @@ private void negotiate(SocketChannel channel) throws IOException {
280270
}
281271
}
282272

273+
private HeaderPacket cachingSha2PasswordFullAuth(SocketChannel channel, HeaderPacket header, byte[] pass,
274+
byte[] seed) throws IOException {
275+
AuthSwitchResponsePacket responsePacket = new AuthSwitchResponsePacket();
276+
responsePacket.authData = new byte[] { 2 };
277+
byte[] auth = responsePacket.toBytes();
278+
HeaderPacket h = new HeaderPacket();
279+
h.setPacketBodyLength(auth.length);
280+
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
281+
PacketManager.writePkg(channel, h.toBytes(), auth);
282+
logger.info("caching sha2 password fullAuth request public key packet is sent out.");
283+
284+
header = PacketManager.readHeader(channel, 4);
285+
byte[] body = PacketManager.readBytes(channel, header.getPacketBodyLength(), timeout);
286+
AuthSwitchRequestMoreData packet = new AuthSwitchRequestMoreData();
287+
packet.fromBytes(body);
288+
if (packet.status != 0x01) {
289+
throw new IOException("caching_sha2_password get public key failed");
290+
}
291+
logger.info("caching sha2 password fullAuth get server public key succeed.");
292+
byte[] publicKeyBytes = packet.authData;
293+
294+
byte[] encryptedPassword = null;
295+
try {
296+
encryptedPassword = MySQLPasswordEncrypter.scrambleRsa(publicKeyBytes, pass, seed);
297+
} catch (Exception e) {
298+
logger.error("rsa encrypt failed {}", publicKeyBytes);
299+
throw new IOException("caching_sha2_password auth failed", e);
300+
}
301+
302+
// send auth
303+
responsePacket = new AuthSwitchResponsePacket();
304+
responsePacket.authData = encryptedPassword;
305+
auth = responsePacket.toBytes();
306+
h = new HeaderPacket();
307+
h.setPacketBodyLength(auth.length);
308+
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
309+
PacketManager.writePkg(channel, h.toBytes(), auth);
310+
logger.info("caching sha2 password fullAuth response auth data packet is sent out.");
311+
return PacketManager.readHeader(channel, 4);
312+
}
313+
314+
private HeaderPacket authSwitchAfterAuth(byte[] encryptedPassword, HeaderPacket header) throws IOException {
315+
assert encryptedPassword != null;
316+
AuthSwitchResponsePacket responsePacket = new AuthSwitchResponsePacket();
317+
responsePacket.authData = encryptedPassword;
318+
byte[] auth = responsePacket.toBytes();
319+
320+
HeaderPacket h = new HeaderPacket();
321+
h.setPacketBodyLength(auth.length);
322+
h.setPacketSequenceNumber((byte) (header.getPacketSequenceNumber() + 1));
323+
PacketManager.writePkg(channel, h.toBytes(), auth);
324+
logger.info("auth switch response packet is sent out.");
325+
header = PacketManager.readHeader(channel, 4);
326+
return header;
327+
}
328+
283329
private void auth323(SocketChannel channel, byte packetSequenceNumber, byte[] seed) throws IOException {
284330
// auth 323
285331
Reply323Packet r323 = new Reply323Packet();

driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/utils/MySQLPasswordEncrypter.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
package com.alibaba.otter.canal.parse.driver.mysql.utils;
22

33
import java.security.DigestException;
4+
import java.security.InvalidKeyException;
5+
import java.security.KeyFactory;
46
import java.security.MessageDigest;
57
import java.security.NoSuchAlgorithmException;
8+
import java.security.PublicKey;
9+
import java.security.spec.InvalidKeySpecException;
10+
import java.security.spec.X509EncodedKeySpec;
11+
import javax.crypto.BadPaddingException;
12+
import javax.crypto.Cipher;
13+
import javax.crypto.IllegalBlockSizeException;
14+
import javax.crypto.NoSuchPaddingException;
615

716
public class MySQLPasswordEncrypter {
817

@@ -85,6 +94,26 @@ public static String scramble323(String pass, String seed) {
8594
return new String(chars);
8695
}
8796

97+
public static final byte[] scrambleRsa(byte[] publicKeyBytes, byte[] pass,
98+
byte[] seed) throws NoSuchAlgorithmException, InvalidKeySpecException,
99+
NoSuchPaddingException, InvalidKeyException,
100+
IllegalBlockSizeException, BadPaddingException {
101+
byte[] input = new byte[pass.length + 1];
102+
System.arraycopy(pass, 0, input, 0, pass.length);
103+
byte[] encryptedPassword = new byte[input.length];
104+
xorString(input, encryptedPassword, seed, input.length);
105+
String publicKeyPem = new String(publicKeyBytes).replace("\n", "")
106+
.replace("-----BEGIN PUBLIC KEY-----", "")
107+
.replace("-----END PUBLIC KEY-----", "");
108+
byte[] certificateData = java.util.Base64.getDecoder().decode(publicKeyPem.getBytes());
109+
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(certificateData);
110+
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
111+
PublicKey publicKey = keyFactory.generatePublic(keySpec);
112+
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
113+
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
114+
return cipher.doFinal(encryptedPassword);
115+
}
116+
88117
private static long[] hash(String src) {
89118
long nr = 1345345333L;
90119
long add = 7;

0 commit comments

Comments
 (0)