Java MD5与RSA加密使用
转发数据到广州,那边要求 HTTP 请求的头部需要用 MD5 签名,请求体数据需要使用 RSA 加密,研究了一下。
MD5
MD5(Message Digest Algorithm 5)是一种广泛使用的加密哈希函数,可将任意长度的消息转换为128位的哈希值(通常以32个十六进制字符表示)。MD5 算法是一种单向加密算法,也就是说只能进行加密,无法解密。因此,它常用于密码的加密存储和数字签名验证等场景。
MD5 算法的主要原理是通过多次对输入数据进行压缩、迭代和变换,最终生成一个哈希值。MD5 算法的具体实现包括以下几个步骤:
- 填充消息:首先需要对消息进行填充,使其长度满足一个特定的条件(例如长度为64的倍数),以便于后续的处理。
- 初始值设置:设置4个32位的寄存器(A、B、C、D)的初始值,作为哈希值的组成部分。
- 消息分块:将填充后的消息按照长度为512位的块进行分组,每组包含16个32位的字。
- 循环迭代:对每个消息块进行循环迭代,共进行64轮,每轮都包含4个变换步骤。
- 输出结果:最后将4个寄存器的值连接起来,形成128位的哈希值。
MD5 算法被广泛应用于互联网安全领域,例如密码加密、数字签名、消息认证等。但是,由于 MD5 算法存在一些漏洞,例如碰撞攻击,因此在一些安全性要求更高的场合,如用户密码存储,建议使用更为安全的加密算法,例如 SHA-2 系列的哈希函数。
彩虹表攻击:彩虹表攻击是一种密码破解方法,通常用于破解哈希函数加密的密码。彩虹表是一种预先计算好的表,其中包含了大量可能的明文和它们对应的哈希值。攻击者使用彩虹表可以快速地反推出哈希值对应的明文密码。这种攻击方法相对于暴力破解等其他破解方法来说速度非常快,因为攻击者不需要每次都重新计算哈希值,而是直接在预先计算好的彩虹表中查找匹配。
简单来说,MD5 加密是单向的,是没法从加密后的密文解密得到明文的。因此主要的应用就是密码的密文保存、对比数据是否被篡改。
MD5Util
MD5 加密的应用比较简单,放一个 MD5Util 看一下:
public class MD5Util {
public static String encipherHeader(String loginName, String loginPwd, String timestamp) {
//String timestampStr = Long.toString(timestamp);
StringBuffer sb = new StringBuffer();
sb.append("appkey=");
sb.append(loginName);
sb.append(",secret=RSA,signt=");
sb.append(timestamp);
sb.append(",secretKey=");
sb.append(loginPwd);
String header = sb.toString();
String md5Header = md5Encipher(header);
return md5Header;
}
public static String md5Encipher(String str){
String md5Str = null;
StringBuffer sb = new StringBuffer();
try {
// 创建 MessageDigest 对象并指定算法为 MD5
MessageDigest md = MessageDigest.getInstance("MD5");
// 将字符串转换为字节数组并进行加密
byte[] md5Bytes = md.digest(str.getBytes());
// 将字节数组转换为字符串
for (byte b : md5Bytes) {
sb.append(String.format("%02x", b));
}
md5Str = sb.toString();
System.out.println("MD5 加密前原始数据:" + str);
System.out.println("MD5 加密结果:" + md5Str);
} catch (NoSuchAlgorithmException e) {
System.err.println("不支持的加密算法:MD5");
e.printStackTrace();
}
return md5Str;
}
}
其中 md5Encipher
是对字符串进行加密的方法,headerEncipher
是我自己要用的对 HTTPHeader 加密的方法。
测试
写一个 Main 方法测试一下加密:
public static void main(String[] args) {
// 接收对方传来的 MD5 加密后的密文
String pwd = "4d7ad62f7d74f7defd845740661f53e2";
// 根据对方传来的数据计算 MD5 加密的结果
String encipher = MD5Util.md5Encipher("appkey=1234567890,secret=RSA,signt=1637109703238,secretKey=abcdefghijklmnopqrstuvwxyz");
System.out.println("加密结果:" + encipher);
if(pwd.equals(encipher)){
// 两次结果一致,数据没有被篡改/签名有效
System.out.println("数据相等");
}else {
// 两次结果不一致,数据被篡改了/签名无效
System.out.println("数据不相等");
}
}
直接写在工具类里,运行,控制台输出:
MD5 加密前原始数据:appkey=1234567890,secret=RSA,signt=1637109703238,secretKey=abcdefghijklmnopqrstuvwxyz
MD5 加密结果:4d7ad62f7d74f7defd845740661f53e2
加密结果:4d7ad62f7d74f7defd845740661f53e2
==========数据相等==========
进程已结束,退出代码0
验证成功,没什么问题。
RSA
RSA是一种公钥加密算法,其加密和解密过程都需要用到两个密钥,一个是公钥,另一个是私钥。公钥可以自由发布,私钥则是保密的。
RSA的安全性基于大数分解难题,也就是说,对于一个足够大的整数,要找到它的所有质因数是非常困难的。RSA算法的核心是选择两个大质数p和q,然后计算它们的乘积N=p * q。然后根据一定的算法计算出一个公钥和一个私钥。
公钥包含两个参数N和e,私钥包含两个参数N和d。其中,N为大质数p和q的乘积,e是一个小于N的正整数,且e与(p-1) * (q-1)互质,d是e的模逆元,满足 (e * d) % ((p-1) * (q-1)) = 1。
RSA加密过程如下:
- 接收方生成一对密钥,其中一个是公钥,公开给发送方;另一个是私钥,保存在自己手中。
- 发送方获取接收方的公钥,将明文进行加密后发送。
- 接收方接收到密文后,使用自己的私钥进行解密,得到明文。
RSA算法的优点是安全性高,加密速度较快,可以用于数据的加密、数字签名等方面。缺点是加密和解密的速度较慢,且加密的数据长度有限制。
RSA 加密在网上找了找都是讲 RSA 算法的,或者应用起来比较复杂。所以搞个简单的工具类,万一以后还要用到就可以直接用了。
RSAUtil
也是放一个 RSAUtil,其中包括了生成公钥私钥、使用公钥加密、使用私钥解密的方法:
public class RSAUtil {
//密钥长度
private final static int KEY_SIZE = 1024;
//保存产生的公钥与私钥
private static Map<String, String> keyMap = new HashMap<String, String>();
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception
*/
public static String encrypt(String str, String publicKey) throws Exception {
// 将公钥解码为 base64
byte[] decoded = decryptBASE64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
// RSA加密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = encryptBASE64(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception
*/
public static String decrypt(String str, String privateKey) throws Exception {
//64位解码加密后的字符串
byte[] inputByte = decryptBASE64(str);
//base64编码的私钥
byte[] decoded = decryptBASE64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
// 编码返回字符串
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}
// 解码返回byte
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
/**
* 随机生成密钥对
* @throws Exception
*/
public static void generateKeyPair() throws Exception {
// KeyPairGenerator 基于RSA算法,用于生成一对公钥和私钥
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 获取公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 获取私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 获取公钥字符串
String publicKeyString = encryptBASE64(publicKey.getEncoded());
// 得到私钥字符串
String privateKeyString = encryptBASE64(privateKey.getEncoded());
// 保存公钥和私钥
keyMap.put("pubKey", publicKeyString);
keyMap.put("privateKey", privateKeyString);
}
}
其中用到了 common-codec
的依赖:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
测试
写一个 Main 方法测试一下获取密钥和加密解密的过程:
public static void main(String[] args) {
try {
// 生成公钥和私钥
generateKeyPair();
String publicKey = keyMap.get("pubKey");
System.out.println("公钥:" + publicKey);
String privateKey = keyMap.get("privateKey");
System.out.println("私钥:" + privateKey);
String orgData = "然而也应当记住黑暗的时日";
System.out.println("原始数据:" + orgData);
String encryptStr = encrypt(orgData,publicKey);
System.out.println("加密数据:" + encryptStr);
String decryptStr = decrypt(encryptStr,privateKey);
System.out.println("解密数据:" + decryptStr);
if(orgData.equals(decryptStr)){
System.out.println("==========原始数据与解密数据一致==========");
}else {
System.out.println("==========原始数据与解密数据不一致==========");
}
} catch (Exception e) {
e.printStackTrace();
}
}
也是直接在工具类里写的,运行,控制台输出:
公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/+dP4fJbr4EjhLxjUMcI7zOpnOES5VBnM03Uf
w5yuPglwc2UVeV0B12AC3GIwTZ/94Ohf0+a3g9ecx6CVEzyYeuIfnOjXhM7waVZdR8LcbwsN8kgX
92lpc2qNSquMRU57Wt/cxSoarBXyh1DcCRxzQFACEc04voeVRHzuB6QKZwIDAQAB
私钥:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL/50/h8luvgSOEvGNQxwjvM6mc4
RLlUGczTdR/DnK4+CXBzZRV5XQHXYALcYjBNn/3g6F/T5reD15zHoJUTPJh64h+c6NeEzvBpVl1H
wtxvCw3ySBf3aWlzao1Kq4xFTnta39zFKhqsFfKHUNwJHHNAUAIRzTi+h5VEfO4HpApnAgMBAAEC
gYBLBwt1yNN++hfhkfOFMrEzh+FwV8hcGec/asESmfOJEYvE3AR8gQL9bjwCwjjJofzOTvDiSsGX
pTpF9qrmuC7sxqKN+xVdAJzoTIzUSXd/Zd/RLhxDWHLSppyJ5c1n1pikDKrAkUqfWnp2gjMUT4Pd
pU4WH/W7IFV9H0o+AOCbcQJBAPV/KN871ydNkwQL8+zu2L3iXTHMWKmEv40AXI+vh9c5iS08fiJi
VL3TQjC/W8SQ4Emj2lF48XSIdTO7gPFF7I8CQQDIMHkrFjeWYyk6QuCC5mV9F95RyQDk0yvrcLtg
aEvA9wZVv/FcZOq7I2ejcYWt1wi99sXpFwdZ0oiI3GZFByCpAkEAo+RIfP+OG4cGZuUz6zFpMRs1
7FDnwAQHfTKImMQug9i9Y53G911+BVxMDA80TH4Lvh3NWibLy2huFiNPacOssQJAU2fmw+3gwRaV
ccG1WrR1alYMeZS+e5gD/3cbioJJtZ72E7oB7JXbOpb4sh81LAWgjc0IDiJbHLBb1HHHZlEe6QJB
ANzDR19vzmit+miwFkNbVfCPSFX0oKbWZaNfkJ+zmg4scCDppny4S2pxZQ+zyOGATXUlJH3YvHd2
lqDpIbXxong=
原始数据:然而也应当记住黑暗的时日
加密数据:fi//U40WewohsDAva8u0j71pzwOsIoCAxpR1aTvNr7HnU3pmU1MmePonePePab0cZBwIvl/3TQyV
GLduPS2XOdIIqKrT28bXLL02f7UXkFCICrD/JiotClG6gbKOtEKCpgOIKKoLEXA7RPDLzw7QFSjz
8m9z4s3/uHjrCcaaTwY=
解密数据:然而也应当记住黑暗的时日
==========原始数据与解密数据一致==========
进程已结束,退出代码0
没什么问题,加解密能够应用就行了,其中的原理不必深究了。。。
标签:公钥,加密,String,RSA,Java,私钥,MD5 From: https://www.cnblogs.com/qiyuanc/p/Back11.html