# 非开票签名
# 简介
API签名使用JWT(Java Web Token)进行签名,详见https://jwt.io/。 各类编程语言参考官网的支持列表。 一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
注意事项
TIP
- 签名使用的密钥必须必须与平台appid注册的一致
- 具体使用方式参考样例代码示例
- JWT名词说明
- iss: jwt签发者。使用appid
- sub: 主题。例如:billing=开票;reimbursement=报销;
- aud: 接收jwt的一方。固定值: einvoice.yonyoucloud.com
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的.
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
# 样例代码
# JAVA(JDK1.6及其以上)
# Maven配置文件依赖
依赖配置如下,其中bcprov-jdk15on和使用的签名私钥格式有关,使用PEM格式的签名私钥时需要使用此引用。
<!-- jjwt,Java Web Token签名工具包 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
<!-- bcprov-jdk15on,PEM格式私钥证书读取工具类 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.52</version>
</dependency>
# 签名代码
签名工具类
package com.yonyou.einvoice.test;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Map;
import org.bouncycastle.util.io.pem.PemReader;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.compression.CompressionCodecs;
/**
* @author wangweir
*
*/
public class SignUtils {
private String sign(String paramsMap) throws Exception {
// 读取CA证书与PEM格式证书需要根据实际证书使用情况而定,目前这两种都支持
PrivateKey privateKey = loadPrivateKeyOfCA();
// PrivateKey privateKey = loadPrivateKeyOfPem();
Map<String, Object> claims = JwtParamBuilder.build().setSubject("tester").setIssuer("einvoice").setAudience("einvoice").addJwtId().addIssuedAt().getClaims();
// 使用jdk1.6版本时,删除下面代码的中.compressWith(CompressionCodecs.DEFLATE)
String compactJws = Jwts.builder().signWith(SignatureAlgorithm.RS512, privateKey)
.setClaims(claims).compressWith(CompressionCodecs.DEFLATE).compact();
return compactJws;
}
/**
* 读取PEM编码格式
*
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
protected static PrivateKey loadPrivateKeyOfPem()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PemReader reader = new PemReader(new FileReader("D:\\CA\\keystore\\测试.private"));
byte[] privateKeyBytes = reader.readPemObject().getContent();
reader.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);
return privateKey;
}
/**
* 计算MD5
*
* @param str
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
private String getMD5(String str) throws UnsupportedEncodingException, NoSuchAlgorithmException {
byte[] buf = null;
buf = str.getBytes("utf-8");
MessageDigest md5 = null;
md5 = MessageDigest.getInstance("MD5");
md5.update(buf);
byte[] tmp = md5.digest();
StringBuilder sb = new StringBuilder();
for (byte b : tmp) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
/**
* 读取证书私钥
*
* @return
* @throws UnrecoverableKeyException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws IOException
*/
protected static PrivateKey loadPrivateKeyOfCA() throws UnrecoverableKeyException,
KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
String keypath = "D:\\CA\\keystore\\NC_65_SCM.p12";
FileInputStream in = new FileInputStream(keypath);
KeyStore ks = KeyStore.getInstance("pkcs12");
String pwd = "123456";
ks.load(in, pwd.toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey caprk = (PrivateKey) ks.getKey(alias, pwd.toCharArray());
return caprk;
}
}
签名辅助工具类
package com.yonyou.einvoice.test;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author wangweir
*
*/
public class JwtParamBuilder {
/** JWT {@code Issuer} claims parameter name: <code>"iss"</code> */
public static final String ISSUER = "iss";
/** JWT {@code Subject} claims parameter name: <code>"sub"</code> */
public static final String SUBJECT = "sub";
/** JWT {@code Audience} claims parameter name: <code>"aud"</code> */
public static final String AUDIENCE = "aud";
/** JWT {@code Expiration} claims parameter name: <code>"exp"</code> */
public static final String EXPIRATION = "exp";
/** JWT {@code Not Before} claims parameter name: <code>"nbf"</code> */
public static final String NOT_BEFORE = "nbf";
/** JWT {@code Issued At} claims parameter name: <code>"iat"</code> */
public static final String ISSUED_AT = "iat";
/** JWT {@code JWT ID} claims parameter name: <code>"jti"</code> */
public static final String ID = "jti";
private Map<String, Object> claims;
private final long now;
private JwtParamBuilder() {
claims = new HashMap<>();
now = System.currentTimeMillis() / 1000l;
}
public static JwtParamBuilder build() {
return new JwtParamBuilder();
}
public JwtParamBuilder addIssuedAt() {
claims.put(ISSUED_AT, now);
return this;
}
public JwtParamBuilder setExpirySeconds(final Integer expirySeconds) {
claims.put(EXPIRATION, now + expirySeconds);
return this;
}
public JwtParamBuilder setNotBeforeSeconds(final Integer beforeSeconds) {
claims.put(NOT_BEFORE, now - beforeSeconds);
return this;
}
public JwtParamBuilder setSubject(String sub) {
addOneClaim(SUBJECT, sub);
return this;
}
public JwtParamBuilder setIssuer(String iss) {
addOneClaim(ISSUER, iss);
return this;
}
public JwtParamBuilder setAudience(String aud) {
addOneClaim(AUDIENCE, aud);
return this;
}
public JwtParamBuilder addJwtId() {
return setJwtId(UUID.randomUUID().toString());
}
public JwtParamBuilder setJwtId(String jwtid) {
addOneClaim(ID, UUID.randomUUID().toString());
return this;
}
public JwtParamBuilder claim(String name, Object value) {
if (value == null) {
this.claims.remove(name);
} else {
this.claims.put(name, value);
}
return this;
}
private void addOneClaim(String key, String value) {
if (value != null && value.length() > 0) {
claims.put(key, value);
}
}
/**
* @return the claims
*/
public Map<String, Object> getClaims() {
return claims;
}
}
# JAVA(JDK1.5)
# Maven配置文件依赖
依赖配置如下,其中bcprov-jdk15on和使用的签名私钥格式有关,使用PEM格式的签名私钥时需要使用此引用,签名jar包联系接口调试人获取。
# 签名代码
签名工具类
package com.yonyou.einvoice.test;
import com.auth0.jwt.Algorithm;
import com.auth0.jwt.JWTSigner;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.util.*;
/**
* @author wangweir
*
*/
public class SignUtils {
private String sign(String json) throws Exception {
// 读取CA证书
PrivateKey privateKey = loadPrivateKeyOfCA();
Map<String, Object> claims =
JwtParamBuilder.build().setSubject("tester").setIssuer("einvoice").setAudience("einvoice")
.addJwtId().addIssuedAt().getClaims();
JWTSigner signer = new JWTSigner(privateKey);
String compactJws = signer.sign(claims, new JWTSigner.Options().setExpirySeconds(300)
.setNotValidBeforeLeeway(5).setIssuedAt(true).setJwtId(true).setAlgorithm(Algorithm.RS256));
return compactJws;
}
/**
* 计算MD5
*
* @param str
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
private static String getMD5(String str) throws UnsupportedEncodingException,
NoSuchAlgorithmException {
byte[] buf = null;
buf = str.getBytes("utf-8");
MessageDigest md5 = null;
md5 = MessageDigest.getInstance("MD5");
md5.update(buf);
byte[] tmp = md5.digest();
StringBuilder sb = new StringBuilder();
for (byte b : tmp) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
/**
* 读取PEM编码格式
*
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
protected static PrivateKey loadPrivateKeyOfPem()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PemReader reader = new PemReader(new FileReader("D:\\CA\\keystore\\测试.private"));
byte[] privateKeyBytes = reader.readPemObject().getContent();
reader.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);
return privateKey;
}
/**
* 读取证书私钥
*
* @return
* @throws UnrecoverableKeyException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws IOException
*/
protected RSAPrivateKey loadPrivateKeyOfCA() throws UnrecoverableKeyException, KeyStoreException,
NoSuchAlgorithmException, CertificateException, IOException {
FileInputStream in = new FileInputStream(KEYPATH);
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(in, PASSWORD.toCharArray());
String alias = ks.aliases().nextElement();
RSAPrivateKey caprk = (RSAPrivateKey) ks.getKey(alias, PASSWORD.toCharArray());
return caprk;
}
}
签名辅助工具类
package test;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author wangweir
*
*/
public class JwtParamBuilder {
/** JWT {@code Issuer} claims parameter name: <code>"iss"</code> */
public static final String ISSUER = "iss";
/** JWT {@code Subject} claims parameter name: <code>"sub"</code> */
public static final String SUBJECT = "sub";
/** JWT {@code Audience} claims parameter name: <code>"aud"</code> */
public static final String AUDIENCE = "aud";
/** JWT {@code Expiration} claims parameter name: <code>"exp"</code> */
public static final String EXPIRATION = "exp";
/** JWT {@code Not Before} claims parameter name: <code>"nbf"</code> */
public static final String NOT_BEFORE = "nbf";
/** JWT {@code Issued At} claims parameter name: <code>"iat"</code> */
public static final String ISSUED_AT = "iat";
/** JWT {@code JWT ID} claims parameter name: <code>"jti"</code> */
public static final String ID = "jti";
private Map<String, Object> claims;
private final long now;
private JwtParamBuilder() {
claims = new HashMap();
now = System.currentTimeMillis() / 1000l;
}
public static JwtParamBuilder build() {
return new JwtParamBuilder();
}
public JwtParamBuilder addIssuedAt() {
claims.put(ISSUED_AT, now);
return this;
}
public JwtParamBuilder setExpirySeconds(final Integer expirySeconds) {
claims.put(EXPIRATION, now + expirySeconds);
return this;
}
public JwtParamBuilder setNotBeforeSeconds(final Integer beforeSeconds) {
claims.put(NOT_BEFORE, now - beforeSeconds);
return this;
}
public JwtParamBuilder setSubject(String sub) {
addOneClaim(SUBJECT, sub);
return this;
}
public JwtParamBuilder setIssuer(String iss) {
addOneClaim(ISSUER, iss);
return this;
}
public JwtParamBuilder setAudience(String aud) {
addOneClaim(AUDIENCE, aud);
return this;
}
public JwtParamBuilder addJwtId() {
return setJwtId(UUID.randomUUID().toString());
}
public JwtParamBuilder setJwtId(String jwtid) {
addOneClaim(ID, UUID.randomUUID().toString());
return this;
}
public JwtParamBuilder claim(String name, Object value) {
if (value == null) {
this.claims.remove(name);
} else {
this.claims.put(name, value);
}
return this;
}
private void addOneClaim(String key, String value) {
if (value != null && value.length() > 0) {
claims.put(key, value);
}
}
/**
* @return the claims
*/
public Map<String, Object> getClaims() {
return claims;
}
}
# C#
# 添加引用
使用包管理开发工具NuGet添加依赖。NuGet具体使用方法请自行Google。 在程序包管理器控制台输入下列命令安装依赖 1、Install-Package Newtonsoft.Json 2、Install-Package jose-jwt Newtonsoft.Json用来进行json转换 jose-jwt用来进行jwt签名
# API调用代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Web;
using Jose;
using Newtonsoft.Json;
namespace Invoicing
{
internal class Program
{
private static void Main(string[] args)
{
}
/// <summary>
/// 数据签名
/// </summary>
/// <param name="requestdatas"></param>
/// <returns></returns>
private static string Sign(string requestdatas)
{
var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
//5分钟内有效
var exp = ts.TotalMilliseconds + 360000;
var payload = new Dictionary<string, object>
{
{"sub", "tester"},
{"exp", exp}
};
var privateKey =
new X509Certificate2("D:/CA/keystore/pro22.pfx", "password",
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as
RSACryptoServiceProvider;
var token = JWT.Encode(payload, privateKey, JwsAlgorithm.PS256);
return token;
}
/// <summary>
/// 获取md5值
/// </summary>
/// <param name="requestdatas"></param>
/// <returns></returns>
private static string GetMd5(string requestdatas)
{
using (var md5Hash = MD5.Create())
{
var hash = GetMd5Hash(md5Hash, requestdatas);
return hash;
}
}
private static string GetMd5Hash(MD5 md5Hash, string input)
{
// Convert the input string to a byte array and compute the hash.
var data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
var sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (var i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
}
}
← 开票签名 OpenAPI连通测试 →