Java代码实现数字信封

1. 前言

        本篇博客是工作经验总结,如果您发现此篇博客有疏漏或有待改进之处,欢迎评论区交流。

2. 数字信封

        数字信封使用的是接收者的非对称密钥对。即:用接收者的公钥加密,且只能由接收者的私钥解密。其实现过程如下:

        (1)信息发送者发送信息时,首先生成一个共享的对称密钥,用该对称密钥加密要发送的明文,得到数据密文;

        (2)信息发送者用接收者的公钥加密此对称密钥,形成对称密钥密文;

        (3)信息发送者将(1)和(2)的结果结合在一起,形成数字信封,一并传给信息接收者。信息接收者接到数据后,使用自己的私钥解密对称密钥密文,得到共享的对称密钥;再用此对称密钥解密数据密文,得到真正的原始明文;

3. 数字信封和数字签名

        简要说明数字信封和数字签名的区别。数字签名和数字信封都使用非对称密钥算法,但实现过程相反,使用的密钥对不同。

        (1)数字签名使用的是发送方的密钥对,发送方用自己的私钥进行加密,接收方用发送方的公钥进行解密,这是一个一对多的关系,任何拥有发送方公钥的人都可以验证数字签名的正确性。

        (2)数字信封使用的是接收方的密钥对,这是多对一的关系,任何知道接收方公钥的人都可以向接收方发送加密信息,只有惟一拥有接收方私钥者才能对信息解密。

        (3)数字签名只采用了非对称密钥加密算法和数字摘要技术,能够保证发送信息的完整性(但未保证信息的机密性);数字信封采用了对称密钥加密算法和非对称密钥加密算法相结合的方法,能保证信息传输的机密性。

4. 数字信封代码实现

4.1. 需要引入的包

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.70</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.8</version>
</dependency>

4.2 实现思路

本示例的对称加密使用SM4算法,非对称加密使用SM2算法,算法库使用BC库


创建数字信封实现思路:
1. 根据传递的对称密钥算法,如SM4/CBC/PKCS5PADDING, 生成对称密钥;
2. 使用对称密钥加密数据原文得到数据密文;
3. 使用接收方公钥加密对称密钥得到对称密钥密文;
4. 按照数据密文|对称密钥密文|对称加密算法类型|IV值的形式把结果拼接起来。


解密数字信封实现思路:
1. 传递上述第4步中的结果和私钥到解密数字信封接口中;
2. 获取其中的数据密文、对称密钥密文、对称加密算法类型和IV值;
3. 使用私钥解密对称密钥密文,得到对称密钥;
4. 使用对称密钥、对称密钥算法和IV值解密数据密文得到数据原文。


考虑下数字信封的实际应用场景是什么?

4.3 具体实现

4.3.1 代码实现

        代码中有相应的注释,此处就不再阐述代码逻辑了。

package com.example.enveloped;


import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Objects;

/**
 * <p>本示例的对称加密使用SM4算法,非对称加密使用SM2算法,算法库使用BC库</p>
 *
 *
 * <p>创建数字信封实现思路:
 *
 * <p>1. 根据传递的对称密钥算法,如SM4/CBC/PKCS5PADDING, 生成对称密钥;
 * <p>2. 使用对称密钥加密数据原文得到数据密文;
 * <p>3. 使用接收方公钥加密对称密钥得到对称密钥密文;
 * <p>4. 按照数据密文|对称密钥密文|对称加密算法类型|IV值的形式把结果拼接起来。
 *
 * <p>解密数字信封实现思路:
 * <p>1. 传递上述第4步中的结果和私钥到解密数字信封接口中;
 * <p>2. 获取其中的数据密文、对称密钥密文、对称加密算法类型和IV值;
 * <p>3. 使用私钥解密对称密钥密文,得到对称密钥;
 * <p>4. 使用对称密钥、对称密钥算法和IV值解密数据密文得到数据原文。
 *
 *
 * <p>考虑下数字信封的实际应用场景是什么?
 */
public class CreateEnveloped {

    // iv值的长度(字节)
    public static final int IV_LENGTH = 16;
    // 默认iv值
    public static final String IV = "cPqChVEW9OpnJQTkcObQvA==";
    // 加密模式
    public static final String ECB = "ECB";
    public static final String CBC = "CBC";
    public static final String BASE64_SEPARATOR = "|";
    private static final String KEY_ALGORITHM = "EC";

    /**
     * 解开数字信封
     *
     * @param envelopedData 数字信封数据
     * @param priKey        私钥
     * @return 信封里面的数据
     */
    public static String openEnvelope(String envelopedData, String priKey) {

        // 获取数据密文、密钥密文、加密算法和IV值
        Base64.Decoder decoder = Base64.getDecoder();
        String[] parts = envelopedData.split("\\|");
        byte[] encryptData = decoder.decode(parts[0]);
        byte[] encryptKeyBytes = decoder.decode(parts[1]);
        String encAlgorithm = new String(decoder.decode(parts[2]));
        byte[] iv = decoder.decode(parts[3]);

        // 私钥解密对称密钥密钥
        PrivateKey privateKey = getPrivateKey(priKey);
        // 此处使用了hutool工具提供的国密SM2工具类
        SM2 sm2 = new SM2(privateKey, null);
        byte[] key = sm2.decrypt(encryptKeyBytes, KeyType.PrivateKey);

        // 使用对称密钥解密数据密文
        byte[] plainTextBytes;
        if (encAlgorithm.contains(CBC)) {
            plainTextBytes = SM4Util.decrypt(encAlgorithm, key, iv, encryptData);
        }else {
            plainTextBytes = SM4Util.decrypt(encAlgorithm, key, null, encryptData);
        }
        return new String(plainTextBytes);
    }

    /**
     * 创建数字信封
     *
     * @param encAlgorithm 对称加密算法(由使用者自己指定)
     * @param pubKey       接收方的公钥
     * @param plainText    数据原文
     * @return 封装的数字信封数据
     */
    public static String createEnvelope(String encAlgorithm, String pubKey, String plainText) {

        // 原文
        byte[] dataToUse = plainText.getBytes();

        // 生成用于加密数据的对称密钥
        String alg = encAlgorithm.substring(0, encAlgorithm.indexOf("/"));
        Key key = SM4Util.genKey(alg);

        // 使用对称密钥加密明文数据
        byte[] ivToUse;
        byte[] encryptData;
        if (encAlgorithm.contains(CBC)) {
            ivToUse = getIv(IV_LENGTH);
            encryptData = SM4Util.encrypt(encAlgorithm, key.getEncoded(), ivToUse, dataToUse);
        } else {
            ivToUse = Base64.getDecoder().decode(IV);
            encryptData = SM4Util.encrypt(encAlgorithm, key.getEncoded(), null, dataToUse);
        }

        // 通过接收方的公钥加密对称密钥
        PublicKey publicKey = getPublicKey(pubKey);
        // 此处使用了hutool工具提供的国密SM2工具类
        SM2 sm2 = new SM2(null, publicKey);
        byte[] encryptKeyBytes = sm2.encrypt(key.getEncoded(), KeyType.PublicKey);

        return assembleEnvelop(encryptData, encryptKeyBytes, encAlgorithm.getBytes(), ivToUse);
    }

    /**
     * 组装数字信封数据
     *
     * @param encryptData          加密后的数据密文
     * @param encryptAsymmetricKey 加密后的对称密钥密文
     * @param encAlg               对称加密算法
     * @param ivToUse              IV值(如果使用者使用了CBC加密模式,则随机生成一个IV值;如果使用者使用了ECB加密模式,则给一个默认的IV值。这样做是为了让结果统一)
     * @return
     */
    private static String assembleEnvelop(byte[] encryptData, byte[] encryptAsymmetricKey, byte[] encAlg, byte[] ivToUse) {
        Base64.Encoder encoder = Base64.getEncoder();
        return encoder.encodeToString(encryptData) + BASE64_SEPARATOR +
                encoder.encodeToString(encryptAsymmetricKey) + BASE64_SEPARATOR +
                encoder.encodeToString(encAlg) + BASE64_SEPARATOR +
                encoder.encodeToString(ivToUse);
    }

    /**
     * 根据十六进制的公钥字符串,获取公钥对象
     *
     * @param pubKey 十六进制的公钥字符串
     * @return 公钥对象
     */
    private static PublicKey getPublicKey(String pubKey) {
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(HexUtil.decodeHex(pubKey));
        KeyFactory keyFactory;
        PublicKey publicKey;
        try {
            keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
        return publicKey;
    }

    /**
     * 根据十六进制的私钥字符串,获取私钥对象
     *
     * @param priKey 十六进制的私钥对象
     * @return 私钥对象
     */
    private static PrivateKey getPrivateKey(String priKey) {
        PrivateKey privateKey;
        try {
            byte[] privateKeyBytes = HexUtil.decodeHex(priKey);
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        return privateKey;
    }

    /**
     * 获取指定长度的随机数,此处充当IV值
     *
     * @param length 随机数长度
     * @return 随机数
     */
    private static byte[] getIv(int length) {
        byte[] randomData = new byte[length];
        RandomUtil.getSecureRandom().nextBytes(randomData);
        return randomData;
    }
}

// SM4工具类
class SM4Util {

    public static final int SYM_KEY_SIZE = 128;
    public static final String SM4 = "SM4";

    // 使用BC库
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 生成指定算法的对称密钥 - 对象
     *
     * @param algorithmType 对称算法,如SM4
     * @return 对称密钥对象
     */
    public static Key genKey(String algorithmType) {
        KeyGenerator keyGenerator;
        try {
            keyGenerator = KeyGenerator.getInstance(algorithmType, BouncyCastleProvider.PROVIDER_NAME);
            keyGenerator.init(SYM_KEY_SIZE);
            return keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 对称加密
     *
     * @param cipherAlgorithm 对称加密算法,如 SM4/CBC/PKCS5PADDING
     * @param key             对称密钥
     * @param iv              iv值
     * @param originalText    原文字节数组
     * @return 密文字节数组
     */
    public static byte[] encrypt(String cipherAlgorithm, byte[] key, byte[] iv, byte[] originalText) {
        byte[] encryptData;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
            Key secretKey = new SecretKeySpec(key, SM4);
            if (Objects.isNull(iv)) {
                cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            } else {
                IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
            }
            encryptData = cipher.doFinal(originalText);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
            throw new RuntimeException(e.getMessage());
        }
        return encryptData;
    }


    /**
     * 对称解密
     *
     * @param cipherAlgorithm 对称解密算法,如 SM4/CBC/PKCS5PADDING
     * @param key             对称密钥
     * @param iv              iv值
     * @param encryptText     密文字节数组
     * @return 原文字节数组
     */
    public static byte[] decrypt(String cipherAlgorithm, byte[] key, byte[] iv, byte[] encryptText) {
        byte[] encryptData;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm, BouncyCastleProvider.PROVIDER_NAME);
            Key secretKey = new SecretKeySpec(key, SM4);
            if (Objects.isNull(iv)) {
                cipher.init(Cipher.DECRYPT_MODE, secretKey);
            } else {
                IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
                cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
            }
            encryptData = cipher.doFinal(encryptText);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
            throw new RuntimeException(e.getMessage());
        }
        return encryptData;
    }
}

4.3.2 测试代码

package com.example.enveloped;

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.asymmetric.SM2;
import cn.hutool.crypto.symmetric.SM4;
import org.junit.Test;

import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Arrays;
import java.util.Base64;

public class EnvelopedTest {

    public static void main(String[] args) {
        //printKeyPair();

        String privateKey = "308193020100301306072a8648ce3d020106082a811ccf5501822d04793077020101042073f9ec0500d25e731f505de740f30a71e095a482101e7471b52cbb9e06e1813fa00a06082a811ccf5501822da1440342000452127847a8b7107848e5020295d08541e72551490e061928ce641c87714706f75211f964265a79ca92a49bc0e8b24de848876f01fdd368cd6fbdc24151cb8e8f";
        String publicKey = "3059301306072a8648ce3d020106082a811ccf5501822d0342000452127847a8b7107848e5020295d08541e72551490e061928ce641c87714706f75211f964265a79ca92a49bc0e8b24de848876f01fdd368cd6fbdc24151cb8e8f";

        String envelope = CreateEnveloped.createEnvelope("SM4/CBC/PKCS5PADDING", publicKey, "123QAZ");
        System.out.println("封装的数字信封:" + envelope);

        String result = CreateEnveloped.openEnvelope(envelope, privateKey);
        System.out.println("解密的数字信封:" + result);

    }

    private static void printKeyPair() {
        SM2 sm2 = new SM2();
        String privateKeyHex = HexUtil.encodeHexStr(sm2.getPrivateKey().getEncoded());
        String publicKeyHex = HexUtil.encodeHexStr(sm2.getPublicKey().getEncoded());
        System.out.println("priKey: " + privateKeyHex);
        System.out.println("pubKey: " + publicKeyHex);
    }
}

4.3.2 测试结果

5. 如果此篇博客对你有帮助或启发,感谢点个赞~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/923915.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第 4 章 Java 并发包中原子操作类原理剖析

原子变量操作类 AtomicLong 是原子性递增或者递减类&#xff0c;其内部使用 Unsafe 来实现&#xff0c;AtomicLong类也是在 rt.jar 包下面的&#xff0c;AtomicLong 类就是通过 BootStarp 类加载器进行加载的。这里的原子操作类都使用 CAS 非阻塞算法 private static final lon…

Android调起系统分享图片到其他应用

Android调起系统分享图片到其他应用 有时候分享不想接第三方的&#xff0c;其实如果你的分享要求不是很高&#xff0c;调系统的分享也是可以的。 一、思路&#xff1a; 用intent.action Intent.ACTION_SEND 二、效果图&#xff1a; 三、关键代码&#xff1a; //这个是分享…

C++中虚继承为什么可以解决菱形继承的数据冗余问题

在C中菱形继承会有数据冗余的问题发生&#xff0c;我们可以使用虚继承来解决&#xff0c;那虚继承的原理是什么&#xff0c;为什么它可以解决这个问题。 菱形继承的数据冗余问题 class A { public:int data; };class B : public A {};class C : public A {};class D : public…

LSA详情与特殊区域

LSA是构成LSDB的重要原材料&#xff0c;在OSPF中发挥很大作用。 报文 通用头部 LS age&#xff1a;LSA寿命&#xff0c;0-3600s Options&#xff1a;可选项 LS type&#xff1a;LSA类型&#xff0c;三要素之一 Link State ID&#xff1a;LSAID 三要素之一 Advertising Ro…

Kubeadm 安装 Kubernetes 高可用集群 v1.30.0

1、修改主机名&#xff08;各个节点&#xff09; hostnamectl set-hostname xxx2、hosts 文件加入主机名&#xff08;全部节点&#xff09; cat /etc/hosts 192.168.88.5 master1 192.168.88.6 master2 192.168.88.7 master3 192.168.88.8 node13、关闭防火墙&#xff08;全部…

泥石流灾害风险评估与模拟丨AI与R语言、ArcGIS、HECRAS融合,提升泥石流灾害风险预测的精度和准确性

目录 第一章 理论基础 第二章 泥石流风险评估工具 第三章 数据准备与因子提取 第四章 泥石流灾害评价 第五章 HECRAS软件的应用 第六章 操作注意事项与模型优化 泥石流灾害的频发与严重后果&#xff0c;已成为全球范围内防灾减灾工作的重大挑战。随着科技的不断进步&…

自由学习记录(25)

只要有修改&#xff0c;子表就不用元表的参数了&#xff0c;用自己的参数&#xff08;只不过和元表里的那个同名&#xff09; 子表用__index“继承”了父表的值&#xff0c;此时子表仍然是空表 一定是创建这样一个同名的变量在原本空空的子表里&#xff0c; 传参要传具体的变…

leetcode 3206. 交替组 I 简单

给你一个整数数组 colors &#xff0c;它表示一个由红色和蓝色瓷砖组成的环&#xff0c;第 i 块瓷砖的颜色为 colors[i] &#xff1a; colors[i] 0 表示第 i 块瓷砖的颜色是 红色 。colors[i] 1 表示第 i 块瓷砖的颜色是 蓝色 。 环中连续 3 块瓷砖的颜色如果是 交替 颜色&…

彻底解决 macOS 下Matplotlib 中文显示乱码问题

彻底解决 macOS 下Matplotlib 中文显示乱码问题 在使用 Python 的 Matplotlib 库进行数据可视化时&#xff0c;中文字符的显示常常会出现乱码问题&#xff0c;尤其在 macOS 系统上。在网上找了一大堆方法&#xff0c;花了很久&#xff0c;发现不是要安装各种字体就是要改配置&…

深度学习笔记24_天气预测

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、我的环境 1.语言环境&#xff1a;Python 3.9 2.编译器&#xff1a;Pycharm 3.深度学习环境&#xff1a;TensorFlow 2.10.0 二、GPU设置…

podman 源码 5.3.1编译

1. 构建环境 在麒麟V10服务器操作系统上构建&#xff1a;Kylin-Server-V10-GFB-Release-2204-Build03-ARM64.iso。由于只是编译 podman 源码&#xff0c;没必要特地在物理机或服务上安装一个这样的操作系统&#xff0c;故采用在虚拟机里验证。 2. 安装依赖 参考资料&#xf…

【K8S系列】深入解析 Kubernetes 中的 Deployment

Kubernetes&#xff08;K8s&#xff09;是一个开源的容器编排平台&#xff0c;旨在自动化应用程序的部署、扩展和管理。在 Kubernetes 中&#xff0c;Deployment 是一种用于管理无状态应用的工作负载资源&#xff0c;提供了丰富的功能&#xff0c;包括版本控制、滚动更新和回滚…

玩转 Burp Suite (1)

内容预览 ≧∀≦ゞ 玩转 Burp Suite (1)声明Burp Suite 简介Dashboard&#xff08;仪表盘&#xff09;1. 默认任务管理2. 暂停任务3. 新建扫描任务4. 使用总结 Target&#xff08;目标&#xff09;1. SIte Map &#xff08;站点地图&#xff09;2. Scope&#xff08;范围&#…

【ArcGISPro】Sentinel-2数据处理

错误 默认拉进去只组织了4个波段,但是实际有12个波段 解决方案 数据下载 Sentinel-2 数据下载-CSDN博客 数据处理 数据查看 创建镶嵌数据集 在数据管理工具箱中找到创建镶嵌数据集

智慧环保大数据解决方案

1. 智慧环保概述 智慧环保是“数字环保”的延伸&#xff0c;借助物联网技术整合环境监控对象&#xff0c;通过云计算实现环境管理与决策的智能化。其核心在于快速感知城市环境指标&#xff0c;保障人体健康与生命安全。 2. 智慧环保总体目标 智慧环保的总体目标是建立全面感…

【H2O2|全栈】JS进阶知识(八)ES6(4)

目录 前言 开篇语 准备工作 浅拷贝和深拷贝 浅拷贝 概念 常见方法 弊端 案例 深拷贝 概念 常见方法 弊端 逐层拷贝 原型 构造函数 概念 形式 成员 弊端 显式原型和隐式原型 概念 形式 constructor 概念 形式 原型链 概念 形式 结束语 前言 开篇语…

03-微服务搭建

1、搭建分布式基本环境 分布式组件 功能 SpringCloud Alibaba - Nacos 注册中心&#xff08;服务发现/注册&#xff09;、配置中心&#xff08;动态配置管理&#xff09; SpringCloud Alibaba - Sentinel 服务容错&#xff08;限流、降级、熔断&#xff09; SpringCloud …

Vue前端开发2.3.2-4 绑定指令

本文介绍了Vue中的绑定指令&#xff0c;包括属性绑定指令v-bind、事件绑定指令v-on以及双向数据绑定指令v-model。通过创建单文件组件&#xff0c;演示了如何使用这些指令来控制DOM属性、监听事件和实现表单输入与数据的双向同步。同时&#xff0c;探讨了v-model的修饰符如.num…

uniapp开发支付宝小程序自定义tabbar样式异常

解决方案&#xff1a; 这个问题应该是支付宝基础库的问题&#xff0c;除了依赖于官方更新之外&#xff0c;开发者可以利用《自定义 tabBar》曲线救国 也就是创建一个空内容的自定义tabBar&#xff0c;这样即使 tabBar 被渲染出来&#xff0c;但从视觉上也不会有问题 1.官方文…

双向链表、循环链表、栈

双向循环链表 class Node:#显性定义出构造函数def __init__(self,data):self.data data #普通节点的数据域self.next None #保存下一个节点的链接域self.prior None #保存前一个节点饿链接域 class DoubleLinkLoop:def __init__(self, node Node):self.head nodeself.siz…