go执行java -jar 完成DSA私钥解析并签名

news/2025/2/24 20:19:23

        起因,最近使用go对接百度联盟api需要使用到DSA私钥完成签名过程,在百度提供的代码示例里面没有go代码的支持,示例中仅有php、python2和3、java的代码,网上找了半天发现go中对DSA私钥解析支持不友好,然后决定使用在java中完成签名计算过程,生成可执行jar后由外部传入参数获取签名数据。

百度联盟api文档说明:

java">1)权限开通后,登录百度联盟媒体平台(union.baidu.com),在【账户管理 – API 管理】模块查询 AccessKey;

2)自行生成 PEM 格式的 DSA 私钥公钥对,并妥善保管私钥公钥,具体生成方式如下:
1. ① 生成随机参数
2. openssl dsaparam -out dsaparam.pem 1024
3. ② 生成 DSA 私钥 privkey.pem
4. openssl gendsa -out privkey.pem dsaparam.pem
5. ③ 生成公钥 pubkey.pem
6. openssl dsa -in privkey.pem -pubout -out pubkey.pem

3)按照以下说明生成签名,并以此作为请求字段调用 API:
1. 需要将:
2. ① 用户的 AccessKey
3. ② HTTP Method(GET,POST,PUT 等),见接口定义中的 HTTP 方法
4. ③ 请求的资源的 url 及 query 参数(不包含协议及 Host 部分),见接口定义中的 URL 
5. ④ x-ub-date 时间戳
6. ⑤ ContentType 如:application/json
7. ⑥ body 的 32 位 MD5 编码串
8. // 注:GET 请求无 5,6
9. 用“\n”连接起来,作为待签名的数据。
10. 然后将待签名的数据用 DSA 私钥通过 SHA1 算法加密编码,加密后的结果(字节数组)使用 BASE64 进行编码,作为签名使用。
11. 
12. 示例:
13.
14. POST 请求
15. // 用户的 AccessKey 为 6SL21N4H3J9FE70ZXXXXXXXXXXXXXXXX
16. // 当前的 unix 时间为 16183682792
17. // POST /ssp/1/sspservice/appadpos/app/adpos/create
18. ==== 待签名的内容,不包含本行内容 ====
19. "6SL21N4H3J9FE70ZXXXXXXXXXXXXXXXX\n"
20. + "POST\n"
21. + "/ssp/1/sspservice/appadpos/app/adpos/create\n"
22. + "16183682792\n"
23. + "application/json\n" 
24. + "b6cc88bb12023b96917f3a057a5c67b7"
25. GET 请求
26. // 用户的 AccessKey 为 6SL21N4H3J9FE70ZXXXXXXXXXXXXXXXX
27. // 当前的 unix 时间为 16183682792
28. // GET /union/11.0/apps?page=1&count=10
29. ==== 待签名的内容,不包含本行内容 ====
30. "6SL21N4H3J9FE70ZXXXXXXXXXXXXXXXX\n"
31. + "GET\n"
32. + "/union/11.0/apps?page=1&count=10\n"
33. + "16183682792\n"
34. + "\n" 
35. + ""
36. ==== 签名内容结束,注意,不是以\n 结尾 ====

4)每次请求时,需要在 HTTP Header 中额外增加以下两项内容(必填):
l x-ub-authorization:用户信息+请求信息的签名;格式为:${AccessKey}: ${Signature};
l x-ub-date:请求的时间戳,精确到秒或毫秒。

java代码:

        main方法

java">import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import java.lang.reflect.Type;
import java.security.PrivateKey;
import java.util.List;

public class App {
    public static void main(String[] args) {
        if (args == null || args.length != 2) {
            throw new RuntimeException("params nums error");
        }
        String dsaPrivateKey = args[0];
        // post arr := []string{AccessKey, Method, Path, Unix, ContentType, string(jsonData)}
        // get arr := []string{AccessKey, Method, Path, Unix}
        String jsonArray = args[1];
        Gson gson = new Gson();
        Type listType = new TypeToken<List<String>>() {}.getType();
        List<String> stringList = gson.fromJson(jsonArray, listType);
        if (stringList.size() == 6) {
            // post
            String body = stringList.get(stringList.size() - 1);
            // 取出最后一位body入参
            JsonObject jsonObject = gson.fromJson(body, JsonObject.class);
            byte[] content = jsonObject.toString().getBytes();
            MD5Digest digest = new MD5Digest();
            digest.update(content, 0, content.length);
            byte[] digestBytes = new byte[digest.getDigestSize()];
            digest.doFinal(digestBytes, 0);
            stringList.set(stringList.size() - 1, new String(Hex.encode(digestBytes)));
        } else if (stringList.size() == 4) {
            // get
            // empty ContentType
            stringList.add("");
            // empty content md5
            stringList.add("");
        } else {
            System.out.println();
        }
        // 待签名数据
        String stringTobeSigned = String.join("\n", stringList);
        PrivateKey privateKey = SignatureUtils.parsePemPrivateKey(dsaPrivateKey);
        byte[] signatureBytes = SignatureUtils.signMessage(privateKey, stringTobeSigned.getBytes());
        String signature = new String(Base64.encode(signatureBytes));
        System.out.print(stringList.get(0) + ":" + signature);
    }
}

        签名工具类SignatureUtils :

java">import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;

import java.io.IOException;
import java.io.StringReader;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;

/**
 * 签名相关的工具类
 *
 * @version 1.0.0
 */
public class SignatureUtils {

    static {
        if (Security.getProvider("BC") == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * 读取privateKey
     *
     * @param pemKey
     * @return
     */
    public static PrivateKey parsePemPrivateKey(String pemKey) {
        PEMReader reader = new PEMReader(new StringReader(pemKey));
        Object key;
        try {
            key = reader.readObject();
            reader.close();
        } catch (IOException e) {
            throw new RuntimeException("read pubKey error, pemKey: " + pemKey);
        }

        if (key instanceof KeyPair) {
            return ((KeyPair) key).getPrivate();
        }
        throw new RuntimeException("not a private key, pemKey: " + pemKey);
    }

    /**
     * 签名信息
     *
     * @param privateKey
     * @param message
     * @return
     */
    public static byte[] signMessage(PrivateKey privateKey, byte[] message) {
        Signature dsa;
        try {
            dsa = Signature.getInstance("SHA1withDSA", "SUN");
            dsa.initSign(privateKey);
            dsa.update(message);
            return dsa.sign();
        } catch (Exception e) {
            throw new RuntimeException("sign error, stack trace: " + e);
        }
    }

}

        pom.xml

java"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>xxx.xxx</groupId>
  <artifactId>baiduDsaParse</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>baiduDsaParse</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>3.3.0</version>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>xxx.xxx.App</mainClass> <!-- 指定你的主类 -->
            </manifest>
          </archive>
        </configuration>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>


  <dependencies>

    <dependency>
      <groupId>org.bouncycastle</groupId>
      <artifactId>bcprov-jdk16</artifactId>
      <version>1.46</version>
    </dependency>
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.5</version>
    </dependency>
  </dependencies>

</project>

go代码:

        执行jar方法

java">package xxx

import (
	"fmt"
	"os/exec"
)

type ThisSoft struct {
}

func (thisSoft *ThisSoft) RunCommand(dsaPrivateKey, params string) (string, error) {
	// 这里要改为服务器的资源路径
	filePath := "D:/goProjects/server/lib/baiduDsaParse-1.0-SNAPSHOT.jar"

	// 要执行的命令
	cmd := exec.Command("java", "-jar", filePath, dsaPrivateKey, params)
	outByte, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Println("命令执行出错:", err)
		return "", err
	}
	return string(outByte), nil
}

api调用:

java">func (thisMethod *ThisBaiduApi) MediumGetPage(AccessKey string, dsaPrivateKey string, request *MediumGetPageRequest) (*MediumPageResponse, error) {
	// 将请求参数序列化为 JSON
	jsonData, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}

	// 发送 HTTP 请求
	Path := "/ssp/1/sspservice/medium/app-manage/page-sdk-app"
	Method := "POST"
	client := &http.Client{}
	fmt.Println("request json:" + string(jsonData))
	req, err := http.NewRequest(Method, BASE_URL+Path, bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, err
	}
	// 时间戳
	Unix := strconv.FormatInt(time.Now().Unix(), 10)
	ContentType := "application/json"
	arr := []string{AccessKey, Method, Path, Unix, ContentType, string(jsonData)}
	params, err := json.Marshal(arr)
	if err != nil {
		return nil, err
	}
    // 执行jar获取签名,私钥和参数传入jar中计算出签名
    // 这里把参入传过去计算是为了防止计算签名是json序列化工具不同引起错误
	token, err := soft.RunCommand(dsaPrivateKey, string(params))
	if err != nil {
		return nil, errors.New("generate auth fail")
	}
	// 设置请求头
	req.Header.Set("Content-Type", ContentType)
	req.Header.Set("x-ub-authorization", token)
	req.Header.Set("x-ub-date", Unix)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return nil, errors.New("resp error, Status=" + resp.Status)
	}
	// 解析返回值
	var response MediumPageResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return nil, err
	}
	return &response, nil
}

 如果有go原生的支持请留言告诉我,十分感谢!


http://www.niftyadmin.cn/n/5864783.html

相关文章

微信小程序页面导航与路由:实现多页面跳转与数据传递

在上一篇中&#xff0c;我们学习了微信小程序的数据绑定和事件处理&#xff0c;实现了动态交互功能。然而&#xff0c;一个完整的小程序通常由多个页面组成&#xff0c;用户需要在不同页面之间进行跳转。本文将深入探讨微信小程序的页面导航与路由机制&#xff0c;帮助你实现多…

leetcode_位运算 2206. 将数组划分成相等数对

2206. 将数组划分成相等数对 给你一个整数数组 nums&#xff0c;它包含 2 * n 个整数。 你需要将 nums 划分成 n 个数对&#xff0c;满足&#xff1a; 每个元素 只属于一个数对。同一数对中的元素相等 。如果可以将 nums 划分成 n 个数对&#xff0c;请你返回 true &#xff0…

BOOST电路设计

目录 1电路模型 2器件选型 2.1设计需求 2.2参数计算 2.2.1电感L计算 2.2.2电容计算 2.2.3电阻计算 3仿真测试 4参数测试 4.1负载调整率 4.2电容测试 4.3电感测试 5实际应用 1电路模型 Boost升压电路,可以工作在电流断续工作模式(DCM)和电流连续工作模式(CCM)。CCM工…

LeetCodehot 力扣热题100 课程表

题目背景 这个问题要求我们判断是否可以完成所有课程的学习。每门课程可能依赖其他课程作为前置课程&#xff0c;构成了一个有向图。我们需要确定是否存在环&#xff0c;若存在环&#xff0c;说明课程之间互相依赖&#xff0c;无法完成所有课程&#xff1b;如果不存在环&#x…

RFID涉密载体柜:智能安全,全程守护,提供智能化的安全管控

行业背景 RFID智能载体柜&#xff08;DW-G101&#xff09;是一种便捷化的载体管控系统&#xff0c;它采用RFID技术实现信息化&#xff0c;可以大大提高载体管理的效率和准确性。 随着信息化的快速发展&#xff0c;涉密载体&#xff08;如文件、U盘、光盘等&#xff09;的管理…

SQL笔记#数据更新

一、数据的插入(INSERT语句的使用方法) 1、什么是INSERT 首先通过CREATE TABLE语句创建表&#xff0c;但创建的表中没有数据&#xff1b;再通过INSERT语句向表中插入数据。 --创建表ProductIns CREATE TABLE ProductIns (product_id CHAR(4) NOT NULL,product_name …

Gin从入门到精通 (五)数据绑定与验证

数据绑定与验证 数据绑定是指将请求数据&#xff08;如 JSON、表单、URL 参数等&#xff09;绑定到 Go 语言中的结构体。Gin 提供了便捷的方法将请求中的数据映射到预定义的结构体字段上&#xff0c;使得开发者可以像访问结构体字段一样访问请求数据。 数据验证是对绑定到结构…

Github 2025-02-21 Java开源项目日报Top7

根据Github Trendings的统计,今日(2025-02-21统计)共有7个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目7Groovy项目1C++项目1TypeScript项目1本地托管的PDF文件操作工具 创建周期:464 天开发语言:Java, HTML协议类型:GNU General Public …