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

目录

对称加密算法

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 {//初始化Ciphercipher.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 {@Idprivate Long id;private String idcard;//脱敏的身份证private Long idcardCipherId;//身份证加密IDprivate String idcardCipherText;//身份证密文private String name;//脱敏的姓名private Long nameCipherId;//姓名加密IDprivate 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;@Autowiredprivate 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);//设置aadif (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);//设置aadif (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];//随机生成IVSecureRandom random = new SecureRandom();random.nextBytes(iv);//处理aadbyte[] 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();//保存IVcipherData.setIv(Base64.getEncoder().encodeToString(iv));//保存密钥cipherData.setSecureKey(Base64.getEncoder().encodeToString(key.getEncoded()));cipherRepository.save(cipherData);//返回本地加密IDencryptResult.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");//加载IVbyte[] decodedIv = Base64.getDecoder().decode(cipherData.getIv());//处理aadbyte[] aaddata = null;if (!StringUtils.isEmpty(aad))aaddata = aad.getBytes();//解密return doDecrypt(Base64.getDecoder().decode(cipherText.getBytes()), originalKey, decodedIv, aaddata);}
}

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

    @Autowiredprivate 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.hqwc.cn/news/449155.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无人机激光雷达标定板

机载激光雷达标定板是用于校准和验证机载激光雷达系统的设备。由于机载激光雷达系统在测量地形、建筑物和植被等方面具有广泛的应用,因此标定板的使用对于确保测量结果的准确性和可靠性至关重要。 标定板通常由高反射率的材料制成,如镀金的玻璃或陶瓷&am…

flv视频格式批量截取封面图(不占内存版)--其他视频格式也通用

flv视频格式批量截取封面图(不占内存版)--其他视频格式也通用 需求(实现的效果)功能实现htmlcssjs 需求(实现的效果) 批量显示视频,后端若返回有imgUrl,则直接显示图1, 若无&#xf…

【推荐】运放作为跟随器时,负反馈上加电阻的作用?

请问何种信号源或者输出是什么状况下跟随器需要使用电阻呢?使用多大阻值? 答:信号源内阻较大时,添加阻值与信号源内阻相同的反馈电阻,可以减少输出失调电压,提高精度。 R2的作用是为了防止输出意外接地&am…

AspNet web api 和mvc 过滤器差异

最近在维护老项目。定义个拦截器记录接口日志。但是发现不生效 最后发现因为继承的 ApiController不是Controller 只能用 System.Web.Http下的拦截器生效。所以现在总结归纳一下 Web Api: System.Web.Http.Filters.ActionFilterAttribute 继承该类 Mvc: System.Web.Mvc.Ac…

版本管理git及其命令介绍-附带详细操作

前言 在版本管理时代之前,人们写软件的方式如下图1所示 图1 无版本管理的代码 其坏处就是软件版本随着时间越来越多,每个版本修改了什么内容,修改了哪些文件,如果没有详细记录也不知道。这样久会导致如果我们想回退到某个版本内…

4通过干扰 Char 设备为 PRNG 添加后门_Linux_Rootkit.md

Xcellerator 密码学Linux其他逆向工程 文章目录 [Linux Rootkit 第 4 部分:通过干扰 Char 设备为 PRNG 添加后门](https://xcellerator.github.io/posts/linux_rootkits_04/)Linux 中的字符设备字符设备的读取例程编写 Rootkit我们能去哪里呢? Linux Ro…

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

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

编译Duilib库

编译Duilib,遇到几个错误; 最终生成的lib如下; 报一个错误,无法打开源文件"StdAfx.h", 查了一下资料,反正我的在下图 C/C - 常规 - 附加包含目录,填入下图内容就可以了,这…

【老生常谈】之Java反射机制

文章目录 序言一、基本概念1.1 Java反射机制是什么?1.2 反射机制功能1.3 反射机制的优缺点 二、Java反射机制API2.1 java.lang.Class 类2.2 java.lang.reflect 包2.2.1 java.lang.reflect.Constructor2.2.2 java.lang.reflect.Method2.2.3 java.lang.reflect.Field2…

【亲测有效】无法获得下列许可 SOLIDWORKS Standard 无效的(不一致的) 使用许可号码 (-8,544,0)

在观看本文章前,请注意看你的报错代码是否和我的一致,如果不是,直接跳过本文章。 前言:我安装的是SOLIDWORKS2022版,软件已经安装完毕,SolidWorks_Flexnet_Server文件夹里面的两个注册表已经安装完毕&#…

LLM应用开发与落地:使用gradio十分钟搭建聊天UI

一、背景 如果你是做LLM应用开发的,特别是做后端开发,你一定会遇到怎么快速写一个聊天UI界面来调试prompt或agent的问题。这时候的你可能在苦恼中,毕竟react.js, next.js, css, html也不是每个人都那么熟练,对吧?即使…

深度学习入门笔记(七)卷积神经网络CNN

我们先来总结一下人类识别物体的方法: 定位。这一步对于人眼来说是一个很自然的过程,因为当你去识别图标的时候,你就已经把你的目光放在了图标上。虽然这个行为不是很难,但是很重要。看线条。有没有文字,形状是方的圆的,还是长的短的等等。看细节。纹理、颜色、方向等。卷…