一裳仆、RSA加密簡介
RSA加密是一種非對稱加密腕让」虑眨可以在不直接傳遞密鑰的情況下,完成解密纯丸。這能夠確保信息的安全性偏形,避免了直接傳遞密鑰所造成的被破解的風險。是由一對密鑰來進行加解密的過程觉鼻,分別稱為公鑰和私鑰俊扭。兩者之間有數(shù)學相關(guān),該加密算法的原理就是對一極大整數(shù)做因數(shù)分解的困難性來保證安全性坠陈。通常個人保存私鑰萨惑,公鑰是公開的(可能同時多人持有)捐康。
二、RSA加密庸蔼、簽名區(qū)別
加密和簽名都是為了安全性考慮解总,但略有不同。常有人問加密和簽名是用私鑰還是公鑰姐仅?其實都是對加密和簽名的作用有所混淆花枫。簡單的說,加密是為了防止信息被泄露掏膏,而簽名是為了防止信息被篡改劳翰。這里舉2個例子說明。
第一個場景:戰(zhàn)場上馒疹,B要給A傳遞一條消息佳簸,內(nèi)容為某一指令。
RSA的加密過程如下:
1行冰、A生成一對密鑰(公鑰和私鑰)溺蕉,私鑰不公開,A自己保留悼做。公鑰為公開的疯特,任何人可以獲取。
2肛走、A傳遞自己的公鑰給B漓雅,B用A的公鑰對消息進行加密。
3朽色、A接收到B加密的消息邻吞,利用A自己的私鑰對消息進行解密。
在這個過程中葫男,只有2次傳遞過程抱冷,第一次是A傳遞公鑰給B,第二次是B傳遞加密消息給A梢褐,即使都被敵方截獲旺遮,也沒有危險性,因為只有A的私鑰才能對消息進行解密盈咳,防止了消息內(nèi)容的泄露耿眉。
第二個場景:A收到B發(fā)的消息后,需要進行回復“收到”鱼响。
RSA簽名的過程如下:
1鸣剪、A生成一對密鑰(公鑰和私鑰),私鑰不公開,A自己保留筐骇。公鑰為公開的债鸡,任何人可以獲取。
2铛纬、A用自己的私鑰對消息加簽娘锁,形成簽名,并將加簽的消息和消息本身一起傳遞給B饺鹃。
3莫秆、B收到消息后,在獲取A的公鑰進行驗簽悔详,如果驗簽出來的內(nèi)容與消息本身一致镊屎,證明消息是A回復的。
在這個過程中茄螃,只有2次傳遞過程缝驳,第一次是A傳遞加簽的消息和消息本身給B,第二次是B獲取A的公鑰归苍,即使都被敵方截獲用狱,也沒有危險性,因為只有A的私鑰才能對消息進行簽名拼弃,即使知道了消息內(nèi)容夏伊,也無法偽造帶簽名的回復給B,防止了消息內(nèi)容的篡改吻氧。
但是溺忧,綜合兩個場景你會發(fā)現(xiàn),第一個場景雖然被截獲的消息沒有泄露盯孙,但是可以利用截獲的公鑰鲁森,將假指令進行加密,然后傳遞給A振惰。第二個場景雖然截獲的消息不能被篡改歌溉,但是消息的內(nèi)容可以利用公鑰驗簽來獲得,并不能防止泄露骑晶。所以在實際應(yīng)用中痛垛,要根據(jù)情況使用,也可以同時使用加密和簽名透罢,比如A和B都有一套自己的公鑰和私鑰榜晦,當A要給B發(fā)送消息時冠蒋,先用B的公鑰對消息加密羽圃,再對加密的消息使用A的私鑰加簽名,達到既不泄露也不被篡改,更能保證消息的安全性朽寞。
總結(jié):公鑰加密识窿、私鑰解密、私鑰簽名脑融、公鑰驗簽喻频。
三、RSA加密肘迎、簽名的方法甥温,代碼例子如下:
3.1 首先引入commons-codec
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
3.2 新建密鑰對對象
/**
* @author: huangyibo
* @Date: 2022/4/29 18:47
* @Description: 非對稱加密 密鑰對對象
*/
public class RsaKeyPair {
private String publicKey;
private String privateKey;
public RsaKeyPair(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
}
3.3 構(gòu)建RSA工具類
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author: huangyibo
* @Date: 2022/5/6 15:52
* @Description:
*/
public class RSAUtil {
/**
* RSA編碼
*/
public static final String ALGORITHM = "RSA";
public static final String SIGN_ALGORITHM = "SHA512withRSA";
/**
* 默認種子, 構(gòu)建RSA密鑰對, 生成的密鑰對不變
*/
private static final String DEFAULT_SEED = "0f22507a10bbddd07d8a3082122966e3";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 構(gòu)建RSA密鑰對
* @return
* @throws NoSuchAlgorithmException
*/
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
// 初始化隨機產(chǎn)生器
/*SecureRandom secureRandom = new SecureRandom();
//加入默認種子, 生成的密鑰對不變
secureRandom.setSeed(DEFAULT_SEED.getBytes());
keyPairGenerator.initialize(1024, secureRandom);*/
// 不加入默認種子, 每次生成的密鑰對會變化
keyPairGenerator.initialize(1024, new SecureRandom());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
return new RsaKeyPair(publicKeyString, privateKeyString);
}
/**
* RSA 公鑰加密
* @param data 加密字符串
* @param publicKeyText 公鑰
* @return 密文
* @throws Exception 加密過程中的異常信息
*/
public static String encryptByPublicKey(String data, String publicKeyText) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = data.getBytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 對數(shù)據(jù)分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 獲取加密內(nèi)容使用base64進行編碼,并以UTF-8為標準轉(zhuǎn)化成字符串
// 加密后的字符串
return Base64.encodeBase64String(encryptedData);
}
/**
* RSA 私鑰解密
* @param data 加密字符串
* @param privateKeyText 私鑰
* @return 明文
* @throws Exception 解密過程中的異常信息
*/
public static String decryptByPrivateKey(String data, String privateKeyText) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 對數(shù)據(jù)分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
// 解密后的內(nèi)容
return new String(decryptedData, StandardCharsets.UTF_8);
}
/**
* 私鑰加密
*
* @param data 加密數(shù)據(jù)
* @param privateKeyText 私鑰
* @return 密文
* @throws Exception 加密過程中的異常信息
*/
public static String encryptByPrivateKey(String data, String privateKeyText) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
int inputLen = data.getBytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 對數(shù)據(jù)分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 獲取加密內(nèi)容使用base64進行編碼,并以UTF-8為標準轉(zhuǎn)化成字符串
// 加密后的字符串
return Base64.encodeBase64String(encryptedData);
}
/**
* 公鑰解密
*
* @param data 加密字符串
* @param publicKeyText 公鑰
* @return 明文
* @throws Exception 解密過程中的異常信息
*/
public static String decryptByPublicKey(String data, String publicKeyText) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 對數(shù)據(jù)分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
// 解密后的內(nèi)容
return new String(decryptedData, StandardCharsets.UTF_8);
}
/**
* 簽名
*
* @param data 待簽名數(shù)據(jù)
* @param privateKey 私鑰
* @return 簽名
*/
public static String sign(String data, String privateKey) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PrivateKey key = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initSign(key);
signature.update(data.getBytes());
return new String(Base64.encodeBase64(signature.sign()));
}
/**
* 驗簽
*
* @param srcData 原始字符串
* @param publicKey 公鑰
* @param sign 簽名
* @return 是否驗簽通過
*/
public static boolean verify(String srcData, String publicKey, String sign) throws Exception {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGN_ALGORITHM);
signature.initVerify(key);
signature.update(srcData.getBytes());
return signature.verify(Base64.decodeBase64(sign.getBytes()));
}
public static void main(String[] args) {
try {
// 生成密鑰對
RsaKeyPair rsaKeyPair = generateKeyPair();
System.out.println("私鑰:" + rsaKeyPair.getPrivateKey());
System.out.println("公鑰:" + rsaKeyPair.getPublicKey());
// RSA 公鑰加密
String data = "待加密的文字內(nèi)容";
String encryptPublicKeyData = encryptByPublicKey(data, rsaKeyPair.getPublicKey());
System.out.println("公鑰加密后內(nèi)容:" + encryptPublicKeyData);
// RSA 私鑰解密
String decryptPrivateKeyData = decryptByPrivateKey(encryptPublicKeyData, rsaKeyPair.getPrivateKey());
System.out.println("私鑰解密后內(nèi)容:" + decryptPrivateKeyData);
// RSA 私鑰加密
String data1 = "待加密的文字內(nèi)容";
String encryptPrivateKeyData = encryptByPrivateKey(data1, rsaKeyPair.getPrivateKey());
System.out.println("私鑰加密后內(nèi)容:" + encryptPrivateKeyData);
// RSA 公鑰解密
String decryptPublicKeyData = decryptByPublicKey(encryptPrivateKeyData, rsaKeyPair.getPublicKey());
System.out.println("公鑰解密后內(nèi)容:" + decryptPublicKeyData);
// RSA簽名
String sign = sign(data, rsaKeyPair.getPrivateKey());
// RSA驗簽
boolean result = verify(data, rsaKeyPair.getPublicKey(), sign);
System.out.println("驗簽結(jié)果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.print("加解密異常");
}
}
}
PS:RSA加密對明文的長度有所限制,規(guī)定需加密的明文最大長度=密鑰長度-11(單位是字節(jié)妓布,即byte)姻蚓,所以在加密和解密的過程中需要分塊進行。而密鑰默認是1024位匣沼,即1024位/8位-11=128-11=117字節(jié)狰挡。所以默認加密前的明文最大長度117字節(jié),解密密文最大長度為128字释涛。那么為啥兩者相差11字節(jié)呢加叁?是因為RSA加密使用到了填充模式(padding),即內(nèi)容不足117字節(jié)時會自動填滿唇撬,用到填充模式自然會占用一定的字節(jié)它匕,而且這部分字節(jié)也是參與加密的。
密鑰長度的設(shè)置就是上面例子的keyPairGenerator.initialize(1024, new SecureRandom());
窖认〕剩可自行調(diào)整,當然非對稱加密隨著密鑰變長耀态,安全性上升的同時性能也會有所下降轮傍。