import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/**
* <h3>Title:RSA 加密|解密 文件 </h3><hr/>
*
* 1978年,由三位麻省理工学院的学者提出
* - 于其他算法而言,RSA效率较慢
* - 私钥与公钥,可互为加解密
*
* 本例使用了公钥加密,私钥解密
*
* 密钥长度: 默认值 1024,范围 512 ~ 65536
* 填充方式:
* - NoPadding
* - PKCS1Padding
* - ...略
* 工作模式: ECB\NONE
* ( 参考: Java加密与解密的艺术 (第2版) )
*
* 密钥的产生:
* 1. 选择两个保密的大素数 p 和 q(p 跟 q 相差要很大,p-q<n的4次方根)
* 2. 计算 n=pq,r=(p-1)(q-1),找一个互质的数 e
* 3. 组成公钥 n、e
* 4. 找一个数作为私钥,要求跟 e 的乘积除以 r 余数必须为 1
* 5. 销毁 p、q、r 三个数
* ( 参考: BV1h5411m78h )
*
* TODO 签名与验签
*
* @author juner(夜路沏茶人)
* @date 2022年04月24日 08:35 周日
* @since JDK_1.8
* @see
* - 参考: RSA实现对文件的加密解密
* https://blog.csdn.net/LevenLa/article/details/116705456
* - 使用了 私钥加密、公钥解密
*
* - 参考: javac、java命令调用jar包
* https://blog.csdn.net/qq_20906903/article/details/107124602
* - 测试环境为,单个 class 运行指定 jar
* @version 0.5.3
*/
@SuppressWarnings("all")
public class RSA5 {
/** 读取文件,以 1M 为单位 */
private static final Integer READER_1M = (2<<9) * 1;
/** 密钥单位大小为,512 */
private static final Integer UNIT_SIZE = 2<<8;
/** 默认显示时间格式 */
private static final String DEFAULT_TIME_SHOW = "yyyy-MM-dd hh:mm:ss";
/** 默认时间线格式,为保障时间线唯一,此处使用了 SSS 标注唯一 */
private static final String DEFAULT_TIME_LINE = "yyyyMMdd_hhmm_ss_SSS";
/** RSA 算法 */
private static final String RSA = "RSA";
/** BC 提供 */
private static final String BC = "BC";
/** UTF-8 编码 */
private static final String UTF8 = "UTF-8";
/** 公钥默认读取路径 */
private static final String PUBLIC_KEY_FILE = getClassPath(RSA5.class) + "request.key";
/** 私钥默认读取路径 */
private static final String PRIVATE_KEY_FILE = getClassPath(RSA5.class) + "response.key";
/** 私钥 */
private String prky;
/** 公钥 */
private String puky;
// 加密等级制度
// - 等级越高,加密越严格(前提是保护要私钥且不能丢失)
// - 个人电脑加密等级,不要超出 S3072
// - 加密等级越高,速度越慢。
// - 推荐使用 S1024(速度较快) 或 S2048(相比较而言更安全)
// - S3072(速度太慢),其余更高等级加密不推荐
private static Size S1024;
private static Size S2048;
private static Size S3072;
private static Size S7680;
private static Size S15360;
/** 当前加密等级 */
private static Size thisSize;
static {
// 初始化加密等级
S1024 = Size.create(2 * (2 << 8));
S2048 = Size.create(4 * (2 << 8));
S3072 = Size.create(6 * (2 << 8));
S7680 = Size.create(15 * (2 << 8));
S15360 = Size.create(30 * (2 << 8));
// 加密等级通过修改 thisSize 来指定
thisSize = S1024 ;
}
/**
* 实例化
*/
public RSA5() {
this.readKeyPair();
}
/**
* 获取 class 运行路径
* @param clazz Class类
* @return 运行路径(由于测试系统为Windows系统,所以此处进行了截取,从1开始截取后面所有内容 )
*/
public static final String getClassPath(Class clazz) {
return clazz.getClassLoader()
.getResource("")
.getPath()
.substring(1);
}
/**
* 获取当前时间
* @return
*/
private static String getNowDate() {
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat(DEFAULT_TIME_SHOW);
return format.format(date);
}
/**
* 获取时间线
* @return
*/
private static String getLineDate() {
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat(DEFAULT_TIME_LINE);
return format.format(date);
}
/**
* 生成密钥对
* @return 密钥对
*/
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator kpg;
// 添加提供者 BC
Security.addProvider(new BouncyCastleProvider());
// 密钥对实例化,指定算法及提供者
kpg = KeyPairGenerator.getInstance(RSA, BC);
assert kpg!=null : "key generate error.";
// 密钥对初始化,指定密钥大小
kpg.initialize(thisSize.getKeySize());
// 获取生成的密钥对
return kpg.generateKeyPair();
} catch (Exception e) {
return null;
}
}
/**
* 判定 密钥文件 是否存在
* 暂定逻辑为,必须公钥密钥同时存在
* @return
*/
private static boolean areKeysPresent() {
File privateKey = new File(PRIVATE_KEY_FILE);
File publicKey = new File(PUBLIC_KEY_FILE);
return privateKey.exists() && publicKey.exists();
}
/**
* 保存密钥对
* @param keyPair 密钥对
*/
private static void saveKeyPair(KeyPair keyPair) {
// 如果密钥对存在,则终止重写
// - 如果使用其他密钥对,导致密钥不识别,需要更换正确的密钥对或密钥等级
// - 因此,建议使用固定等级的密钥对
// - 即,只换密钥对,不变等级
if(areKeysPresent()) return;
// 写入密钥对文件
writeDataBuffer(new File(PUBLIC_KEY_FILE), loadPublicKey(keyPair.getPublic()));
writeDataBuffer(new File(PRIVATE_KEY_FILE), loadPrivateKey(keyPair.getPrivate()));
}
/**
* 读取密钥对文件,并赋值给相关对象的属性
*/
private void readKeyPair() {
this.prky = readDataOnce(PRIVATE_KEY_FILE);
this.puky = readDataOnce(PUBLIC_KEY_FILE);
}
/**
* 加载私钥
* @param key 私钥字符串
* @return 私钥对象
*/
private static PrivateKey loadPrivateKey(String key) {
try {
// 如果读取字符串为空,则直接抛出
assert key!=null : "private-key is null.";
// 通过 私钥字节数组,指定 算法,再次生成 私钥对象
// 私钥 使用 PKCS8EncodedKeySpec
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(
java.util.Base64.getDecoder().decode((key.getBytes()))
);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePrivate(keySpec);
} catch (Exception e) {
error(e.getMessage());
return null;
}
}
/**
* 加载公钥对象
* @param key 公钥字符串
* @return 公钥对象
*/
private static PublicKey loadPublicKey(String key) {
try {
// 如果读取字符串为空,则直接抛出
if (key == null) {
throw new RuntimeException("public-key is null.");
}
// 通过 公钥字节数组,指定 算法,再次生成 公钥对象
// 公钥 使用 X509EncodedKeySpec
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(
java.util.Base64.getDecoder().decode((key.getBytes()))
);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePublic(keySpec);
} catch (Exception e) {
error(e.getMessage());
return null;
}
}
/**
* 将 Key 对象转换成 字符串
* @param key Key
* @return 字符串
*/
private static String keyToString(Key key) {
try {
byte[] keyBytes = key.getEncoded();
return new String(java.util.Base64.getEncoder().encode(keyBytes), UTF8);
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 加载私钥字符串
* @param privateKey 私钥对象
* @return 私钥字符串
*/
private static String loadPrivateKey(PrivateKey privateKey) {
return keyToString(privateKey);
}
/**
* 加载公钥字符串
* @param publicKey 公钥对象
* @return 公钥字符串
*/
private static String loadPublicKey(PublicKey publicKey) {
return keyToString(publicKey);
}
/**
* 给文件中写入数据
* @param file 文件对象
* @param data 数据字节数组
*/
private static void writeData(
File file,
byte[] data
) {
try {
FileOutputStream out = new FileOutputStream(file);
assert data != null : "write data is null.";
out.write(data);
out.flush();
out.close();
} catch (Exception e) {
error(e.getMessage());
}
}
/**
* 给文件中写入数据
* @param file 文件对象
* @param content 字符串对象
*/
private static void writeDataBuffer(
File file,
String content
) {
try {
if (file.getParentFile() != null) {
// 建立多级文件夹
file.getParentFile().mkdirs();
}
// 创建新的空文件
file.createNewFile();
FileOutputStream stream = new FileOutputStream(file);
// 以 UTF-8 编码方式进行写入
OutputStreamWriter outputStreamWriter =
new OutputStreamWriter(stream, UTF8);
BufferedWriter writer = new BufferedWriter(outputStreamWriter);
writer.write(content);
writer.flush();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 以 UTF-8 读码方式,一次性读取全部数据
* - 适用于小型文件
* - 此处使用在读取完整的密钥
* @param filePath 文件路径
* @return 文件内容
*/
private static String readDataOnce(String filePath) {
try {
byte[] bytes = Files.readAllBytes(Paths.get(filePath));
return new String(bytes, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 加密
*
* @param source 指定需要加密的文件
* @param publicKey 公钥字符串
* 使用公钥加密,因为私钥比公钥破解难度更高
* @param filePath 新文件路径及文件名
*/
public void encrypt(
String source,
String publicKey,
String filePath
) {
try {
// 将 公钥字符串 加载为 公钥对象
RSAPublicKey rsaPublicKey = (RSAPublicKey) loadPublicKey(publicKey);
assert rsaPublicKey != null : "rsa-publik-key is null";
// 读取需要加密的文件,并将其转换为 字节数组
File file = new File(source);
byte[] sourceData = handleReadData(file);
// 使用公钥进行加密数据
byte[] data = encryptByPublicKey(sourceData, rsaPublicKey.getEncoded());
// 将加密后的数据信息写入到文件
assert data != null: "data is null";
file = new File(filePath);
writeData(file, data);
} catch (Exception e) {
error(e.getMessage());
}
}
/**
* 解密
* @param source 需要解密的文件
* @param privateKey 私钥
* @param extend 解密后的新文件
* 解密时将文件解码后,重新生成新文件
*/
public void decrypt(
String source,
String privateKey,
String extend
) {
try {
// 加载私钥对象
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) loadPrivateKey(privateKey);
// 读取文件数据
File file = new File(source);
byte[] sourceDataByte = handleReadData(file);
// 进行解密操作
assert rsaPrivateKey != null : "rsa-private-key is null";
assert sourceDataByte != null : "source-data-byte is null";
byte[] data = decryptByPrivateKey(sourceDataByte, rsaPrivateKey.getEncoded());
// 写入到新文件
file = new File(extend);
writeData(file, data);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 读取数据(核心)
* @param file 预读取文件
* @return 文件相关内容的字节
* @throws IOException 文件访问异常、权限异常
*/
private byte[] handleReadData(
File file
) throws IOException {
try {
// 以 1M 为单位,进行分段读取文件,并重组数据,返回数据相关字节数组
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
byte[] tmpBuffer = new byte[READER_1M];
int count;
while ((count = in.read(tmpBuffer)) != -1) {
byteOut.write(tmpBuffer, 0, count);
tmpBuffer = new byte[READER_1M];
}
in.close();
return byteOut.toByteArray();
} catch (Exception e) {
error(e.getMessage());
return null;
}
}
/**
* 処理数据(核心)
* @param cipher cipher配置对象
* @param fileByte 文件字节数组
* @param modeIndex 处理模式
* 选项一、Cipher.ENCRYPT_MODE
* 选项二、Cipher.DECRYPT_MODE
* @return
*/
private byte[] handleData(
Cipher cipher,
byte[] fileByte,
int modeIndex
) {
try {
// 进行模式选择
// - 根据密钥大小等级,进行模式选择的最值
int singleMax = modeIndex == Cipher.DECRYPT_MODE
? thisSize.getDecryptSize() : thisSize.getEncryptSize();
// 获取数据最大长度,并作其余初始化
int inputLen = fileByte.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 分段処理进行 加密/解密 操作
while (inputLen - offSet > 0) {
if (inputLen - offSet > singleMax) {
cache = cipher.doFinal(fileByte, offSet, singleMax);
} else {
cache = cipher.doFinal(fileByte, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * singleMax;
}
// 数据转换、关闭资源、并返回已处理数据
byte[] data = out.toByteArray();
out.close();
return data;
} catch (Exception e) {
error(e.getMessage());
return null;
}
}
/**
* 通过 公钥 进行加密操作
*
* @param fileByte 文件字节数组
* @param publicKeyByte 公钥字节数组
* @return 加密文件字节数组
* @throws Exception 异常
*/
private byte[] encryptByPublicKey(
byte[] fileByte,
byte[] publicKeyByte
) throws Exception {
// 指定加密算法
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 生成公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyByte);
Key publicKey = keyFactory.generatePublic(x509KeySpec);
// 指定配置并初始化
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 进行加密処理
return handleData(cipher, fileByte, Cipher.ENCRYPT_MODE);
}
/**
* 通过 私钥 进行加密操作
*
* @param dataStr 加密文件字节数组
* @param privateKeyByte 私钥字节数组
* @return 文件字节数组
* @throws Exception 异常
*/
private byte[] decryptByPrivateKey(
byte[] dataStr,
byte[] privateKeyByte
) throws Exception {
// 指定加密算法
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
// 生成私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKeyByte);
Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
// 指定配置并初始化
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 进行解密処理
return handleData(cipher, dataStr, Cipher.DECRYPT_MODE);
}
// TODO 临时,错误提示
private static void error(String message) {
System.err.println(" ");
System.err.println(" - error: " + message + " (" + getNowDate() +")");
System.err.println(" ");
}
// TODO 临时,消息输出
private static void message(String message) {
System.out.println(" ");
System.out.println(" - info: " + message + " (" + getNowDate() +")");
System.out.println(" ");
}
/**
* TODO 标记,临时
* 操作
* @param sc 输入操作
*/
private static void options(Scanner sc) {
System.out.println("---------------------------");
System.out.println("input options number: ");
System.out.println("1: encrypt file ");
System.out.println("2: decrypt file");
System.out.println("x: exit");
System.out.println("ls: show path");
System.out.println("---------------------------");
String content = sc.nextLine();
switch (content) {
case "1": opt1(sc); break;
case "2": opt2(sc); break;
case "x": sc.close(); break;
case "ls":
System.out.println(getClassPath(RSA5.class));
options(sc);
break;
default:
error("invalid options");
options(sc);
break;
}
}
/**
* TODO 标记,临时
* 操作一,进行加密
* @param sc 输入操作
*/
private static void opt1(Scanner sc) {
System.out.println("---------------------------");
System.out.println("input file name: ");
System.out.println("---------------------------");
String content = sc.nextLine().trim();
if(!"".equals(content)) {
if("=x".equalsIgnoreCase(content)) {
options(sc);
}
if(new File(getClassPath(RSA5.class) + content).exists()) {
RSA5 service = new RSA5();
service.encrypt(getClassPath(RSA5.class) + content, service.puky,getClassPath(RSA5.class) + content + ".sign");
message("encrypt final '" + content + "' to " + content + ".sign ");
} else {
error("not found '" + content + "'. ");
opt1(sc);
}
} else {
opt1(sc);
}
}
/**
* TODO 标记,临时
* 操作二,进行解密
* @param sc 输入操作
*/
private static void opt2(Scanner sc) {
System.out.println("---------------------------");
System.out.println("input file name (.sign): ");
System.out.println("---------------------------");
String content = sc.nextLine().trim();
if(!"".equals(content)) {
if("=x".equalsIgnoreCase(content)) {
options(sc);
}
if(new File(getClassPath(RSA5.class) + content).exists()) {
String lineDate = getLineDate();
RSA5 service = new RSA5();
service.decrypt(getClassPath(RSA5.class) + content, service.prky, getClassPath(RSA5.class) + lineDate + ".json");
message(content + "' to " + lineDate + ".json ");
} else {
error("not found '" + content + "'.");
opt2(sc);
}
} else {
opt2(sc);
}
}
public static void main(String[] args) throws Exception {
// 测试说明
// - 平级文件: bcprov-jdk15on-1.60.jar
// - 加密操作: 会在平级目录下生成 密钥对文件
// PUBLIC_KEY_FILE
// PRIVATE_KEY_FILE
// - 解密操作: 平级目录下必须存在可用的 密钥对文件,且密钥对文件等级与配置等级一致
// 测试
// - 生成二进制文件
// javac -encoding UTF-8 -classpath .;bcprov-jdk15on-1.60.jar RSA5.java
// - 执行
// java -ea -classpath .;bcprov-jdk15on-1.60.jar RSA5
// 生成密钥对文件,并保存;如果存在,则不生成
KeyPair keyPair = generateKeyPair();
saveKeyPair(keyPair);
// 实例化并读取密钥文件
RSA5 service = new RSA5();
service.readKeyPair();
message(" - lv " + thisSize.getKeySize());
// 操作
Scanner sc = new Scanner(System.in);
options(sc);
}
}
/**
* 密钥大小等级
*/
@SuppressWarnings("all")
class Size {
/** 密钥大小 */
private Integer keySize;
/** 最大加密大小 */
private Integer encryptSize;
/** 最大解密大小 */
private Integer decryptSize;
// --------------------------------------- 直接创建并配置
/**
* 创建配置 密钥大小等级
* @param keySize 密钥大小
* @return 密钥配置
*/
public static Size create(Integer keySize) {
// 大于等于 512
assert keySize>=512;
// 小于等于 65535
assert keySize<=65535;
// 必须是 64 的整数倍
assert keySize%64==0;
return new Size(keySize)
.setEncryptSize(keySize/8 - 11)
.setDecryptSize(keySize/8);
}
// --------------------------------------- 禁止通过 new 实例化对象
private Size(Integer keySize) {
this.keySize = keySize;
}
// --------------------------------------- 获取密钥大小等级配置
public Integer getKeySize() {
return keySize;
}
public Integer getEncryptSize() {
return encryptSize;
}
public Integer getDecryptSize() {
return decryptSize;
}
// --------------------------------------- 禁止通过 set 方法改变配置
private Size setKeySize(Integer keySize) {
this.keySize = keySize;
return this;
}
private Size setEncryptSize(Integer encryptSize) {
this.encryptSize = encryptSize;
return this;
}
private Size setDecryptSize(Integer decryptSize) {
this.decryptSize = decryptSize;
return this;
}
}
RSA加密文件
最新推荐文章于 2024-06-25 02:40:23 发布