QQ机器人WebHook签名验签JAVA版
- 官方文档中仅提供了GO,PYTHON和NODE的SDK,并且提供的示例也仅有GO版本的,特此为JAVA做一版。
复制代码 前期准备
- QQ开放平台已创建机器人获取对应的机器人ID和密钥等资料信息。
复制代码 maven引用:- <dependency>
- <groupId>org.bouncycastle</groupId>
- bcprov-jdk15on</artifactId>
- <version>1.70</version>
- </dependency>
复制代码 签名工具类
签名类:- import lombok.extern.slf4j.Slf4j;
- import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
- import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
- import org.bouncycastle.crypto.signers.Ed25519Signer;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.nio.charset.StandardCharsets;
- import java.util.Arrays;
- /**
- * @author chen
- * @version 1.0
- * @description: TODO 回调签名加解密帮助类
- * @date 14 4月 2025 14:14
- */
- @Slf4j
- public class CallBackSignUtil {
- private static final int ED25519_SEED_SIZE = 32;
- /**
- * @description: TODO 验证签名是否对应
- * @author chen
- * @date: 15 4月 2025 14:03
- */
- public static boolean verifySignature(String appSecret, String xSignatureEd25519, String xSignatureTimestamp, String reqBody) throws IOException {
- byte[] seed = expandSeed(appSecret.getBytes(StandardCharsets.UTF_8));
- // 用 seed 构造 Ed25519 私钥
- Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(seed, 0);
- // 从私钥推导出公钥
- Ed25519PublicKeyParameters publicKey = privateKey.generatePublicKey();
- byte[] signature = hexStringToByteArray(xSignatureEd25519);
- if (signature.length != 64 || (signature[63] & 0xE0) != 0) {
- return false;
- }
- ByteArrayOutputStream msg = new ByteArrayOutputStream();
- msg.write(xSignatureTimestamp.getBytes());
- msg.write(reqBody.getBytes());
- byte[] msgBytes = msg.toByteArray();
- Ed25519Signer signer = new Ed25519Signer();
- signer.init(false, publicKey);
- signer.update(msgBytes, 0, msgBytes.length);
- return signer.verifySignature(signature);
- }
- /**
- * @description: TODO 生成秘钥
- * @author chen
- * @date: 15 4月 2025 13:50
- */
- public static String generateResponse(String botSecret, String eventTs, String plainToken) throws Exception {
-
- byte[] seed = expandSeed(botSecret.getBytes(StandardCharsets.UTF_8));
- Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(seed, 0);
- // 生成Ed25519密钥对
- ByteArrayOutputStream msg = new ByteArrayOutputStream();
- msg.write(eventTs.getBytes());
- msg.write(plainToken.getBytes());
- byte[] msgBytes = msg.toByteArray();
- Ed25519Signer signer = new Ed25519Signer();
- signer.init(true, privateKey);
- signer.update(msgBytes, 0, msgBytes.length);
- byte[] signature = signer.generateSignature();
- return bytesToHex(signature);
- }
- /**
- * @description: TODO 字节转换
- * @author chen
- * @date: 15 4月 2025 13:49
- */
- private static String bytesToHex(byte[] bytes) {
- if (bytes == null) {
- throw new IllegalArgumentException("bytes cannot be null");
- }
- StringBuilder result = new StringBuilder();
- for (byte b : bytes) {
- result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
- }
- return result.toString();
- }
- /**
- * @description: TODO 秘钥补齐
- * @author chen
- * @date: 15 4月 2025 13:50
- */
- private static byte[] expandSeed(byte[] input) {
- if (input == null) {
- throw new IllegalArgumentException("Input cannot be null");
- }
- ByteArrayOutputStream output = new ByteArrayOutputStream();
- while (output.size() < ED25519_SEED_SIZE) {
- output.writeBytes(input);
- }
- return Arrays.copyOf(output.toByteArray(), ED25519_SEED_SIZE);
- }
- /**
- * @description: TODO 哈希16字符串转字节
- * @author chen
- * @date: 18 4月 2025 11:04
- */
- private static byte[] hexStringToByteArray(String s) {
- int len = s.length();
- if ((len & 1) != 0) {
- throw new IllegalArgumentException("Hex string must have even length");
- }
- byte[] data = new byte[len / 2];
- for (int i = 0; i < len; i += 2) {
- data[i / 2] = (byte) (
- (Character.digit(s.charAt(i), 16) << 4)
- + Character.digit(s.charAt(i + 1), 16)
- );
- }
- return data;
- }
- }
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |