如何实现RSA签名与验签

泪湿孤枕 提交于 2020-04-05 16:51:40

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提供的公钥进行验签或者解密。

之所以这么实现是因为私钥是万万不能泄露的。 

同时应该告知对方使用的具体的签名算法。

 

更安全的做法是交换公钥证书,如何生成公钥证书,如何验证公钥证书及从证书中提取公钥等问题下一篇博客进行整理。

生成私钥和公钥,直接使用支付宝开放平台的开发助手工具。

 

 

 

 

 

 

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!