项目安全-----加密算法实现

news2024/11/17 13:34:49

目录

对称加密算法

AES (ECB模式)

AES(CBC 模式)。

非对称加密


对称加密算法

对称加密算法,是使用相同的密钥进行加密和解密。使用对称加密算法来加密双方的通信的话,双方需要先约定一个密钥,加密方才能加密,接收方才能 解密。常用的加密算法,有 DES、3DES 和 AES,国密算法包括SM1,SM4和SM7。 目前,使用 DES 来加密数据非常不安全。因此,在业务代码中要避免使用 DES 加密。而 3DES 算法,是使用不同的密钥进行三次 DES 串联调用,虽然解决 了 DES 不够安全的问题,但是比 AES 慢,也不太推荐。我们来看看AES的算法,AES 算法有ECB、CBC、 CFB、OFB、CTR 模式

AES (ECB模式)

private static final String KEY = "secretkey1234567"; //密钥
    //测试ECB模式
    @GetMapping("ecb")
    public void ecb() throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
        test(cipher, null);
    }
    //获取加密秘钥帮助方法
    private static SecretKeySpec setKey(String secret) {
        return new SecretKeySpec(secret.getBytes(), "AES");
    }
    //测试逻辑
    private static void test(Cipher cipher, AlgorithmParameterSpec parameterSpec) throws Exception {
        //初始化Cipher
        cipher.init(Cipher.ENCRYPT_MODE, setKey(KEY), parameterSpec);
        //加密测试文本
        System.out.println("一次:" + Hex.encodeHexString(cipher.doFinal("abcdefghijklmnop".getBytes())));
        //加密重复一次的测试文本
        System.out.println("两次:" + Hex.encodeHexString(cipher.doFinal("abcdefghijklmnopabcdefghijklmnop".getBytes())));
        //下面测试是否可以通过操纵密文来操纵明文 
        //发送方账号
        byte[] sender = "1000000000012345".getBytes();
        //接收方账号
        byte[] receiver = "1000000000034567".getBytes();
        //转账金额
        byte[] money = "0000000010000000".getBytes();
        //加密发送方账号
        System.out.println("发送方账号:" + Hex.encodeHexString(cipher.doFinal(sender)));
        //加密接收方账号
        System.out.println("接收方账号:" + Hex.encodeHexString(cipher.doFinal(receiver)));
        //加密金额
        System.out.println("金额:" + Hex.encodeHexString(cipher.doFinal(money)));
        //加密完整的转账信息
        byte[] result = cipher.doFinal(ByteUtils.concatAll(sender, receiver, money));
        System.out.println("完整数据:" + Hex.encodeHexString(result));
        //用于操纵密文的临时字节数组
        byte[] hack = new byte[result.length];
        //把密文前两段交换
        System.arraycopy(result, 16, hack, 0, 16);
        System.arraycopy(result, 0, hack, 16, 16);
        System.arraycopy(result, 32, hack, 32, 16);
        cipher.init(Cipher.DECRYPT_MODE, setKey(KEY), parameterSpec);
        分区 业务常见问题 的第 16 页  cipher.init(Cipher.DECRYPT_MODE, setKey(KEY), parameterSpec);
        //尝试解密
        System.out.println("原始明文:" + new String(ByteUtils.concatAll(sender, receiver, money)));
        System.out.println("操纵密文:" + new String(cipher.doFinal(hack)));
    }

 两个相同明文分组产生的密文,就是两个相同的密文分组叠在一起。在不知道密钥的情况下,我们操纵密文实现了对明文数据的修改,对调了发送方账号 和接收方账号。所以说,ECB 模式虽然简单,但是不安全,不推荐使用。

AES(CBC 模式)。

private static final String initVector = "abcdefghijklmnop"; //初始化向量
@GetMapping("cbc")
public void cbc() throws Exception {
 Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
 IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
 test(cipher, iv);
}

可以看到,相同的明文字符串复制一遍得到的密文并不是重复两个密文分组,并且调换密文分组的顺序无法操纵明文。 不要在代码中写死一个固定的密钥和初始化向量,最好和之前提到的盐一样,是唯一、独立并且每次都变化的。推荐使用独立的加密服务来管控密钥、做 加密操作,千万不要把密钥和密文存在一个数据库,加密服务需要设置非常高的管控标准。数据库中不能保存明文的敏感信息,但可以保存脱敏的信息。 普通查询的时候,直接查脱敏信息即可。下面举个例子:

@Data
@Entity
public class UserData {
    @Id
    private Long id;
    private String idcard;//脱敏的身份证
    private Long idcardCipherId;//身份证加密ID
    private String idcardCipherText;//身份证密文
    private String name;//脱敏的姓名
    private Long nameCipherId;//姓名加密ID
    private String nameCipherText;//姓名密文
}
@Data
@Entity
public class CipherData {
    @Id
    @GeneratedValue(strategy = AUTO)
    private Long id;
    private String iv;//初始化向量
    private String secureKey;//密钥
}

 加密服务使用 GCM 模式( Galois/Counter Mode)的 AES-256 对称加密算法,也就是 AES-256-GCM,这是一种AEAD(Authenticated Encryption with Associated Data)认证加密算法,除了能实现普通加密算法提供的保密性之外,还能实现可认证性和密文完整性,是目前最推荐的 AES 模式。使用类似 GCM 的 AEAD 算法进行加解密,除了需要提供初始化向量和密钥之外,还可以提供一个 AAD(附加认证数据,additional authenticated data),用于验证未 包含在明文中的附加信息,解密时不使用加密时的 AAD 将解密失败。其实,GCM 模式的内部使用的就是 CTR 模式,只不过还使用了 GMAC 签名算法,对 密文进行签名实现完整性校验。

我们实现基于 AES-256-GCM 的加密服务,包含下面的主要逻辑:加密时允许外部传入一个 AAD 用于认证,加密服务每次都会使用新生成的随机值作为密 钥和初始化向量。在加密后,加密服务密钥和初始化向量保存到数据库中,返回加密 ID 作为本次加密的标识。应用解密时,需要提供加密 ID、密文和加 密时的 AAD 来解密。加密服务使用加密 ID,从数据库查询出密钥和初始化向量。


@Service
public class CipherService {
    //密钥长度
    public static final int AES_KEY_SIZE = 256;
    //初始化向量长度
    public static final int GCM_IV_LENGTH = 12;
    //GCM身份认证Tag长度
    public static final int GCM_TAG_LENGTH = 16;
    @Autowired
    private CipherRepository cipherRepository;
    //内部加密方法
    public static byte[] doEncrypt(byte[] plaintext, SecretKey key, byte[] iv, byte[] aad) throws Exception {
        //加密算法
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        //Key规范
        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
        //GCM参数规范
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        //加密模式
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
        //设置aad
        if (aad != null)
            cipher.updateAAD(aad);
        //加密
        byte[] cipherText = cipher.doFinal(plaintext);
        return cipherText;
    }
    //内部解密方法
    public static String doDecrypt(byte[] cipherText, SecretKey key, byte[] iv, byte[] aad) throws Exception {
        //加密算法
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        //Key规范
        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
        //GCM参数规范
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
        //解密模式
        cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
        //设置aad
        if (aad != null)
            cipher.updateAAD(aad);
        //解密
        byte[] decryptedText = cipher.doFinal(cipherText);
        return new String(decryptedText);
    }
    //加密入口
    public CipherResult encrypt(String data, String aad) throws Exception {
        //加密结果
        CipherResult encryptResult = new CipherResult();
        //密钥生成器
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        //生成密钥
        keyGenerator.init(AES_KEY_SIZE);
        SecretKey key = keyGenerator.generateKey();
        //IV数据
        byte[] iv = new byte[GCM_IV_LENGTH];
        //随机生成IV
        SecureRandom random = new SecureRandom();
        random.nextBytes(iv);
        //处理aad
        byte[] aaddata = null;
        if (!StringUtils.isEmpty(aad))
            aaddata = aad.getBytes();
        aaddata = aad.getBytes();
        //获得密文
        encryptResult.setCipherText(Base64.getEncoder().encodeToString(doEncrypt(data.getBytes(), key, iv, aaddata)));
        //加密上下文数据
        CipherData cipherData = new CipherData();
        //保存IV
        cipherData.setIv(Base64.getEncoder().encodeToString(iv));
        //保存密钥
        cipherData.setSecureKey(Base64.getEncoder().encodeToString(key.getEncoded()));
        cipherRepository.save(cipherData);
        //返回本地加密ID
        encryptResult.setId(cipherData.getId());
        return encryptResult;
    }
    //解密入口
    public String decrypt(long cipherId, String cipherText, String aad) throws Exception {
        //使用加密ID找到加密上下文数据
        CipherData cipherData = cipherRepository.findById(cipherId).orElseThrow(() -> new IllegalArgumentException("invlaid cipherId"));
        //加载密钥
        byte[] decodedKey = Base64.getDecoder().decode(cipherData.getSecureKey());
        //初始化密钥
        SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
        //加载IV
        byte[] decodedIv = Base64.getDecoder().decode(cipherData.getIv());
        //处理aad
        byte[] aaddata = null;
        if (!StringUtils.isEmpty(aad))
            aaddata = aad.getBytes();
        //解密
        return doDecrypt(Base64.getDecoder().decode(cipherText.getBytes()), originalKey, decodedIv, aaddata);
    }
}

我们可以让用户选择,如果需要保护二要素的话,就自己输入一个查询密码作为 AAD。系统需要读取用户敏感信息的时候,还需要用户提供这个密码,否 则无法解密。这样一来,即使黑客拿到了用户数据库的密文、加密服务的密钥和 IV,也会因为缺少 AAD 无法解密。

    @Autowired
    private CipherService cipherService;
    //加密
    @GetMapping("right")
    public UserData right(@RequestParam(value = "name", defaultValue = "test") String name,
                          @RequestParam(value = "idcard", defaultValue = "300000000000001234") String idCard,
                          @RequestParam(value = "aad", required = false)String aad) throws Exception {
        UserData userData = new UserData();
        userData.setId(1L);
        //脱敏姓名
        userData.setName(chineseName(name));
        //脱敏身份证
        userData.setIdcard(idCard(idCard));
        //加密姓名
        CipherResult cipherResultName = cipherService.encrypt(name,aad);
        userData.setNameCipherId(cipherResultName.getId());
        userData.setNameCipherText(cipherResultName.getCipherText());
        //加密身份证
        CipherResult cipherResultIdCard = cipherService.encrypt(idCard,aad);
        userData.setIdcardCipherId(cipherResultIdCard.getId());
        userData.setIdcardCipherText(cipherResultIdCard.getCipherText());
        return userRepository.save(userData);
    }
    //解密
    @GetMapping("read")
    public void read(@RequestParam(value = "aad", required = false)String aad) throws Exception {
        //查询用户信息
        UserData userData = userRepository.findById(1L).get();
        //使用AAD来解密姓名和身份证
        log.info("name : {} idcard : {}",
                log.info("name : {} idcard : {}",
                        cipherService.decrypt(userData.getNameCipherId(), userData.getNameCipherText(),aad),
                        cipherService.decrypt(userData.getIdcardCipherId(), userData.getIdcardCipherText(),aad));
    }
    //脱敏身份证
    private static String idCard(String idCard) {
        String num = StringUtils.right(idCard, 4);
        return StringUtils.leftPad(num, StringUtils.length(idCard), "*");
    }
    //脱敏姓名
    public static String chineseName(String chineseName) {
        String name = StringUtils.left(chineseName, 1);
        return StringUtils.rightPad(name, StringUtils.length(chineseName), "*");

{"id":1,"name":"朱*","idcard":"************** 1234","idcardCipherId":26346,"idcardCipherText":"t/wIh1XTj00wJP1Lt3aGzSvn9GcqQWEwthN58KKU4KZ4Tw==","nameCipherId":26347,"name CipherText":"+gHrk1 mWmveBMVUo+CYon8Zjj9QAtw=="} [21:46:00.079] [http-nio-45678-exec-6] [INFO ] [o.g.t.c.s.s.StoreIdCardController:102 ] - name : test idcard : 300000000000001234

错误的aad会抛出异常 javax.crypto.AEADBadTagException: Tag mismatch! at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:578) at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1116) at com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1053) at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853) at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) at javax.crypto.Cipher.doFinal(Cipher.java:2164)

非对称加密

公钥密码算法。公钥密码是由一对密钥对构成的,使用公钥或者说加密密钥来加密,使用私钥或者说解密密钥来解密,公钥可以任意公开,私钥不能公 开。使用非对称加密的话,通信双方可以仅分享公钥用于加密,加密后的数据没有私钥无法解密,国密算法包括SM2,SM9。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

openGauss学习笔记-212 openGauss 数据库运维-日志参考

文章目录 openGauss学习笔记-212 openGauss 数据库运维-日志参考212.1 日志类型简介212.2 系统日志212.3 操作日志212.4 审计日志212.5 WAL日志212.6 性能日志 openGauss学习笔记-212 openGauss 数据库运维-日志参考 212.1 日志类型简介 在数据库运行过程中,会出现…

Java面试——计网篇

一、基础篇 1、 TCP/IP 网络模型 对于同一台设备上的进程间通信,有很多种方式,比如有管道、消息队列、共享内存、信号等方式,而对于不同设备上的进程间通信,就需要网络通信,而设备是多样性的,所以要兼容多…

RDBMS-MySQL高级

数据操作语句(DML)多表/关联查询Mysql中的函数事务执行流程数据库的备份与还原数据库表设计三范式 一、数据操作语句(DML) 插入数据 语法: 1.1插入(insert [into])或添加一条数据 -- 指定列…

excel统计分析——卡方独立性检验(下)

参考资料:生物统计学 书接上文:https://blog.csdn.net/maizeman126/article/details/135893731 2、配对列联表 配对设计的数据,进行列联表检验时,采用McNemar-Bowker检验法进行检验。检验统计量为: 自由度dfk(k-1)/2…

CSS要点总结

一、CSS 快速入门 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>css 快速入门</title><!-- 解读1. 在 head 标签内&#xff0c;出现了 <style type"text/css"></style…

Spring Framework(6.x)源码编译与源码阅读入门

目录 一、Spring Framework 源码获取问题 1.1 Spring Framework 官网 1.2 Spring Framework 源码地址 1.3 关于访问不了GitHub 官网的解决方案 1.3.1 修改本地hosts文件 1.3.2 GitHub520 1.3.3 Gitee 导入 二、Spring Framework 源码编译 2.1 环境说明 2.1.1 JDK版本 …

代码随想录算法训练营29期|day38 任务以及具体安排

第九章 动态规划part01 509. 斐波那契数 //非压缩状态的版本 class Solution {public int fib(int n) {if (n < 1) return n; int[] dp new int[n 1];dp[0] 0;dp[1] 1;for (int index 2; index < n; index){dp[index] dp[index - 1] dp[index - 2];}r…

【AI数字人-论文】Geneface论文

文章目录 前言pipelineaudio-to-motionMotion domain adaptation可视化 Motion-to-imageHead-NeRFTorso-NeRF 结果对比 前言 语音驱动的说话人视频合成旨在根据一段输入的语音&#xff0c;合成对应的目标人脸说话视频。高质量的说话人视频需要满足两个目标&#xff1a; &#…

【无标题】Vue项目中你是如何解决跨域的呢?

文章目录 一、跨域是什么二、如何解决CORSProxy 一、跨域是什么 跨域本质是浏览器基于同源策略的一种安全手段 同源策略&#xff08;Sameoriginpolicy&#xff09;&#xff0c;是一种约定&#xff0c;它是浏览器最核心也最基本的安全功能 所谓同源&#xff08;即指在同一个域…

Cmake语法学习3:语法

1.双引号 1.1 命令参数 1&#xff09;介绍 命令中多个参数之间使用空格进行分隔&#xff0c;而 cmake 会将双引号引起来的内容作为一个整体&#xff0c;当它当成一个参数&#xff0c;假如你的参数中有空格&#xff08;空格是参数的一部分&#xff09;&#xff0c;那么就可以使…

智慧商城(continue)

文章目录 1.静态页面结构准备和动态渲染2.搜索 - 历史记录管理1. 写好基础静态页面,可以先往里面加一点假数据2. 上面基本的渲染直接利用history渲染就可以了3. 搜索历史基本渲染结束了,开始点击搜索添加历史4. vant内用v-model" ",可以快速拿到搜索框的值5. 往历史记…

Vue3.0(一):Vue的引入-options api-模板语法

Vue的引入方式 CDN方式进行引入 将以下 script标签引入即可 <script src"https://unpkg.com/vue3/dist/vue.global.js"></script><!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><met…

oracle19C 密码包含特殊字符@ 导致ORA-12154

oracle 19C 密码包含特殊字符 出现登录失败&#xff0c;针对此问题一次说个明白 ORA-12154: TNS:could not resolve the connect identifier specified Oracle 19c之前密码是可以包含特殊字符&#xff0c;但是如果包含特殊字符需要双引号 比如oracle11g 正常 如果密码包含特殊…

创新大赛专访丨金智维荣膺2023年度数字化创新服务卓越品牌:专注提供企业级RPA平台,重塑企业生产力,让员工更有价值

日前&#xff0c;2023第三届全国人力资源创新大赛颁奖典礼暨成果展圆满举行。自2023年10月份启动以来&#xff0c;大赛共吸引了457个案例报名参赛&#xff0c;经组委会专家团队评审严格审核&#xff0c;企业赛道共有103个案例获奖、72家企业、13位个人、7个产业园斩获荣誉。 珠…

帮管客CRM SQL注入漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

简单实践 java spring boot 自动配置模拟

1.概要 1.1 需求&#xff0c;自己写一个redis-spring-boot-starter模拟自动配置 自动配置就是在引入*-starter坐标后&#xff0c;可以已经spring框架的规则实现一些Bean的自动注入&#xff0c;并设置一些参数的默认值&#xff0c;且也可以在引入的工程中修改这些配置的值。这…

C#代码添加脚本头

目录 前言 代码展示 前言 创建脚本的时候添加脚本的介绍 代码展示 using System.IO;/// <summary> /// 创建脚本自动添加头注 /// </summary> public class CommentFirst : UnityEditor.AssetModificationProcessor {/// <summary>/// 在资源创建生成.me…

springboot整合RabbitMQ,RabbitMQ实现高级特性消息不丢失

1.生产者可靠性消息投递 简单操作参考---------打开主页上篇博客 https://blog.csdn.net/weixin_45810161/article/details/135906602?spm1001.2014.3001.5501 在使用RabbitMQ的时候,怎么保证保证消息不丢失,RabbitMQ提供了两种不同的方式来控制消息的可靠性投递 1.confirm…

js中的数据类型(存储上的差别)

文章目录 前言一、基本类型NumberUndefinedStringNullBooleanSymbol 二、引用类型ObjectArrayFunction其他引用类型 三、存储区别基本类型引用类型 小结 前言 在JavaScript中&#xff0c;我们可以分成两种类型&#xff1a; 基本类型复杂类型 两种类型的区别是&#xff1a;存…

老版本labelme如何不保存imagedata

我的版本是3.16&#xff0c;默认英文且不带取消保存imagedata的选项。 最简单粗暴的方法就是在json文件保存时把传递过来的imagedata数据设定为None&#xff0c;方法如下&#xff1a; 找到labelme的源文件&#xff0c;例如&#xff1a;D:\conda\envs\deeplab\Lib\site-packages…