Java签名

本文介绍了数字签名的概念及其在防止文件篡改和替换中的作用,详细阐述了签名的生成与验证过程。通过使用非对称加密算法RSA和摘要算法MD5,确保了数据的完整性和来源可靠性。Java代码示例展示了如何生成和验证签名,以及在实际应用场景中的签名规则。此外,还讨论了时间戳和流水号nonce在防止重复提交和数据失效中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java-签名

签名是什么

现实中,由于我们每个人的笔迹近似独一无二,所以一旦我们在文件中签字就无法抵赖说不是自己签的,因为对方可以做笔迹鉴定。

计算机的世界更加错综复杂,A向B发送了一个文件,中途可能别拦截,然后可能被篡改或者替换,B怎么知道收到的文件一定是A发送的原件呢?

为了解决这个问题,聪明的计算机大佬们发明了数字签名这么个东东,数字签名并不是说这个签名是一串数字,而是为了区分传统的手写签名。
A发送文件的同时也把这个文件的签名发送给B,B只要验证文件签名是正确的,就可以认为文件是A发送的而且是原件。

也就是说,签名可以让B很自信的确定收到的文件是否被 篡改替换

多处用于金钱交易方面和重大信息传递方面

签名是怎么产生的

我们先来看看签名是怎么生成的,简单来说有如下几步:

生成唯一的非对称加密公钥和私钥对,一般是RSA算法;
对要发送的文件计算摘要,一般是MD5算法;
使用私钥对摘要进行加密,得到签名
为什么要计算摘要呢?主要是出于性能考虑,非对称加密安全性较好,但是不足之处是加密和解密过程复杂,如果要加密的字符很多多性能影响会很大。所以一般没有人会对整个问价进行加密生成签名,虽然这样也能达到签名的目的。

为什么使用私钥加密?大部分情况下,我们使用公钥加密、私钥解密,但是签名却是使用私钥加密,公钥解密。这是因为如果用公钥加密,则必须把私钥发送给要验证签名的一方,不仅麻烦而且很危险;而使用私钥加密,公钥验证方可以很容易得到,不存在泄漏风险,而且由于发送的签名只是摘要,就算被拦截用公钥解密,得到的信息也只是一串字符,没有任何意义。

综上所示,签名的生成方生成签名,并把签名和公钥发送给验证方,验证方根据公钥解密得到摘要,然后对接收到的文件重新计算摘要,两个摘要一样就说明文件没有被替换和篡改。

签名 VS 篡改+替换

为什么签名可以防止篡改呢?因为签名是通告摘要算法(MD5)产生的,任何一位数据的修改都会导致摘要发生变化,所以验证方只要判断签名解密后的摘要和文件计算的摘要一致,就说明文件未被篡改。

为什么签名可以防止文件被整个替换呢?如果攻击者另外找一个假文件,自己生成假的签名呢?别忘了,因为签名生成方产生的公钥和私钥对是唯一的,而且公钥验证方可以很容易获取到,如果签名是伪造的,则解密会出现错误,或者解密得到的摘要和源文件摘要会大不相同。

综上所示,B通过验证签名就可以知道这个文件一定是A发送的,而且一定是A发送的原件,所以就可以无条件的信任这个文件啦~

生成签名和验证签名工具类

在一些项目中,客户端在调用服务的接口时,通常需要设置签名验证,以保证对客户端的认证。在签名过程中一般每个公司都有自己的签名规则和签名算法,广泛使用的是使用非对称加密算法RSA为核心,在客户端使用私钥对参数进行加密生成一个密钥,传给服务端,然后在服务端再对这个这个密钥使用公钥进行解密,解密出来的字符串参数与客户端传过来的参数进行对比,如果一样,验证成功。

总结起来就是:

客户端

首先获取所有的参数,然后对他们进行排序,生成一个字符串

对这个字符串MD5加密,然后转为大写

然后使用私钥对MD5再次加密,生成最终的签名sign

把这个签名sign传给服务端

服务端

获取所有的参数

把参数中签名sign参数去除,然后排序,生成一个字符串

对这个字符串MD5加密,然后转为大写

使用公钥对sign字符串进行解密获取一个String,然后和第三步中获取的字符串相对,如果相等,则验证成功

下面我们就通过以上规则实现客户端的签名和服务端的验证。

import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class SignatureUtil{

    /**得到产生的私钥/公钥对 
     * @return KeyPair
     */
    public static KeyPair getKeypair(){
        //产生RSA密钥对(myKeyPair)  
        KeyPairGenerator myKeyGen = null;
        try {
            myKeyGen = KeyPairGenerator.getInstance("RSA");
            myKeyGen.initialize(512); //最低大小512
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        KeyPair myKeyPair = myKeyGen.generateKeyPair();
        return myKeyPair;
    }
    /**根据私钥和信息生成签名 
     * @param privateKey
     * @param data
     * @return 签名的Base64编码
     */
    public static String getSignature(PrivateKey privateKey,String data){
        Signature sign;
        String res = "";
        try {
            sign = Signature.getInstance("MD5WithRSA");
            sign.initSign(privateKey);
            sign.update(data.getBytes());
            byte[] signSequ = sign.sign();
            res = Base64.getEncoder().encodeToString(signSequ);
        }catch(NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            
            e.printStackTrace();
        }
        return res;
    }

    /**验证签名 
     * @param publicKey 公钥的Base64编码 
     * @param sign 签名的Base64编码 
     * @param data 生成签名的原数据
     * @return
     */
    public static boolean verify(String publicKey, String sign, String data){
        boolean res = true;
        try {
            byte[] keyBytes = Base64.getDecoder().decode(publicKey);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicK = keyFactory.generatePublic(keySpec);

            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initVerify(publicK);
            signature.update(data.getBytes());
            res = signature.verify(Base64.getDecoder().decode(sign));
        }catch(NoSuchAlgorithmException | InvalidKeyException | SignatureException | InvalidKeySpecException e) {
            
            e.printStackTrace();
        }
        return res;
    }

    //将字符串私密钥还原  PrivateKey 对象
    public static PrivateKey loadPrivateKey(String key64)  {
        byte[] clear = Base64.getDecoder().decode(key64.getBytes());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(clear);
        KeyFactory fact = null;
        try {
            fact = KeyFactory.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        PrivateKey priv = null;
        try {
            priv = fact.generatePrivate(keySpec);
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        Arrays.fill(clear, (byte) 0);
        return priv;

    }
    //将字符串公秘钥还原  PublicKey 对象
    public static PublicKey loadPublicKey(String stored)
    {
        byte[] data = Base64.getDecoder().decode((stored.getBytes()));
        X509EncodedKeySpec spec = new X509EncodedKeySpec(data);
        PublicKey publicKey=null;
        try {
            KeyFactory fact = KeyFactory.getInstance("RSA");
            try {
                publicKey= fact.generatePublic(spec);
            } catch (InvalidKeySpecException e) {
                e.printStackTrace();
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return publicKey;
    }

    final static KeyPair keyPair = getKeypair();
    /*(1)生成公钥和私钥对*/
    public static Map<String,String> key(){
        Map<String,String> map=new HashMap<String,String>(){{
            put("publicKey",Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()));
            put("privateKey",Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()));
        }};

        return   map;
    }

    /***
     * 生成签名
    * @Author: HuAnmin
    * @email:  3426154361@qq.com
    * @Date:   2021/4/6 0:06
    * @param:  privateKey  私密钥
    * @param:  data  需要签名的数据
    * @return: java.lang.String
    * @Description: 方法功能描述....
    */
    public static String  getSignature(String privateKey,String data){

        return  getSignature(loadPrivateKey(privateKey),data);
    }



    public static void main(String[] args)  {


        System.out.println("公钥:" + key().get("publicKey"));
        System.out.println("私钥:" + key().get("privateKey"));

        String data = "给我签名吧!";
        /*(2)用私钥生成签名*/
        String signature = getSignature(key().get("privateKey"), data);
        System.out.println("签名是:" +signature );


        /*(3)利用公秘钥 验证签名*/
        System.out.println("验证签名的结果是:" + verify(key().get("publicKey"),signature,data));

    }
} 

如果服务器发送给客户端数据后严格点是要加上时间戳的timestamp

客户端请求服务端数据,服务端发送给客户端的数据,如果在指定时间内客户端没有回复数据那么,将数据失效

为了避免多次客户端提交相同请求,服务端返回多次数据, 在请求的参数中加入流水号nonce(防止重复提交),至少为10位。在签名验证成功后,判断是否重复提交; 原理就是结合redis,判断是否已经提交过

就比如一个人手机卡了按下了好几次支付那么如果没有设置nonce 那么这几次订单都将成功多给别人付了好几次的钱,

当接口发送数据到服务端时候如果服务端没有执行那么redis就没有这个nonce,如果执行成功的那么redis就有当前请求的nonce下次…,在遇到新的请求的时候获取请求的nonce和redis中的nonce对比如果相同的那么就能判断是相同的接口请求,不用执行当前请求 比如: 微信支付 , 商品下单 , 商品付款…

点赞 -收藏-关注-便于以后复习和收到最新内容
有其他问题在评论区讨论-或者私信我-收到会在第一时间回复
如有侵权,请私信联系我
感谢,配合,希望我的努力对你有帮助^_^

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胡安民

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值