证书FAQ
证书FAQ
openssl证书生成和使用
以OpenSSL 1.0.2k-fips版本说明,按以下三个步骤:
- 生成私钥文件rsaprivatekey_pass.pem
生成4096位RSA私匙文件rsaprivatekey_pass.pem,用aes256加密,口令为123456:
openssl genrsa -out rsaprivatekey_pass.pem -passout pass:123456 -aes256 4096
注:
- 私钥文件及其口令企业自己负责保管。
企业用私钥文件进行数据签名。
企业可自行修改口令值123456为其它值,企业侧本地代码读取aes256加密后的本地私钥文件时需要使用此口令解密私钥文件。
- 如果企业用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
- 生成公钥证书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:使用私钥对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 | 参数校验失败,请确认后重试 |
401 | UnauthorizedError。认证消息头错误,请检查消息头 |
实现RSA签名
- 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);
- 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);
}
}
}
- 公钥证书
实现AES加密
- 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
- 对于企业网络是否有要求?
A:企业开通proxy与防火墙通道,可以访问openapi url即可。
带宽要求较低,如果并发量不大,不需要特别考虑带宽。
带宽计算公式:TPS * 接口数据量 * (1 + 30%)
假如仅调用“企业自有差旅审批单同步”接口,且接口一次访问占用流量0.5KB,假设每秒10次并发,则占用带宽为:10*0.5*1.3Kb/s = 6.5 Kbps。