找回密码
 立即注册
首页 业界区 安全 QQ机器人webhook签名验签Java版

QQ机器人webhook签名验签Java版

呵桢 2025-6-1 00:10:47
QQ机器人WebHook签名验签JAVA版
  1. 官方文档中仅提供了GO,PYTHON和NODE的SDK,并且提供的示例也仅有GO版本的,特此为JAVA做一版。
复制代码
前期准备
  1. QQ开放平台已创建机器人获取对应的机器人ID和密钥等资料信息。
复制代码
maven引用:
  1. <dependency>
  2.         <groupId>org.bouncycastle</groupId>
  3.         bcprov-jdk15on</artifactId>
  4.         <version>1.70</version>
  5. </dependency>
复制代码
签名工具类

签名类:
  1. import lombok.extern.slf4j.Slf4j;
  2. import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
  3. import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
  4. import org.bouncycastle.crypto.signers.Ed25519Signer;
  5. import java.io.ByteArrayOutputStream;
  6. import java.io.IOException;
  7. import java.nio.charset.StandardCharsets;
  8. import java.util.Arrays;
  9. /**
  10. * @author chen
  11. * @version 1.0
  12. * @description: TODO 回调签名加解密帮助类
  13. * @date 14 4月 2025 14:14
  14. */
  15. @Slf4j
  16. public class CallBackSignUtil {
  17.     private static final int ED25519_SEED_SIZE = 32;
  18.     /**
  19.      * @description: TODO 验证签名是否对应
  20.      * @author chen
  21.      * @date: 15 4月 2025 14:03
  22.      */
  23.     public static boolean verifySignature(String appSecret, String xSignatureEd25519, String xSignatureTimestamp, String reqBody) throws IOException {
  24.         byte[] seed = expandSeed(appSecret.getBytes(StandardCharsets.UTF_8));
  25.         // 用 seed 构造 Ed25519 私钥
  26.         Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(seed, 0);
  27.         // 从私钥推导出公钥
  28.         Ed25519PublicKeyParameters publicKey = privateKey.generatePublicKey();
  29.         byte[] signature = hexStringToByteArray(xSignatureEd25519);
  30.         if (signature.length != 64 || (signature[63] & 0xE0) != 0) {
  31.             return false;
  32.         }
  33.         ByteArrayOutputStream msg = new ByteArrayOutputStream();
  34.         msg.write(xSignatureTimestamp.getBytes());
  35.         msg.write(reqBody.getBytes());
  36.         byte[] msgBytes = msg.toByteArray();
  37.         Ed25519Signer signer = new Ed25519Signer();
  38.         signer.init(false, publicKey);
  39.         signer.update(msgBytes, 0, msgBytes.length);
  40.         return signer.verifySignature(signature);
  41.     }
  42.     /**
  43.      * @description: TODO  生成秘钥
  44.      * @author chen
  45.      * @date: 15 4月 2025 13:50
  46.      */
  47.     public static String generateResponse(String botSecret, String eventTs, String plainToken) throws Exception {
  48.   
  49.         byte[] seed = expandSeed(botSecret.getBytes(StandardCharsets.UTF_8));
  50.         Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(seed, 0);
  51.         // 生成Ed25519密钥对
  52.         ByteArrayOutputStream msg = new ByteArrayOutputStream();
  53.         msg.write(eventTs.getBytes());
  54.         msg.write(plainToken.getBytes());
  55.         byte[] msgBytes = msg.toByteArray();
  56.         Ed25519Signer signer = new Ed25519Signer();
  57.         signer.init(true, privateKey);
  58.         signer.update(msgBytes, 0, msgBytes.length);
  59.         byte[] signature = signer.generateSignature();
  60.         return bytesToHex(signature);
  61.     }
  62.     /**
  63.      * @description: TODO 字节转换
  64.      * @author chen
  65.      * @date: 15 4月 2025 13:49
  66.      */
  67.     private static String bytesToHex(byte[] bytes) {
  68.         if (bytes == null) {
  69.             throw new IllegalArgumentException("bytes cannot be null");
  70.         }
  71.         StringBuilder result = new StringBuilder();
  72.         for (byte b : bytes) {
  73.             result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
  74.         }
  75.         return result.toString();
  76.     }
  77.     /**
  78.      * @description: TODO 秘钥补齐
  79.      * @author chen
  80.      * @date: 15 4月 2025 13:50
  81.      */
  82.     private static byte[] expandSeed(byte[] input) {
  83.         if (input == null) {
  84.             throw new IllegalArgumentException("Input cannot be null");
  85.         }
  86.         ByteArrayOutputStream output = new ByteArrayOutputStream();
  87.         while (output.size() < ED25519_SEED_SIZE) {
  88.             output.writeBytes(input);
  89.         }
  90.         return Arrays.copyOf(output.toByteArray(), ED25519_SEED_SIZE);
  91.     }
  92.     /**
  93.      * @description: TODO 哈希16字符串转字节
  94.      * @author chen
  95.      * @date: 18 4月 2025 11:04
  96.      */
  97.     private static byte[] hexStringToByteArray(String s) {
  98.         int len = s.length();
  99.         if ((len & 1) != 0) {
  100.             throw new IllegalArgumentException("Hex string must have even length");
  101.         }
  102.         byte[] data = new byte[len / 2];
  103.         for (int i = 0; i < len; i += 2) {
  104.             data[i / 2] = (byte) (
  105.                     (Character.digit(s.charAt(i), 16) << 4)
  106.                             + Character.digit(s.charAt(i + 1), 16)
  107.             );
  108.         }
  109.         return data;
  110.     }
  111. }
复制代码
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册