起因,最近使用go对接百度联盟api需要使用到DSA私钥完成签名过程,在百度提供的代码示例里面没有go代码的支持,示例中仅有php、python2和3、java的代码,网上找了半天发现go中对DSA私钥解析支持不友好,然后决定使用在java中完成签名计算过程,生成可执行jar后由外部传入参数获取签名数据。
百度联盟api文档说明:
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方法
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 :
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
<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方法
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调用:
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原生的支持请留言告诉我,十分感谢!