证书FAQ


证书FAQ

openssl证书生成和使用

以OpenSSL 1.0.2k-fips版本说明,按以下三个步骤:

  1. 生成私钥文件rsaprivatekey_pass.pem

生成4096位RSA私匙文件rsaprivatekey_pass.pem,用aes256加密,口令为123456:

openssl genrsa -out rsaprivatekey_pass.pem -passout pass:123456 -aes256 4096

注:

  1. 私钥文件及其口令企业自己负责保管

企业用私钥文件进行数据签名。

企业可自行修改口令值123456为其它值,企业侧本地代码读取aes256加密后的本地私钥文件时需要使用此口令解密私钥文件。

  1. 如果企业用JAVA语言开发,JAVA语言无法直接读取不了以上命令生成的私钥文件,需将以上命令生成的私钥文件(pem文件名后缀,rsaprivatekey_pass.pem)转换成pkcs8格式(der文件名后缀,rsaprivatekey_pass.der),JAVA代码才能读取私钥文件,格式转换命令如下:
openssl pkcs8 -topk8 -inform PEM -outform DER -in rsaprivatekey_pass.pem -out rsaprivatekey_pass.der -nocrypt
  1. 生成公钥证书rsapublic_cert.cert

生成公钥证书文件到rsapublic_cert.cert文件,days参数为证书有效天数,可以不加:

openssl req -new -x509 -key rsaprivatekey_pass.pem -out rsapublic_cert.cert -days 1095 -passin pass:123456

企业需将公钥证书文件rsapublic_cert.cert证书文件提供给平台服务商。

  1. 验证证书有效性(私钥签名,公钥验签)

步骤1:使用私钥对test.txt文本内容进行数字签名,签名输出到test.sig:

openssl rsautl -sign -in test.txt -out test.sig -inkey rsaprivatekey_pass.pem -passin pass:123456

步骤2:使用公钥证书对数字签名进行验证,输出到test.vfy:

openssl rsautl -verify -in test.sig -out test.vfy -inkey rsapublic_cert.cert -certin

步骤3:检查以上2步输出的test.vfy和test.txt的内容,必须完全一致。

错误码说明

编号说明
0成功
400参数校验失败,请确认后重试
401UnauthorizedError。认证消息头错误,请检查消息头

实现RSA签名

  1. Java

Java语言实现PSS填充模式,需要依赖第三方开源组件Bouncy Castle,要求JDK 1.8及以上版本。

// 从文件、数据库或自己的密钥管理器中间加载公私钥、证书
KeyPairEntry keyPairEntry = KeyStorage.getInstance().getKeyPair("trip");
KeyPair keyPair = keyPairEntry.getKeyPair(0);
String content = "这是原始数据";

// RSA的PSS填充模式,可以使用Bouncy Castle的实现,建议全局只加载一次
Security.addProvider(new BouncyCastleProvider());

// 使用私钥签名
Signature cipher = Signature.getInstance("SHA256withRSA/PSS");
cipher.initSign(keyPair.getPrivate());
cipher.update(content.getBytes(StandardCharsets.UTF_8));
String sign = Hex.toHexString(cipher.sign());

// 使用公钥验证签名
cipher = Signature.getInstance("SHA256withRSA/PSS");
cipher.initVerify(keyPair.getPublic());
cipher.update(content.getBytes(StandardCharsets.UTF_8));
boolean result = cipher.verify(Hex.decode(sign));
System.out.println(result);

  1. C#

需要.net framework 4.7及以上版本。

using System;
using System.Linq;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

class Hello
{
    static void Main()
    {
        string message = "这是原始数据";
        byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
        
        // 可以从文件等读取证书公私钥
        string privateKey = @"...";
        string publicKey = @"...";
        string sign;

        // 使用私钥签名
        using (CngKey cngKey = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob))
        using (RSACng rsa = new RSACng(cngKey))
        {
            sign = Convert.ToHexString(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pss));
        }
        Console.WriteLine(sign);
        // 使用公钥验签
        X509Certificate2 x509 = new X509Certificate2(Convert.FromBase64String(publicKey));
        using (RSA rsa = x509.GetRSAPublicKey())
        {
            bool result = rsa.VerifyData(data, Convert.FromHexString(sign), HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
            Console.WriteLine(result);
        }
    }
}

  1. 公钥证书

公钥证书

实现AES加密

  1. Java
import org.apache.commons.ssl.util.Hex;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
    private static final String AES_GCM_NO_PADDING = "AES/GCM/NoPadding";
    private static final int GCM_TAG_LENGTH = 16;
    private static final int GCM_IV_LENGTH = 12;
	private static final String AES_KEY = "a5lDXRV5D8hTcDAh";//该字段为平台分配的AESKEY
    private static final char IV_SEP = ':';
	
    /**
     * 加密
     *
     * @param plaintext 明文
     * @return 密文
     */
     public static String encrypt(String data, String secret) throws AuthException {
        byte[] vector = new byte[GCM_IV_LENGTH];
        (new SecureRandom()).nextBytes(vector);

        SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "AES");
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, vector);
        try {
            Cipher cipher = Cipher.getInstance(AES_GCM_NO_PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
            byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
            // 将随机IV和密文拼接起来
            return HexUtils.toHexString(vector) + IV_SEP + HexUtils.toHexString(encrypted);
        } catch (Exception e) {
            log.error("encrypt fail", e);
            throw new AuthException("encrypt fail");
        }
    }
	
    /**
     * 解密
     *
     * @param cipherText 密文
     * @return 明文
     */
  public static String decrypt(String data, String secret) throws AuthException {
        int idx = data.indexOf(IV_SEP);
        byte[] vector = HexUtils.fromHexString(data.substring(0, idx));
        byte[] encrypt = HexUtils.fromHexString(data.substring(idx + 1));

        SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "AES");
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH * Byte.SIZE, vector);
        try {
            Cipher cipher = Cipher.getInstance(AES_GCM_NO_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);

            return new String(cipher.doFinal(encrypt, 0, encrypt.length), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("decrypt fail", e);
            throw new AuthException("decrypt body fail");
        }
    }
}

示例代码汇总(签名+加密)

示例

FAQ

  1. 对于企业网络是否有要求?

A:企业开通proxy与防火墙通道,可以访问openapi url即可。

带宽要求较低,如果并发量不大,不需要特别考虑带宽。

带宽计算公式:TPS * 接口数据量 * (1 + 30%)

假如仅调用“企业自有差旅审批单同步”接口,且接口一次访问占用流量0.5KB,假设每秒10次并发,则占用带宽为:10*0.5*1.3Kb/s = 6.5 Kbps。