java中的签名和证书那些事
1.数字签名
数字签名,简单来说就是通过提供 可鉴别 的 数字信息 验证 自身身份 的一种方式。一套 数字签名 通常定义两种互补的运算,一个用于 签名,另一个用于 验证。分别由 发送者 持有能够 代表自己身份 的 私钥 (私钥不可泄露),由 接受者 持有与私钥对应的 公钥 ,能够在 接受 到来自发送者信息时用于 验证 其身份。签名 最根本的用途是要能够唯一 证明发送方的身份,防止 中间人攻击、CSRF跨域身份伪造。基于这一点在诸如 设备认证、用户认证、第三方认证 等认证体系中都会使用到签名算法。
2. 加密
数字签名是基于加密算法来实现的。加密算法可以用来保护明文不被非法窃取和使用。加密算法主要分为对称加密和非对称加密两种。
2.1 对称加密
对称加密算法的加密与解密密钥相同,还有一些不需要密钥的散列算法。
常见的对称加密算法主要有 DES、3DES、AES 等,散列算法主要有 SHA-1、MD5 等。
2.2 非对称加密
它需要两个密钥,一个称为 公开密钥 (public key),即 公钥,另一个称为 私有密钥 (private key),即 私钥。
- 使用公钥对数据进行加密,只有私钥才能进行解密。
- 使用私钥对数据进行加密,只有公钥才能进行解密。
常见的 非对称算法 主要有 RSA、DSA 等
rsa加密有两种使用方式:
- 第一是对文件内容加密,这种用途需要发送方用公钥对文件加密,接收方用私钥对文件解密。这种方式下,文件在网络传输中都是密文,那么在发送方要用rsa公钥加密.接收方用私钥解密.所以只有私钥的接收方才能解密,看到原文.这是rsa单纯用于文件加密的用途.
- 第二是对文件的sha256签名进行加密,这种方式下,发送方要用私钥对签名进行加密,接收方用公钥进行解密。这种方式下,原文件不加密,rsa与sha265签名算法, 生成的密文放在文件的开头。可完成对文件的验证.即该文件在传输过程中有没有被修改过.如果被修改过,即验证失败.而crc校验,只能验证文件的完整性. 如果被修改, 则验证不出来.
公钥与私钥标准:
- PKCS8是私钥证书标准.
- X509是公钥证书标准.
3. 支付宝支付中的公钥与私钥
3.1 私钥的处理
参见AlipaySignature类中的代码:
代码语言:javascript复制 /** * rsa内容签名 * * @param content * @param privateKey * @param charset * @return * @throws AlipayApiException */ public static String rsaSign(String content, String privateKey, String charset, String signType) throws AlipayApiException {
if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
return rsaSign(content, privateKey, charset); } else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
return rsa256Sign(content, privateKey, charset); } else {
throw new AlipayApiException("Sign Type is Not Support : signType=" signType); }
}
/** * sha256WithRsa 加签 * * @param content * @param privateKey * @param charset * @return * @throws AlipayApiException */ public static String rsa256Sign(String content, String privateKey, String charset) throws AlipayApiException {
try { PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
java.security.Signature signature = java.security.Signature .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
signature.initSign(priKey);
if (StringUtils.isEmpty(charset)) { signature.update(content.getBytes()); } else { signature.update(content.getBytes(charset)); }
byte[] signed = signature.sign();
return new String(Base64.encodeBase64(signed)); } catch (Exception e) { throw new AlipayApiException("RSAcontent = " content "; charset = " charset, e); }
} /** * sha1WithRsa 加签 * * @param content * @param privateKey * @param charset * @return * @throws AlipayApiException */ public static String rsaSign(String content, String privateKey, String charset) throws AlipayApiException { try { PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
java.security.Signature signature = java.security.Signature .getInstance(AlipayConstants.SIGN_ALGORITHMS);
signature.initSign(priKey);
if (StringUtils.isEmpty(charset)) { signature.update(content.getBytes()); } else { signature.update(content.getBytes(charset)); }
byte[] signed = signature.sign();
return new String(Base64.encodeBase64(signed)); } catch (InvalidKeySpecException ie) { throw new AlipayApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", ie); } catch (Exception e) { throw new AlipayApiException("RSAcontent = " content "; charset = " charset, e); } }
其中rsa和rsa256的私钥使用的是getPrivateKeyFromPKCS8;
3.2 公钥的处理:
参见AlipaySignature类中的代码:
代码语言:javascript复制 public static boolean rsaCheckV1(Map<String, String> params, String publicKey, String charset,String signType) throws AlipayApiException { String sign = params.get("sign"); String content = getSignCheckContentV1(params); return rsaCheck(content, sign, publicKey, charset,signType); }
public static boolean rsaCheckV2(Map<String, String> params, String publicKey, String charset) throws AlipayApiException { String sign = params.get("sign"); String content = getSignCheckContentV2(params);
return rsaCheckContent(content, sign, publicKey, charset); }
public static boolean rsaCheckV2(Map<String, String> params, String publicKey, String charset,String signType) throws AlipayApiException { String sign = params.get("sign"); String content = getSignCheckContentV2(params);
return rsaCheck(content, sign, publicKey, charset,signType); }
public static boolean rsaCheck(String content, String sign, String publicKey, String charset, String signType) throws AlipayApiException {
if (AlipayConstants.SIGN_TYPE_RSA.equals(signType)) {
return rsaCheckContent(content, sign, publicKey, charset);
} else if (AlipayConstants.SIGN_TYPE_RSA2.equals(signType)) {
return rsa256CheckContent(content, sign, publicKey, charset);
} else {
throw new AlipayApiException("Sign Type is Not Support : signType=" signType); }
}
public static boolean rsa256CheckContent(String content, String sign, String publicKey, String charset) throws AlipayApiException { try { PublicKey pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(publicKey.getBytes()));
java.security.Signature signature = java.security.Signature .getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
signature.initVerify(pubKey);
if (StringUtils.isEmpty(charset)) { signature.update(content.getBytes()); } else { signature.update(content.getBytes(charset)); } return signature.verify(Base64.decodeBase64(sign.getBytes())); } catch (Exception e) { throw new AlipayApiException( "RSAcontent = " content ",sign=" sign ",charset = " charset, e); } }
public static boolean rsaCheckContent(String content, String sign, String publicKey, String charset) throws AlipayApiException { try { PublicKey pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(publicKey.getBytes()));
java.security.Signature signature = java.security.Signature .getInstance(AlipayConstants.SIGN_ALGORITHMS);
signature.initVerify(pubKey);
if (StringUtils.isEmpty(charset)) { signature.update(content.getBytes()); } else { signature.update(content.getBytes(charset)); }
return signature.verify(Base64.decodeBase64(sign.getBytes())); } catch (Exception e) { throw new AlipayApiException( "RSAcontent = " content ",sign=" sign ",charset = " charset, e); } }
public static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception { KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
StringWriter writer = new StringWriter(); StreamUtil.io(new InputStreamReader(ins), writer);
byte[] encodedKey = writer.toString().getBytes();
encodedKey = Base64.decodeBase64(encodedKey);
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); }
可见公钥的处理是通过getPublicKeyFromX509来处理的;
4. https的加密处理
参见微信支付的代码:
- 方式1:对参数与key及随机串进行排序后md5;
- 方式2: https证书签名 WXPayRequest中的代码:
/** * 请求,只请求一次,不做重试 * @param domain * @param urlSuffix * @param uuid * @param data * @param connectTimeoutMs * @param readTimeoutMs * @param useCert 是否使用证书,针对退款、撤销等操作 * @return * @throws Exception */ private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception { BasicHttpClientConnectionManager connManager; if (useCert) { // 证书 char[] password = config.getMchID().toCharArray(); InputStream certStream = config.getCertStream(); KeyStore ks = KeyStore.getInstance("PKCS12"); ks.load(certStream, password);
// 实例化密钥库 & 初始化密钥工厂 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, password);
// 创建 SSLContext SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, new String[]{"TLSv1"}, null, new DefaultHostnameVerifier());
connManager = new BasicHttpClientConnectionManager( RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory) .build(), null, null, null ); } else { connManager = new BasicHttpClientConnectionManager( RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSocketFactory()) .build(), null, null, null ); }
HttpClient httpClient = HttpClientBuilder.create() .setConnectionManager(connManager) .build();
String url = "https://" domain urlSuffix; HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build(); httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8"); httpPost.addHeader("Content-Type", "text/xml"); httpPost.addHeader("User-Agent", USER_AGENT " " config.getMchID()); httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); return EntityUtils.toString(httpEntity, "UTF-8");
}
注意这里需要的是https的证书