呵桢 发表于 2025-6-1 00:10:47

QQ机器人webhook签名验签Java版

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 & 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;
      for (int i = 0; i < len; i += 2) {
            data = (byte) (
                  (Character.digit(s.charAt(i), 16) << 4)
                            + Character.digit(s.charAt(i + 1), 16)
            );
      }
      return data;
    }
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: QQ机器人webhook签名验签Java版