Java 实现 RSA 非对称加密算法的签名与验签

本文链接: https://blog.csdn.net/xietansheng/article/details/88368326

1. RSA 签名/验签 简介

RSA 非对称加密算法,除了用来加密/解密数据外,还可以用于对数据(文件)的 签名验签,可用于确认数据或文件的完整性与签名者(所有者)。

RSA 密钥对的生成与加密/解密见上一篇: Java 实现 RSA 非对称加密算法:生成密钥对、保存/读取密钥、加密/解密

RSA 密钥对在使用时通常:

  • 加密/解密:通常使用 公钥加密,私钥解密
  • 签名/验签:通常使用 私钥签名,公钥验签

Android 安装包 APK 文件的签名,是 RSA 签名验签的典型应用:Android 打包后,用私钥对 APK 文件进行签名,并把公钥和签名结果放到 APK 包中。下次客户端升级 APK 包时,根据新的 APK 包和包内的签名信息,用 APK 包内的公钥验签校验是否和本地已安装的 APK 包使用的是同一个私钥签名,如果是,则允许安装升级。

2. RSA 签名/验签 代码实例

package com.xiets.rsa;

import sun.misc.BASE64Encoder;

import java.security.*;

/**
 * @author xietansheng
 */
public class Main {

    public static void main(String[] args) throws Exception {
        /*
         * 1. 先生成一对 RSA 密钥, 用于测试
         */
        // 随机生成一对 RAS 密钥(包含公钥和私钥)
        KeyPair keyPair = generateKeyPair();
        // 获取 公钥 和 私钥
        PublicKey pubKey = keyPair.getPublic();
        PrivateKey priKey = keyPair.getPrivate();

        /*
         * 2. 原始数据
         */
        String data = "你好, World";

        /*
         * 3. 私钥签名: 对数据进行签名, 计算签名结果
         */
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance("Sha1WithRSA");
        // 用私钥初始化签名工具
        sign.initSign(priKey);
        // 添加要签名的数据
        sign.update(data.getBytes());
        // 计算签名结果(签名信息)
        byte[] signInfo = sign.sign();
        // 输出签名结果的 Base64 字符串
        System.out.println(new BASE64Encoder().encode(signInfo));

        /*
         * 4. 公钥验签: 用公钥校验数据的签名是否来自指定的私钥
         */
        // 根据指定算法获取签名工具
        sign = Signature.getInstance("Sha1WithRSA");
        // 用公钥初始化签名工具
        sign.initVerify(pubKey);
        // 添加要校验的数据
        sign.update(data.getBytes());
        // 校验数据的签名信息是否正确,
        // 如果返回 true, 说明该数据的签名信息来自该公钥对应的私钥,
        // 同一个私钥的签名, 数据和签名信息一一对应, 只要其中有一点修改, 则用公钥无法校验通过,
        // 因此可以用私钥签名, 然后用公钥来校验数据的完整性与签名者(所有者)
        boolean verify = sign.verify(signInfo);
        System.out.println(verify);
    }

    /**
     * 随机生成 RSA 密钥对(包含公钥和私钥)
     */
    private static KeyPair generateKeyPair() throws Exception {
        // 获取指定算法的密钥对生成器
        KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA");
        // 初始化密钥对生成器(指定密钥长度, 使用默认的安全随机数源)
        gen.initialize(2048);
        // 随机生成一对密钥(包含公钥和私钥)
        return gen.generateKeyPair();
    }

}

2. RSA 签名/验签工具类: RSASignUtils.java

为了方便在代码中直接使用 RSA 签名/验签,将 RSA 签名/验签步骤封装成工具类。

RSASignUtils.java工具类完整代码:

package com.xiets.rsa;

import java.io.*;
import java.security.*;

/**
 * RSA 签名验签工具类
 *
 * @author xietansheng
 */
public class RSASignUtils {

    /** 秘钥对算法名称 */
    private static final String ALGORITHM = "RSA";

    /** 密钥长度 */
    private static final int KEY_SIZE = 2048;

    /** 签名算法 */
    private static final String SIGNATURE_ALGORITHM = "Sha1WithRSA";

    /**
     * 随机生成 RSA 密钥对(包含公钥和私钥)
     */
    public static KeyPair generateKeyPair() throws Exception {
        // 获取指定算法的密钥对生成器
        KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);

        // 初始化密钥对生成器(指定密钥长度, 使用默认的安全随机数源)
        gen.initialize(KEY_SIZE);

        // 随机生成一对密钥(包含公钥和私钥)
        return gen.generateKeyPair();
    }

    /**
     * 私钥签名(数据): 用私钥对指定字节数组数据进行签名, 返回签名信息
     */
    public static byte[] sign(byte[] data, PrivateKey priKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用私钥初始化签名工具
        sign.initSign(priKey);

        // 添加要签名的数据
        sign.update(data);

        // 计算签名结果(签名信息)
        byte[] signInfo = sign.sign();

        return signInfo;
    }

    /**
     * 公钥验签(数据): 用公钥校验指定数据的签名是否来自对应的私钥
     */
    public static boolean verify(byte[] data, byte[] signInfo, PublicKey pubKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用公钥初始化签名工具
        sign.initVerify(pubKey);

        // 添加要校验的数据
        sign.update(data);

        // 校验数据的签名信息是否正确,
        // 如果返回 true, 说明该数据的签名信息来自该公钥对应的私钥,
        // 同一个私钥的签名, 数据和签名信息一一对应, 只要其中有一点修改, 则用公钥无法校验通过,
        // 因此可以用私钥签名, 然后用公钥来校验数据的完整性与签名者(所有者)
        boolean verify = sign.verify(signInfo);

        return verify;
    }

    /**
     * 私钥签名(文件): 用私钥对文件进行签名, 返回签名信息
     */
    public static byte[] signFile(File file, PrivateKey priKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用私钥初始化签名工具
        sign.initSign(priKey);

        InputStream in = null;

        try {
            in = new FileInputStream(file);

            byte[] buf = new byte[1024];
            int len = -1;

            while ((len = in.read(buf)) != -1) {
                // 添加要签名的数据
                sign.update(buf, 0, len);
            }

        } finally {
            close(in);
        }

        // 计算并返回签名结果(签名信息)
        return sign.sign();
    }

    /**
     * 公钥验签(文件): 用公钥校验指定文件的签名是否来自对应的私钥
     */
    public static boolean verifyFile(File file, byte[] signInfo, PublicKey pubKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用公钥初始化签名工具
        sign.initVerify(pubKey);

        InputStream in = null;

        try {
            in = new FileInputStream(file);

            byte[] buf = new byte[1024];
            int len = -1;

            while ((len = in.read(buf)) != -1) {
                // 添加要校验的数据
                sign.update(buf, 0, len);
            }

        } finally {
            close(in);
        }

        // 校验签名
        return sign.verify(signInfo);
    }

    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }

}

RSASignUtils类中主要有以下几个公开静态方法:

// 生成 RSA 秘钥对(包含公钥和私钥)
KeyPair generateKeyPair()

// 私钥签名(数据)
byte[]  sign(byte[] data, PrivateKey priKey)
// 公钥验签(数据)
boolean verify(byte[] data, byte[] signInfo, PublicKey pubKey)

// 私钥签名(文件)
byte[]  signFile(File file, PrivateKey priKey)
// 公钥验签(文件)
boolean verifyFile(File file, byte[] signInfo, PublicKey pubKey)

工具类的使用:

package com.xiets.rsa;

import sun.misc.BASE64Encoder;

import java.io.File;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @author xietansheng
 */
public class Main {

    public static void main(String[] args) throws Exception {
        // 随机生成一对 RAS 密钥(包含公钥和私钥)
        KeyPair keyPair = RSASignUtils.generateKeyPair();
        // 获取 公钥 和 私钥
        PublicKey pubKey = keyPair.getPublic();
        PrivateKey priKey = keyPair.getPrivate();

        // 原始数据
        String data = "你好, World";

        // 私钥签名(数据): 对数据进行签名, 返回签名结果
        byte[] signInfo = RSASignUtils.sign(data.getBytes(), priKey);
        System.out.println("数据签名信息:" + new BASE64Encoder().encode(signInfo));

        // 公钥验签(数据): 用公钥校验数据的签名是否来自公钥对应的私钥
        boolean verify = RSASignUtils.verify(data.getBytes(), signInfo, pubKey);
        System.out.println("数据验签结果:" + verify);

        /*
         * 对文件进行签名和验签
         */
        File file = new File("demo.jpg");

        // 私钥签名(文件): 对文件进行签名, 返回签名结果
        byte[] fileSignInfo = RSASignUtils.sign(data.getBytes(), priKey);
        System.out.println("文件签名信息:" + new BASE64Encoder().encode(fileSignInfo));

        // 公钥验签(文件): 用公钥校验文件的签名是否来自公钥对应的私钥
        boolean fileVerify = RSASignUtils.verify(data.getBytes(), signInfo, pubKey);
        System.out.println("文件验签结果:" + fileVerify);
    }

}

1、数字签名原理 用RSA算法做数字签名,总的来说,就是签名者用私钥参数d加密,也就是签名;验证者用字者的公钥参数e解密来完成认证。 下面简要描述数字签名和认证的过程。 (1)、生成密钥 为用户随机生成一对密钥:公钥(e,n)和私钥(d,n). (2)、签名过程 a) 计算消息的散列值H(M). b) 用私钥(d,n)加密散列值:s=(H(M)) mod n,签名结果就是s. c) 发送消息和签名(M,s). (3)、认证过程 a) 取得发送方的公钥(e,n). b) 解密签名s:h=s mod n. c) 计算消息的散列值H(M). d) 比较,如果h=H(M),表示签名有效;否则,签名无效。 根据上面的过程,我们可以得到RSA数字签名的框图如图2-1: 图 2-1 RSA数字签名框图 2、 假设Alice想和Bob通信,以本地两个文件夹Alice和Bob模拟两个用户,实现消息M和签名的模拟分发 (1)、Alice通过RSA算法生成一对密钥:公钥(e,n)和私钥(d,n),将公私钥分别存入pubKey.txt和priKey.txt中。 pubKey.txt中公钥如下: priKey.txt中私钥如下: (2)、将Alice中的pubKey.txt拷到Bob中,模拟公玥的分发。 (3)、将Alice中的消息info.txt做散列,将散列后的值存入hashInfo.txt中。 (4)、将Alice中的消息hashInfo.txt和签名sign.txt拷到Bob中,实现M密文状态下的签名与模拟分发、消息传递。 (5)Bob取得公钥pubKey.txt,用公钥解密签名,计算消息的散列值H(M).比较,如果h=H(M),表示签名有效;否则,签名无效。 后台运行结果如下:
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

谢TS

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

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

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

打赏作者

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

抵扣说明:

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

余额充值