說明
-
非對稱加密有公鑰和私鑰兩個概念,私鑰自己擁有,不能給別人,公鑰公開。根據應用的不同,我們可以選擇使用不同的密鑰加密。
-
簽名:使用私鑰加密,公鑰解密。用於讓所有公鑰所有者驗證私鑰所有者的身份並且用來防止私鑰所有者發布的內容被篡改,但是不用來保證內容不被他人獲得。
-
加密:用公鑰加密,私鑰解密。用於向公鑰所有者發布信息,這個信息可能被他人篡改,但是無法被他人獲得。
工具引入了hutool,可使用maven方式引入,也可直接下載hutool-all.jar,具體使用方法參考https://hutool.cn/docs/#/
一、定義全局參數
// 構建算法
private static final String ALGORITHM = "RSA";
// ClassPath 項目資源目錄
private static final String ROOT_PATH = ClassUtil.getClassPath();
二、生成公鑰和私鑰文件
// 使用 SecureUtil 創建一個生成器
KeyPair pair = SecureUtil.generateKeyPair(ALGORITHM);
// 使用pair.getPrivate()可生成秘鑰
// 這里把秘鑰轉化為字節,保存成文件
byte[] privateKey = pair.getPrivate().getEncoded();
FileUtil.writeBytes(privateKey, privateKeyUrl);
// 同樣的,使用pair.getPublic()可以得到公鑰
// 這里把公鑰的字節碼轉化為Base64后保存,便於查看和傳輸
String publicKey = Base64.encode(pair.getPublic().getEncoded());
FileUtil.writeUtf8String(publicKey, publicKeyUrl);
三、根據文件內容生成公鑰和私鑰
// 獲取私鑰:讀取私鑰文件內容轉化為PrivateKey對象
public static PrivateKey getPrivateKey(String name) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keyBytes = FileUtil.readBytes(ROOT_PATH + "/cer/" + name + ".pfx");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
// 獲取公鑰:讀取公鑰文件內容轉化為PublicKey對象
public static PublicKey getPublicKey(String name) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
String keyStr = FileUtil.readUtf8String(ROOT_PATH + "/cer/" + name + ".cer");
byte[] keyBytes = (new BASE64Decoder()).decodeBuffer(keyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(keySpec);
}
四、私鑰加密,公鑰解密
// 私鑰加密
// 獲取RSA對象,RSA構造函數有2個參數,RSA(PrivateKey,PublicKey),私鑰加密指向傳入PrivateKey
RSA rsa = new RSA(getPrivateKey(name), null);
// 調用RSA的encrypt方法把content名為進行加密
byte[] bytes = rsa.encrypt(content, KeyType.PrivateKey);
// 然后把加密后的字節碼轉為Hex碼,便於傳輸和存儲
Convert.toHex(bytes)
// 公鑰解密
// 和私鑰加密一樣傳入公鑰參數PublicKey
RSA rsa = new RSA(null, getPublicKey(name));
// 把密文轉化為字節碼,然后使用RSA的decrypt方法進行解密
byte[] bytes = rsa.decrypt(Convert.hexToBytes(content), KeyType.PublicKey);
// 最后把解密后的字節碼轉化為明文,這樣就達到解密的目的
Convert.hexToStr(Convert.toHex(bytes), CharsetUtil.CHARSET_UTF_8)
五、公鑰加密,私鑰解密
// 公鑰加密
// 獲取RSA對象,RSA構造函數有2個參數,RSA(PrivateKey,PublicKey),公鑰加密指向傳入PublicKey
RSA rsa = new RSA(null, getPublicKey(name));
// 調用RSA的encrypt方法把content名為進行加密
byte[] bytes = rsa.encrypt(content, KeyType.PublicKey);
// 然后把加密后的字節碼轉為Hex碼,便於傳輸和存儲
Convert.toHex(bytes)
// 私鑰解密
// 和公鑰加密一樣傳入公鑰參數PrivateKey
RSA rsa = new RSA(getPrivateKey(name), null);
// 把密文轉化為字節碼,然后使用RSA的decrypt方法進行解密
byte[] bytes = rsa.decrypt(Convert.hexToBytes(content), KeyType.PrivateKey);
// 最后把解密后的字節碼轉化為明文,這樣就達到解密的目的
Convert.hexToStr(Convert.toHex(bytes), CharsetUtil.CHARSET_UTF_8)
六、私鑰數據簽名,公鑰數據驗簽
// 私鑰數據簽名
// 步驟:
// 1.把內容進行MD5后;
String contentMd5 = SecureUtil.md5(content);
// 2.使用私鑰把步驟1的內容進行加密,加密后的內容就是簽名字符串。
rencrypt(contentMd5, name)
// 公鑰數據驗簽
// 步驟:
// 1.使用公鑰把簽名字符串進行解密,得到私鑰簽名的源數據的MD5值。
String rdecrypt = rdecrypt(sign, name);
// 2.把需要的驗證的內容進行MD5后,和解密得到的MD5進行對比。MD5值相同,說明私鑰簽名的源數據和實際內容一致。
Objects.equals(SecureUtil.md5(content), rdecrypt)
七、公鑰數據簽名,私鑰數據驗簽
// 公鑰數據簽名
// 步驟:
// 1.把內容進行MD5后;
String contentMd5 = SecureUtil.md5(content);
// 2.使用公鑰把步驟1的內容進行加密,加密后的內容就是簽名字符串。
uencrypt(contentMd5, name);
// 私鑰數據驗簽
// 步驟:
// 1.使用私鑰把簽名字符串進行解密,得到公鑰簽名的源數據的MD5值。
String udecrypt = udecrypt(sign, name);
// 2.把需要的驗證的內容進行MD5后,和解密得到的MD5進行對比。MD5值相同,說明公鑰簽名的源數據和實際內容一致。
Objects.equals(SecureUtil.md5(content), udecrypt);
八、完整代碼
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Decoder;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Objects;
/**
* 非對稱加密有公鑰和私鑰兩個概念,私鑰自己擁有,不能給別人,公鑰公開。根據應用的不同,我們可以選擇使用不同的密鑰加密:
* 簽名:使用私鑰加密,公鑰解密。用於讓所有公鑰所有者驗證私鑰所有者的身份並且用來防止私鑰所有者發布的內容被篡改,但是不用來保證內容不被他人獲得。
* 加密:用公鑰加密,私鑰解密。用於向公鑰所有者發布信息,這個信息可能被他人篡改,但是無法被他人獲得。
*
* @author lixingwu
*/
@Slf4j
public class AsymmetricCryptoUtils {
/**
* 構建算法
*/
private static final String ALGORITHM = "RSA";
/**
* ClassPath
*/
private static final String ROOT_PATH = ClassUtil.getClassPath();
/**
* <p>
* 方法描述:根據名字生成密鑰對.
* 公鑰:{classpath}/cer/{name}.cer
* 私鑰:{classpath}/cer/{name}.pfx
* 如果已經存在公鑰或者私鑰,將不會重復生成
* </p>
* <p> 創建時間:2019-07-08 11:06:40 </p>
* <p> 創建作者:李興武 </p>
*
* @param name the name
* @return 返回密鑰對的地址 publicKeyPath:公鑰地址、privateKeyPath:私鑰地址
* @author "lixingwu"
*/
public static HashMap<String, String> generateKeyPair(String name) {
KeyPair pair = SecureUtil.generateKeyPair(ALGORITHM);
// 判斷私鑰和公鑰是否已經存在
String publicKeyPath = "/cer/" + name + ".cer";
String privateKeyPath = "/cer/" + name + ".pfx";
String publicKeyUrl = ROOT_PATH + publicKeyPath;
String privateKeyUrl = ROOT_PATH + privateKeyPath;
// 只有公鑰或者私鑰不存在時才生成
if (!FileUtil.exist(publicKeyUrl) || !FileUtil.exist(privateKeyUrl)) {
// 保存私鑰
byte[] privateKey = pair.getPrivate().getEncoded();
FileUtil.writeBytes(privateKey, privateKeyUrl);
log.info("{}的私鑰保存地址:{}", name, privateKeyUrl);
// 保存公鑰
String publicKey = Base64.encode(pair.getPublic().getEncoded());
FileUtil.writeUtf8String(publicKey, publicKeyUrl);
log.info("{}的公鑰保存地址:{}", name, publicKeyUrl);
} else {
log.info("{}對應的密鑰對已存在", name);
}
HashMap<String, String> map = CollUtil.newHashMap();
map.put("publicKeyPath", publicKeyPath);
map.put("privateKeyPath", privateKeyPath);
return map;
}
/**
* <p> 方法描述:根據名字獲取對應的私鑰. </p>
* <p> 創建時間:2019-07-08 12:05:56 </p>
* <p> 創建作者:李興武 </p>
*
* @param name the name
* @return the private key
* @throws NoSuchAlgorithmException 創建key失敗
* @throws InvalidKeySpecException 生成私鑰失敗
* @author "lixingwu"
*/
public static PrivateKey getPrivateKey(String name) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keyBytes = FileUtil.readBytes(ROOT_PATH + "/cer/" + name + ".pfx");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePrivate(keySpec);
}
/**
* <p> 方法描述:獲取公鑰. </p>
* <p> 創建時間:2019-07-08 12:08:30 </p>
* <p> 創建作者:李興武 </p>
*
* @param name the name
* @return the public key
* @throws NoSuchAlgorithmException 創建key失敗
* @throws InvalidKeySpecException 生成公鑰失敗
* @throws IOException 證書解碼失敗
* @author "lixingwu"
*/
public static PublicKey getPublicKey(String name) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
String keyStr = FileUtil.readUtf8String(ROOT_PATH + "/cer/" + name + ".cer");
byte[] keyBytes = (new BASE64Decoder()).decodeBuffer(keyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(keySpec);
}
///////////////////////////////////////////////////////////////////
/////////////////////// 私鑰加密,公鑰解密 ///////////////////////////
///////////////////////////////////////////////////////////////////
/**
* <p> 方法描述:私鑰加密. </p>
* <p> 創建時間:2019-07-08 12:18:44 </p>
* <p> 創建作者:李興武 </p>
*
* @param content 內容
* @param name 名字
* @return the string
* @throws NoSuchAlgorithmException 創建key失敗
* @throws InvalidKeySpecException 生成私鑰失敗
* @author "lixingwu"
*/
public static String rencrypt(String content, String name) throws InvalidKeySpecException, NoSuchAlgorithmException {
if (Tools.isBlank(content)) {
return "";
}
RSA rsa = new RSA(getPrivateKey(name), null);
byte[] bytes = rsa.encrypt(content, KeyType.PrivateKey);
return Convert.toHex(bytes);
}
/**
* <p> 方法描述:公鑰解密. </p>
* <p> 創建時間:2019-07-08 12:18:44 </p>
* <p> 創建作者:李興武 </p>
*
* @param content 內容
* @param name 名字
* @return the string
* @throws NoSuchAlgorithmException 創建key失敗
* @throws InvalidKeySpecException 生成公鑰失敗
* @author "lixingwu"
*/
public static String rdecrypt(String content, String name) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
if (Tools.isBlank(content)) {
return "";
}
RSA rsa = new RSA(null, getPublicKey(name));
byte[] bytes = rsa.decrypt(Convert.hexToBytes(content), KeyType.PublicKey);
return Convert.hexToStr(Convert.toHex(bytes), CharsetUtil.CHARSET_UTF_8);
}
///////////////////////////////////////////////////////////////////
/////////////////////// 公鑰加密,私鑰解密 ///////////////////////////
///////////////////////////////////////////////////////////////////
/**
* <p> 方法描述:公鑰加密. </p>
* <p> 創建時間:2019-07-08 12:18:44 </p>
* <p> 創建作者:李興武 </p>
*
* @param content 內容
* @param name 名字
* @return the string
* @throws NoSuchAlgorithmException 創建key失敗
* @throws InvalidKeySpecException 生成公鑰失敗
* @author "lixingwu"
*/
public static String uencrypt(String content, String name) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
if (Tools.isBlank(content)) {
return "";
}
RSA rsa = new RSA(null, getPublicKey(name));
byte[] bytes = rsa.encrypt(content, KeyType.PublicKey);
return Convert.toHex(bytes);
}
/**
* <p> 方法描述:私鑰解密. </p>
* <p> 創建時間:2019-07-08 12:18:44 </p>
* <p> 創建作者:李興武 </p>
*
* @param content 內容
* @param name 名字
* @return the string
* @throws NoSuchAlgorithmException 創建key失敗
* @throws InvalidKeySpecException 生成公鑰失敗
* @author "lixingwu"
*/
public static String udecrypt(String content, String name) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
if (Tools.isBlank(content)) {
return "";
}
RSA rsa = new RSA(getPrivateKey(name), null);
byte[] bytes = rsa.decrypt(Convert.hexToBytes(content), KeyType.PrivateKey);
return Convert.hexToStr(Convert.toHex(bytes), CharsetUtil.CHARSET_UTF_8);
}
///////////////////////////////////////////////////////////////////
///////////////////// 私鑰數據簽名,公鑰數據驗簽 //////////////////////
///////////////////////////////////////////////////////////////////
/**
* <p> 方法描述:私鑰數據簽名,把內容進行MD5后,使用私鑰進行數據加密. </p>
* <p> 創建時間:2019-07-08 15:15:42 </p>
* <p> 創建作者:李興武 </p>
*
* @param content 簽名的內容
* @param name 名字
* @return 返回簽名MD5
* @author "lixingwu"
*/
public static String rsign(String content, String name) throws InvalidKeySpecException, NoSuchAlgorithmException {
// 內容MD5后私鑰數據加密
String contentMd5 = SecureUtil.md5(content);
return rencrypt(contentMd5, name);
}
/**
* <p>
* 方法描述:公鑰數據驗簽.
* 1.簽名內容使用公鑰進行數據解密后得到一串MD5,此MD5值就是私鑰簽名的源數據的MD5值。
* 2.然后把解密得到的MD5和實際內容的MD5值進行對比,MD5值相同,說明私鑰簽名的源數據和實際內容一致。
* </p>
* <p> 創建時間:2019-07-08 15:37:23 </p>
* <p> 創建作者:李興武 </p>
*
* @param name 名字
* @param sign 簽名字符串
* @param content 需要驗證的字符串
* @return the boolean
* @author "lixingwu"
*/
public static boolean rverify(String name, String sign, String content) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
// 簽名內容sign公鑰數據解密后得到內容MD5
String rdecrypt = rdecrypt(sign, name);
// 把解密得到的MD5和實際內容content的MD5進行對比
return Objects.equals(SecureUtil.md5(content), rdecrypt);
}
///////////////////////////////////////////////////////////////////
///////////////////// 公鑰數據簽名,私鑰數據驗簽 //////////////////////
///////////////////////////////////////////////////////////////////
/**
* <p> 方法描述:公鑰數據簽名,把內容進行MD5后,使用公鑰進行數據加密. </p>
* <p> 創建時間:2019-07-08 15:15:42 </p>
* <p> 創建作者:李興武 </p>
*
* @param content 簽名的內容
* @param name 名字
* @return 返回簽名MD5
* @author "lixingwu"
*/
public static String usign(String content, String name) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
// 內容MD5后私鑰數據加密
String contentMd5 = SecureUtil.md5(content);
return uencrypt(contentMd5, name);
}
/**
* <p>
* 方法描述:私鑰數據驗簽.
* 1.簽名內容使用私鑰進行數據解密后得到一串MD5,此MD5值就是公鑰簽名的源數據的MD5值。
* 2.然后把解密得到的MD5和實際內容的MD5值進行對比,MD5值相同,說明公鑰簽名的源數據和實際內容一致。
* </p>
* <p> 創建時間:2019-07-08 15:37:23 </p>
* <p> 創建作者:李興武 </p>
*
* @param name 名字
* @param sign 簽名字符串
* @param content 需要驗證的字符串
* @return the boolean
* @throws InvalidKeySpecException the invalid key spec exception
* @throws NoSuchAlgorithmException the no such algorithm exception
* @throws IOException the io exception
* @author "lixingwu"
*/
public static boolean uverify(String name, String sign, String content) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
// 簽名內容sign公鑰數據解密后得到內容MD5
String udecrypt = udecrypt(sign, name);
// 把解密得到的MD5和實際內容content的MD5進行對比
return Objects.equals(SecureUtil.md5(content), udecrypt);
}
}