md5,sha1,sha256等签名方式相信大家已经都非常熟悉了,今天介绍如何使用RSA进行签名。
RSA签名使用起来其实也是非常的简单,无非就是使用私钥进行签名,使用公钥进行验签。使用方法如下。
public class Main { private static final String private_key = "私钥"; // 私钥为PKCS8格式 private static final String public_key = "公钥"; // 公钥为X509格式,如果提供的是公钥证书,则需要从公钥证书中提取该串 public static void main(String[] args) throws Exception { String content = "待签名内容"; IAsymmetricSign rsaSign = SignManage.getSigner("RSA2"); // IAsymmetricSign rsaSign = SignManage.getSigner("RSA"); // 使用私钥进行签名 String sign = rsaSign.sign(content, "UTF-8", private_key); System.out.println(sign); // 使用公钥进行验签 boolean result = rsaSign.verify(content, "UTF-8", public_key, sign); System.out.println(result); } }
以下是实现过程:
定义接口:即最终能够对外提供的服务。
public interface IAsymmetricSign { String sign(String content, String charset, String privateKey) throws Exception; boolean verify(String content, String charset, String publicKey, String sign) throws Exception; }
定义抽象基类,约定子类必须实现doSign方法,doVerify方法,以及getSignAlgorithm方法供基类调用,即子类必须要实现的具体业务方法。
public abstract class BaseAsymmetricSign implements IAsymmetricSign { private static String DEFAULT_CHARSET = "UTF-8"; @Override public String sign(String content, String charset, String privateKey) throws Exception { if (content == null) { throw new Exception("content不能为空"); } if (charset == null || charset.isEmpty()) { charset = DEFAULT_CHARSET; } if (privateKey == null) { throw new Exception("私钥不能为空"); } try { return doSign(content, charset, privateKey); } catch (Exception e) { // 抛出异常时应该尽可能多的提供现场信息以供判断错误的原因 throw new Exception("使用" + getSignAlgorithm() + "进行签名时遭遇异常", e); } } @Override public boolean verify(String content, String charset, String publicKey, String sign) throws Exception { if (content == null) { throw new Exception("content不能为空"); } if (charset == null || charset.isEmpty()) { charset = DEFAULT_CHARSET; } if (publicKey == null) { throw new Exception("公钥不能为空"); } try { return doVerify(content,charset,publicKey,sign) ; } catch (Exception e) { // 抛出异常时应该尽可能多的提供现场信息以供判断错误的原因 throw new Exception("使用" + getSignAlgorithm() + "进行验签时遭遇异常", e); } } protected abstract String doSign(String content, String charset, String privateKey) throws Exception; protected abstract boolean doVerify(String content, String charset, String publicKey, String sign) throws Exception; protected abstract String getSignAlgorithm() ; }
具体实现业务的类1
import java.security.KeyFactory; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; public class RSA2Sign extends BaseAsymmetricSign { private static final String sign_algorithm = "SHA256WithRSA"; private static final String sign_type = "RSA"; @Override protected String doSign(String content, String charset, String privateKey) throws Exception { byte[] encodedKey = Base64.decode(privateKey, Base64.NO_WRAP); KeyFactory keyFactory = KeyFactory.getInstance(sign_type); PrivateKey priKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey)); Signature signature = Signature.getInstance(getSignAlgorithm()); signature.initSign(priKey); signature.update(content.getBytes(charset)); byte[] signed = signature.sign(); return Base64.encodeToString(signed, Base64.NO_WRAP); } @Override protected boolean doVerify(String content, String charset, String publicKey, String sign) throws Exception { byte[] encodedKey = Base64.decode(publicKey.getBytes(), Base64.NO_WRAP); KeyFactory keyFactory = KeyFactory.getInstance(sign_type); PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); Signature signature = Signature.getInstance(getSignAlgorithm()); signature.initVerify(pubKey); signature.update(content.getBytes(charset)); return signature.verify(Base64.decode(sign, Base64.NO_WRAP)); } @Override protected String getSignAlgorithm() { return sign_algorithm; } }
具体实现业务的类2
public class RSASign extends RSA2Sign{ // 和RSA2签名相比只有 除签名算法名不同以外,签名和验签的使用方法都是一样的 private static final String sign_algorithm = "SHA1WithRSA"; @Override protected String getSignAlgorithm() { return sign_algorithm; } }
具体实现业务的类3, 如何使用国密进行签名可以参考 alipay-sdk-java 中的实现。
public class SMSign extends BaseAsymmetricSign{ // 国密签名未实现 @Override protected String doSign(String content, String charset, String privateKey) throws Exception { throw new Exception("暂未实现该签名"); } @Override protected boolean doVerify(String content, String charset, String publicKey, String sign) throws Exception { throw new Exception("暂未实现该签名"); } @Override protected String getSignAlgorithm() { return "SM" ; } }
工厂类,对外提供签名实例。 其实这里我一直是挺疑惑的,为啥是每个线程new一个实例进行签名和验签,难道上面的实现是非线程安全的么。
public class SignManage { private SignManage(){} public static IAsymmetricSign getSigner(String type) throws Exception { if (type.equals("RSA2")) { return new RSA2Sign(); } if (type.equals("RSA")) { return new RSASign(); } if (type.equals("sm")) { return new SMSign(); } throw new Exception("暂不支持该签名方式"); } }
参考实现:
实现还依赖base64编码和解码,网上实现很多,也可以直接copy alipay-sdk-java中的实现。
具体的业务实现中需要通信的两方交换公钥。
假设A和B通信 , A需要生成一对钥匙,然后把公钥提供供B。 B也需要生成一对钥匙,把公钥提供给A。
A给B发消息 , A使用A的私钥进行签名或者加密,B收到消息后使用A提供的公钥进行验签或者解密。
B给A发消息 , B使用B的私钥进行签名或者加密,A收到消息后使用B提供的公钥进行验签或者解密。
之所以这么实现是因为私钥是万万不能泄露的。
同时应该告知对方使用的具体的签名算法。
更安全的做法是交换公钥证书,如何生成公钥证书,如何验证公钥证书及从证书中提取公钥等问题下一篇博客进行整理。
生成私钥和公钥,直接使用支付宝开放平台的开发助手工具。
来源:oschina
链接:https://my.oschina.net/qidis/blog/3217857