md5,sha1,sha256等签名方式相信大家已经都非常熟悉了,今天介绍如何使用RSA进行签名。
RSA签名使用起来其实也是非常的简单,无非就是使用私钥进行签名,使用公钥进行验签。使用方法如下。
public classMain {
private static finalString private_key= "私钥";// 私钥为PKCS8格式
private static finalString public_key= "公钥";// 公钥为X509格式,如果提供的是公钥证书,则需要从公钥证书中提取该串
public static voidmain(String[] args) throwsException {
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);
// 使用公钥进行验签booleanresult = rsaSign.verify(content,"UTF-8",public_key,sign);System.out.println(result);}
}
以下是实现过程:
定义接口:即最终能够对外提供的服务。
public interfaceIAsymmetricSign {
String sign(String content,String charset,String privateKey) throwsException;
booleanverify(String content,String charset,String publicKey,String sign) throwsException;
}
定义抽象基类,约定子类必须实现doSign方法,doVerify方法,以及getSignAlgorithm方法供基类调用,即子类必须要实现的具体业务方法。
public abstract classBaseAsymmetricSign implementsIAsymmetricSign {
private staticString DEFAULT_CHARSET= "UTF-8";
@OverridepublicString sign(String content,String charset,String privateKey) throwsException {
if(content == null) {
throw newException("content不能为空");}
if(charset == null|| charset.isEmpty()) {
charset = DEFAULT_CHARSET;}
if(privateKey == null) {
throw newException("私钥不能为空");}
try{
returndoSign(content,charset,privateKey);} catch(Exception e) {
// 抛出异常时应该尽可能多的提供现场信息以供判断错误的原因throw newException("使用"+ getSignAlgorithm() + "进行签名时遭遇异常",e);}
}
@Overridepublic booleanverify(String content,String charset,String publicKey,String sign) throwsException {
if(content == null) {
throw newException("content不能为空");}
if(charset == null|| charset.isEmpty()) {
charset = DEFAULT_CHARSET;}
if(publicKey == null) {
throw newException("公钥不能为空");}
try{
returndoVerify(content,charset,publicKey,sign) ;} catch(Exception e) {
// 抛出异常时应该尽可能多的提供现场信息以供判断错误的原因throw newException("使用"+ getSignAlgorithm() + "进行验签时遭遇异常",e);}
}
protected abstractString doSign(String content,String charset,String privateKey) throwsException;
protected abstract booleandoVerify(String content,String charset,String publicKey,String sign) throwsException;
protected abstractString getSignAlgorithm() ;}
具体实现业务的类1
importjava.security.KeyFactory;importjava.security.PrivateKey;importjava.security.PublicKey;importjava.security.Signature;importjava.security.spec.PKCS8EncodedKeySpec;importjava.security.spec.X509EncodedKeySpec;
public classRSA2Sign extendsBaseAsymmetricSign {
private static finalString sign_algorithm= "SHA256WithRSA";private static finalString sign_type= "RSA";
@OverrideprotectedString doSign(String content,String charset,String privateKey) throwsException {
byte[] encodedKey = Base64.decode(privateKey,Base64.NO_WRAP);KeyFactory keyFactory = KeyFactory.getInstance(sign_type);PrivateKey priKey = keyFactory.generatePrivate(newPKCS8EncodedKeySpec(encodedKey));
Signature signature = Signature.getInstance(getSignAlgorithm());signature.initSign(priKey);signature.update(content.getBytes(charset));byte[] signed = signature.sign();returnBase64.encodeToString(signed,Base64.NO_WRAP);}
@Overrideprotected booleandoVerify(String content,String charset,String publicKey,String sign) throwsException {
byte[] encodedKey = Base64.decode(publicKey.getBytes(),Base64.NO_WRAP);KeyFactory keyFactory = KeyFactory.getInstance(sign_type);PublicKey pubKey = keyFactory.generatePublic(newX509EncodedKeySpec(encodedKey));
Signature signature = Signature.getInstance(getSignAlgorithm());signature.initVerify(pubKey);signature.update(content.getBytes(charset));
returnsignature.verify(Base64.decode(sign,Base64.NO_WRAP));}
@OverrideprotectedString getSignAlgorithm() {
returnsign_algorithm;}
}
具体实现业务的类2
public classRSASign extendsRSA2Sign{
// 和RSA2签名相比只有 除签名算法名不同以外,签名和验签的使用方法都是一样的private static finalString sign_algorithm= "SHA1WithRSA";
@OverrideprotectedString getSignAlgorithm() {
returnsign_algorithm;}
}
具体实现业务的类3, 如何使用国密进行签名可以参考 alipay-sdk-java 中的实现。
public classSMSign extendsBaseAsymmetricSign{
// 国密签名未实现
@OverrideprotectedString doSign(String content,String charset,String privateKey) throwsException {
throw newException("暂未实现该签名");}
@Overrideprotected booleandoVerify(String content,String charset,String publicKey,String sign) throwsException {
throw newException("暂未实现该签名");}
@OverrideprotectedString getSignAlgorithm() {
return"SM";}
}
工厂类,对外提供签名实例。 其实这里我一直是挺疑惑的,为啥是每个线程new一个实例进行签名和验签,难道上面的实现是非线程安全的么。
public classSignManage {
privateSignManage(){}
public staticIAsymmetricSign getSigner(String type) throwsException {
if(type.equals("RSA2")) {
return newRSA2Sign();}
if(type.equals("RSA")) {
return newRSASign();}
if(type.equals("sm")) {
return newSMSign();}
throw newException("暂不支持该签名方式");}
}
参考实现:

实现还依赖base64编码和解码,网上实现很多,也可以直接copy alipay-sdk-java中的实现。
具体的业务实现中需要通信的两方交换公钥。
假设A和B通信 , A需要生成一对钥匙,然后把公钥提供供B。 B也需要生成一对钥匙,把公钥提供给A。
A给B发消息 , A使用A的私钥进行签名或者加密,B收到消息后使用A提供的公钥进行验签或者解密。
B给A发消息 , B使用B的私钥进行签名或者加密,A收到消息后使用B提供的公钥进行验签或者解密。
之所以这么实现是因为私钥是万万不能泄露的。
同时应该告知对方使用的具体的签名算法。
更安全的做法是交换公钥证书,这就需要把公钥转换成证书,具体如何操作下一篇博客进行整理。
生成私钥和公钥,直接使用支付宝开放平台的开发助手工具。
