加密算法的简易应用
admin
2024-03-09 08:03:15
0

在介绍如何使用实现加密之前,首先来介绍一个类javax.crypto.Cipher.这是java自带的加密实现类,也是本文依赖的重点。创建实例通过调用静态方法Cipher.getInstance(String),传入参数指定了加密过程,格式如下:

  • “algorithm/mode/padding” or
  • “algorithm”

其中algorithm不做过多解释,就是AES/DES/RSA这样的具体具体加密算法。

mode这里只介绍两种比较常见的CBC和ECB,至于OFB这种高阶模式,有缘再见
ECB全程是 Electronic CodeBook,它会将要加密明文msg分为固定长度,比如64,然后独立加密每一组。这样的加密过程导致了ECB的第一个问题:如果某个块被篡改,只有被篡改块在解密后会出现问题,但是其他块解密仍然是正常的。
如果用ECB加密一段有固定格式的明文,破译加密完全不需要了解如何解密,只需要找到固定格式在密文中的位置,替换想要篡改的位置。
不仅如此,ECB加密模式还存在另一个问题:在64bit块长度下,加密一段明文。这个明文是由一个长度为32bit的字符串不断重复生成的,假设其重复了8次。显然,现在所有待加密的块中的内容都是一样的,加密后的密文也是一样。虽然我不知道怎么利用这一点干坏事
相比而言,CBC(Cipher Block Chaining)则安全的多。CBC不再独立加密每个块,每个块都将作为后续块加密运算的参数,参与下一轮加密,所以密文的解密需要顺序,并且对密文任意位置的篡改,都会导致明文完全不可读。

对于块加密来说,有一个问题是必须要考虑的:如果分组的明文长度小于块长度怎么办?这时候就需要padding来补位,填充明文长度。注意加解密的padding算法保持一致,不然会有问题。

java自带如下几种,括号中数字为key size

AES/CBC/NoPadding (128)

AES/CBC/PKCS5Padding (128)

AES/ECB/NoPadding (128)

AES/ECB/PKCS5Padding (128)

DES/CBC/NoPadding (56)

DES/CBC/PKCS5Padding (56)

DES/ECB/NoPadding (56)

DES/ECB/PKCS5Padding (56)

DESede/CBC/NoPadding (168)

DESede/CBC/PKCS5Padding (168)

DESede/ECB/NoPadding (168)

DESede/ECB/PKCS5Padding (168)

RSA/ECB/PKCS1Padding (1024, 2048)

RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)

RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)

AES对称加密实现

加密原理略,但愿有生之年会补。

借助java自带的security包,大致流程如下。首先获取key,获取方式详见下文。随后用key和iv矩阵构造cipher,用cipher对明文加密。最后对加密得到的二进制数据用base64编码,便于传输

public static String encrypt(String plainText,String slat) {String ans = null;try {Key secret = getSecretKey(plainText, slat);// configure a cipher instanceCipher cipher = Cipher.getInstance(CIPHER_INST);cipher.init(Cipher.ENCRYPT_MODE, secret, getIvSpec());// encrypt the input stringplainText = plainText + slat;byte[] cipherText = cipher.doFinal(plainText.getBytes());// why need to use base64?ans = Base64.getEncoder().encodeToString(cipherText);} finally {return ans;}
}

AES加密时,建议同明文一起,传入一个盐值。传入盐值之后,明文会和盐值拼接,这样即使相同的明文,通过加密后也会生成不同的密文,从而减少被查字典的概率。

这里盐值的生成借助keyGenerator

public static String getSlat() {return KeyGenerators.string().generateKey();
}

解密时与加密过程相反。用相同的key和iv矩阵构造cipher,然后先对密文base64解码,再用cipher解密。如果key和iv与加密时一致,就可以对密文解密得到明文。
由于在加密时使用了salt,所以解密时用相同的规则把盐值去除,得到明文。

public static String decrypt(String cipherText,String slat) {String ans = null;try{Key secret = getSecretKey(cipherText, slat);// configure a cipher instanceCipher cipher = Cipher.getInstance(CIPHER_INST);cipher.init(Cipher.DECRYPT_MODE, secret, getIvSpec());byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));String salText = new String(plainText);ans = salText.substring(0, salText.length() - slat.length());}finally {return ans;}
}

AES加解密的流程很简单,要实现一套加解密方法,关键在得到相同的cipher,而cipher的构造又依赖于iv矩阵和key。
对于iv矩阵处理方法比较简单,可以直接在代码中硬编码

private static final byte[] INITIAL_IV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

但是key处理就没有这么简单。这里推荐直接使用keytools生成本地可读取key存储文件,详见参考1,这里就不翻译了。

keytool -genseckey -keystore aes-keystore.jck -storetype jceks -storepass mystorepass -keyalg AES -keysize 256 -alias jceksaes -keypass mykeypass 

同样,key也有很多奇奇怪怪的存储格式,在生成文件时选用jceks格式,自然java中也有这个格式的解析类

private static Key getSecretKey() throws Exception {Key secret = null;// try to read from file(keyStore)InputStream keyStoreStream = EncryptUtil.class.getClassLoader().getResourceAsStream(KEYSTORE_PATH);KeyStore keyStore = KeyStore.getInstance(KEYSTORE_FORMAT);// password to access keystorekeyStore.load(keyStoreStream, KEYSTORE_PASS.toCharArray());if(keyStore.containsAlias(ALIAS)) {// password to access keysecret = keyStore.getKey(ALIAS, KEY_PASS.toCharArray());}return secret;
}

访问密钥时和生成密钥的过程一致。首先输入keyStore的密码,查询keyStore中是否有该密钥的别名,如果存在输入该密钥的密码获取该密钥

String plainText = "123456";
String salt = EncryptUtil.getSlat();
String cipherText = EncryptUtil.encryptWithAES(plainText, salt);
System.out.println("plainText:"+plainText+" slat:"+salt);
System.out.println("cipherText:"+cipherText);
// do decrypt
String decrypText = EncryptUtil.decryptWithAES(cipherText, salt);
System.out.println("decrypt:"+decrypText);

RSA非对称加密

RSA加密速度很慢,明文长度最好不要太长。如果一定要加密一段长文本,建议用AES加密,再将AES密钥用RSA加密后传输,也可以视为RSA加密的等效。

RSA密钥存储同样有很多格式,这里借助openssl生成RSA加密证书。首先生成一个private key

openssl genrsa -out keypair.pem 2048

从key pair中抽取公钥

openssl rsa -in keypair.pem -pubout -out publickey.crt

转换私钥格式为pkcs8

openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out pkcs8.key

因为用PKCS8EncodedKeySpec对读取内容进行解析,所以私钥格式要一致

        public static PrivateKey getPrivateKey() throws Exception {URL privateKeyRes = EncryptUtil.class.getClassLoader().getResource(privatePath);File privateFile = new File(privateKeyRes.getFile());String key = new String(Files.readAllBytes(privateFile.toPath()), Charset.defaultCharset());String pem = key.replace("-----BEGIN PRIVATE KEY-----", "").replaceAll("\n", "").replace("-----END PRIVATE KEY-----", "");byte[] decode = Base64.getDecoder().decode(pem);KeyFactory keyFactory = KeyFactory.getInstance("RSA/ECB/PKCS1Padding");PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decode);return keyFactory.generatePrivate(keySpec);}

此外根据官方文档描述,非加密的密钥文件有个一特殊的头尾,在读取的时候要去掉。

-----BEGIN PRIVATE KEY-----
.....
-----END PRIVATE KEY-----

还有密钥是base64编码后的,记得解码。

解密代码如下。这里虽然getInstance时只明mode是ECB,但是据说并没有真的用ECB去实现,到底有没有用ECB加密,有待考证。

        public static String decrypt(String cipherText) {String ans = null;try {// decode base64byte[] data = Base64.getDecoder().decode(cipherText);// read private-key from fileKey privateKey = getPrivateKey();// do decryptCipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");cipher.init(Cipher.DECRYPT_MODE, privateKey);byte[] plainData = cipher.doFinal(data);// build String with charsetans = new String(plainData, StandardCharsets.UTF_8);}finally {return ans;}}

nodejs中使用rsa公钥加密.这里padding的算法要和后端解密时cipher中设置的padding一直,否则会有badpadding的问题。
还有就是公钥证书crt没找到更好的加载方式,只能在代码里硬编码。不过公钥即使被人看到了也没有关系吧

const crypto = require('crypto')var rsaEncrypt = function (plainText) {let publicKey = '-----BEGIN PUBLIC KEY-----\n' +''+'-----END PUBLIC KEY-----\n'let cipherText = crypto.publicEncrypt({key: publicKey,padding: crypto.constants.RSA_PKCS1_PADDING}, Buffer.from(plainText))return cipherText.toString('base64')
}

参考链接:

[1]: Keystore 使用
[2]: PKCS#12 Information
[3]: Java AES Encryption and Decryption
[4]: pkcs8格式密钥生成
[5]: openssl pkcs8命令官方文档
[6]:RSA 加密
[7]: Class Cipher
[8]: ECB vs CBC
[9]: RSA/ECB/OAEPWithSHA-1AndMGF1Padding
[10]: Why do we use padding?
[11]: Base64
[12]: crypto 官方文档
[13]: rsa in nodejs

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...