相對對稱加密而言,無需擁有同一組密鑰,非對稱加密是一種“信息公開的密鑰交換協議”。非對稱加密需要公開密鑰和私有密鑰兩組密鑰,公開密鑰和私有密鑰是配對起來的,也就是說使用公開密鑰進行數據加密,只有對應的私有密鑰才能解密。這兩個密鑰是數學相關,用某用戶密鑰加密后的密文,只能使用該用戶的加密密鑰才能解密。如果知道了其中一個,並不能計算出另外一個。因此如果公開了一對密鑰中的一個,並不會危害到另外一個密鑰性質。這里把公開的密鑰為公鑰,不公開的密鑰為私鑰。算法代表:RSA,DSA。
RSA 能同時用於加密和數字簽名,而DSA只能用於簽名,本文重點講解RSA。
1、RSA(算法的名字以發明者的名字命名:Ron Rivest, AdiShamir 和Leonard Adleman)
這種算法1978年就出現了,它是第一個既能用於數據加密也能用於數字簽名的算法。它易於理解和操作,也很流行。算法的名字以發明者的名字命名:Ron Rivest, AdiShamir 和Leonard Adleman。
這種加密算法的特點主要是密鑰的變化,上文我們看到DES只有一個密鑰。相當於只有一把鑰匙,如果這把鑰匙丟了,數據也就不安全了。RSA同時有兩把鑰匙,公鑰與私鑰。同時支持數字簽名。數字簽名的意義在於,對傳輸過來的數據進行校驗。確保數據在傳輸工程中不被修改。
RSA 加密工具類:
package com.blog.www.util.coder.asymmetrical;
import com.blog.www.util.coder.base.BaseCoderUtils;
import lombok.extern.slf4j.Slf4j;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/** * RSA非對稱加密解密工具類 * * <p> * 這種算法1978年就出現了,它是第一個既能用於數據加密也能用於數字簽名的算法。它易於理解和操作,也很流行。 算法的名字以發明者的名字命名:Ron * Rivest, AdiShamir 和Leonard Adleman。 這種加密算法的特點主要是密鑰的變化,RSA同時有兩把鑰匙,公鑰與私鑰。 * 同時支持數字簽名。數字簽名的意義在於,對傳輸過來的數據進行校驗。確保數據在傳輸工程中不被修改。 * <p> * 參考: * <br/> * <ul> * <li><a href='https://www.iteye.com/blog/snowolf-381767'>Java加密技術(四)——非對稱加密算法RSA</a></li> * <li><a href='https://my.oschina.net/jiangli0502/blog/171263'>RSA加密解密及數字簽名Java實現</a></li> * </ul> * <p> * <a href="https://github.com/wwwtyro/cryptico">前端demo<a> <br> * <br> */
@Slf4j
public class RSACoder {
/** * 填充方式。這里有【RSA/ECB/PKCS1Padding】填充(默認)和【RSA/ECB/NoPadding】填充兩種可選。 * <p> * 注意:使用填充時,公鑰每次加密的字符串都會不一樣,這樣更安全;不使用則每次都一樣。因為java默認是填充的,而安卓默認不填充, * 所以安卓默認加密的密文,java默認不能解密!!必須手動指定他們用一致的填充方式,才能正確加密解密。 */
private static final String CIPHER_MODE = "RSA/ECB/PKCS1Padding";
/** * 算法類型 */
private static final String ALGORITHM = "RSA";
/** * RSA密鑰長度必須是64的倍數,在512~65536之間。默認是1024 */
private static final int KEY_SIZE = 1024;
/** * 由公鑰字節數組用Base64編碼成的字符串,方便傳播、儲存 */
private static final String PUB_KEY_BASE64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxKRX+P64kA0qrd3JYYZIZ5jam63DsAlx5PKlfC0hOAhJ3wfD2Bjl3CHKNMtEKwcnZlunvikOt7/7uKdVdxDYzwpU2ivwNXDA5kMPsx8prjwS7FsdCMWnOTGWBTCYeReFHWVmSj4KxYaOO7csPWBR0AhQX9qiPSWDEKcnH5YNiiQIDAQAB";
/** * 由私鑰字節數組用Base64編碼成的字符串,方便傳播、儲存 */
private static final String PRI_KEY_BASE64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALEpFf4/riQDSqt3clhhkhnmNqbrcOwCXHk8qV8LSE4CEnfB8PYGOXcIco0y0QrBydmW6e+KQ63v/u4p1V3ENjPClTaK/A1cMDmQw+zHymuPBLsWx0Ixac5MZYFMJh5F4UdZWZKPgrFho47tyw9YFHQCFBf2qI9JYMQpycflg2KJAgMBAAECgYBltrwc1HzrykQFvDeXTLWwTv+TyFt19UkYhl6L5hNmTkRCI8RvzFUT5XK3ZLSmY2q7lazMTerbo44POU96XVvsV+ltmUW4ohh1cf+q7ICz73r+OEaFdxa+wHFthXvMuKpFbDiH89HfAmGGUVezf7dByClyVxn3yuKlb42ZC6AdsQJBAOyA+mBot7a98txTrMl4jRD1MI9t0dWHA51JzJ2vLBX4IlEr944Qhb6N0lNtYChEkEOCrTlLIDxWUtQhZbTP6R0CQQC/w8PcHulcbCY1JhBTefyzA4jmm9LZ0c+byqCSEiffE6/neTMLOxUpt9zDvtdWw7UvMZWgQ4a8QGZrlCw3Lw9dAkEAg9cqvE/kChU9q6FhszZmvUtR9MLttLD9TNN1I3ohg2W+C64M5L9FL4Lz+toAPrJqEZhpZIUCxWAB8ItlnTRB6QJBAKUMwsv3kxUoRG5kV5LxoK0XMsKBhaZSrmTBrxhqJgUbtb/+Eg/th1aD2LBl1oPoKE75V3Y8CICI0V5whunsSEUCQE1ZvMp5a0yblGENWU5F+kWT3aBCkmMN8Zqp2+R5p8kQ7Chxv7llCZ405YXnTdEQyLp+q6OW+eu0TdIQ3qHkA4c=";
/** * 公鑰對象 */
static PublicKey PUB_KEY;
/** * 私鑰對象 */
static PrivateKey PRI_KEY;
// 初始化
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 靜態代碼塊,初始化密鑰對象,供后面使用
static {
try {
PUB_KEY = restorePubKey();
PRI_KEY = restorePriKey();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
log.error("初始化出錯", e);
}
}
/** * 從 .p12 文件中讀取私鑰。 <br> * <br> * 創建人: leigq <br> * 創建時間: 2017年10月28日 下午4:21:56 <br> * * @param pfxKeyFileName .p12文件路徑 * @param aliasName 私鑰別名 * @param pfxPassword 私鑰密碼 * @return 私鑰對象 */
public static PrivateKey readP12Key(String pfxKeyFileName, String aliasName, String pfxPassword)
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
InputStream fis = new FileInputStream(pfxKeyFileName);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(fis, pfxPassword.toCharArray());
return (PrivateKey) keyStore.getKey(aliasName, pfxPassword.toCharArray());
}
// 加密、解密
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
/** * 通用加密操作 * <p> * 創建人:leigq <br> * 創建時間:2018年8月18日 下午5:37:32 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> * * @param key 公鑰或密鑰對象 * @param data 明文字符串 * @return 密文字節數組用Base64算法編碼成的字符串,方便傳輸、儲存 */
public static String encrypt(Key key, String data)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException {
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return BaseCoderUtils.encryptBase64(bytes);
}
/** * 通用解密操作 * <p> * 創建人:leigq <br> * 創建時間:2018年8月18日 下午5:43:34 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> * * @param key 公鑰或密鑰對象 * @param data 密文字符串(由密文字節數組用Base64算法編碼成的字符串) * @return 明文字符串 */
public static String decrypt(Key key, String data)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException,
IllegalBlockSizeException, BadPaddingException {
Cipher cipher = Cipher.getInstance(CIPHER_MODE);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] bytes = cipher.doFinal(BaseCoderUtils.decryptBase64(data));
return new String(bytes, StandardCharsets.UTF_8);
}
/** * 用公鑰加密 <br> * <br> * 創建人: leigq <br> * 創建時間: 2017年10月24日 下午2:00:49 <br> * * @param decoded 明文字符串 * @return 密文字節數組用Base64算法編碼成的字符串,方便傳輸、儲存 */
public static String encryptByPubKey(String decoded)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
IllegalBlockSizeException, BadPaddingException {
return encrypt(PUB_KEY, decoded);
}
/** * 用私鑰解密 <br> * <br> * 創建人: leigq <br> * 創建時間: 2017年10月24日 下午1:57:42 <br> * * @param encoded 密文字符串(由密文字節數組用Base64算法編碼成的字符串) * @return 明文字符串 */
public static String decryptByPriKey(String encoded) throws NoSuchPaddingException, BadPaddingException,
NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidKeyException {
return decrypt(PRI_KEY, encoded);
}
/** * 用私鑰加密 * <p> * 創建人:leigq <br> * 創建時間:2018年8月18日 下午5:37:32 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> * * @param decoded 明文字符串 * @return 密文字節數組用Base64算法編碼成的字符串,方便傳輸、儲存 */
public static String encryptByPriKey(String decoded)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
BadPaddingException {
return encrypt(PRI_KEY, decoded);
}
/** * 用公鑰解密 * <p> * 創建人:leigq <br> * 創建時間:2018年8月18日 下午5:43:34 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> * * @param encoded 密文字符串(由密文字節數組用Base64算法編碼成的字符串) * @return 明文字符串 */
public static String decryptByPubKey(String encoded)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException,
IllegalBlockSizeException, BadPaddingException {
return decrypt(PUB_KEY, encoded);
}
/** * 還原公鑰,X509EncodedKeySpec 用於構建公鑰的規范 * <p> * 創建人:leigq <br> * 創建時間:2018年8月18日 下午5:16:50 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> * * @return 公鑰對象 */
private static PublicKey restorePubKey()
throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(BaseCoderUtils.decryptBase64(RSACoder.PUB_KEY_BASE64));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
return factory.generatePublic(x509EncodedKeySpec);
}
/** * 還原私鑰,PKCS8EncodedKeySpec 用於構建私鑰的規范 * <p> * 創建人:leigq <br> * 創建時間:2018年8月18日 下午5:19:09 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> * * @return 私鑰對象 */
private static PrivateKey restorePriKey()
throws NoSuchAlgorithmException, InvalidKeySpecException {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(BaseCoderUtils.decryptBase64(RSACoder.PRI_KEY_BASE64));
KeyFactory factory = KeyFactory.getInstance(ALGORITHM);
return factory.generatePrivate(pkcs8EncodedKeySpec);
}
// 更換密鑰對
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
/** * 生成密鑰對。注意這里是生成密鑰對KeyPair,再由密鑰對獲取公私鑰 * <p> * 創建人:leigq <br> * 創建時間:2018年8月18日 下午7:35:21 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> */
public static void generateKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
/* 公鑰、私鑰用 encryptBase64 還是 encryptBase64Sun 加密都可以,后者的 Base64 是多行的,比較適合保存到文件的方式儲存 */
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
log.info("新公鑰:{}", BaseCoderUtils.encryptBase64(publicKey.getEncoded()));
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
log.info("新私鑰:{}", BaseCoderUtils.encryptBase64(privateKey.getEncoded()));
} catch (NoSuchAlgorithmException e) {
log.error("生成密鑰對異常:", e);
}
}
}
測試加密:
/** * RSA 測試 */
@Slf4j
class RSATest {
public static void main(String[] args) throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException,
NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
// 生成密鑰對。注意這里是生成密鑰對KeyPair,再由密鑰對獲取公私鑰
RSACoder.generateKeyPair();
log.warn("公鑰加密 >> 私鑰解密");
String originalStr = "helloWorld";
log.warn("原文:{}", originalStr);
/* 公鑰加密 >> 私鑰解密 */
// 公鑰加密
String encryptByPubKey = RSACoder.encryptByPubKey(originalStr);
log.warn("公鑰加密后:" + encryptByPubKey);
// 私鑰解密
String decryptByPriKey = RSACoder.decryptByPriKey(encryptByPubKey);
log.warn("私鑰解密后:" + decryptByPriKey);
/* 私鑰加密 >> 公鑰解密 */
// 私鑰加密
String encryptByPriKey = RSACoder.encryptByPriKey(originalStr);
log.warn("私鑰加密后:" + encryptByPriKey);
// 公鑰解密
String decryptByPubKey = RSACoder.decryptByPubKey(encryptByPriKey);
log.warn("公鑰解密后:" + decryptByPubKey);
}
}
RSA加密、解密測試結果:
RSA 簽名工具類:
package com.blog.www.util.coder.asymmetrical;
import com.blog.www.util.coder.base.BaseCoderUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
/** * RSA簽名驗簽類 */
@Slf4j
public class RSASignature extends RSACoder {
/** * 用哪個算法來簽名 */
private static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 生成簽名 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ */
/** * 用私鑰生成 Base64數字簽名 * <br> * * @param encoded 加密字符串 * @return 簽名,已用Base64編碼 */
public static String signToBase64(String encoded) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature sign = getSign(encoded.getBytes(StandardCharsets.UTF_8));
return BaseCoderUtils.encryptBase64(sign.sign());
}
/** * 用私鑰生成16進制數字簽名 * <br> * * @param encoded 加密字符串 * @return 簽名,已用轉16進制字符串 */
public static String signToHex(String encoded) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature sign = getSign(encoded.getBytes(StandardCharsets.UTF_8));
return Hex.encodeHexString(sign.sign());
}
/* ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 生成簽名 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ */
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 驗證簽名 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ */
/** * 用公鑰校驗 Base64數字簽名 * <br> * * @param encoded 加密數據 * @param signed 簽名,已用Base64編碼 * @return 校驗成功返回true 失敗返回false */
public static boolean verifyFromBase64(String encoded, String signed) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature verify = getVerify(encoded.getBytes(StandardCharsets.UTF_8));
return verify.verify(BaseCoderUtils.decryptBase64(signed));
}
/** * 用公鑰校驗 16進制數字簽名 * <br> * * @param data 加密數據 * @param signed 簽名,是由字節數組轉換成的16進制字符串 */
public static boolean verifyFromHex(String data, String signed) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, DecoderException {
Signature verify = getVerify(data.getBytes(StandardCharsets.UTF_8));
return verify.verify(Hex.decodeHex(signed));
}
/* ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 驗證簽名 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ */
/** * 用私鑰生成數字簽名對象 * <br> * * @param encoded 加密字符串轉換的字節數組 * @return Signature,方便進一步轉換為其他數據 */
private static Signature getSign(byte[] encoded) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(PRI_KEY);
signature.update(encoded);
return signature;
}
/** * 用公鑰生成校驗數字簽名對象 * <br> * * @param data 加密字符串轉換的字節數組 * @return */
private static Signature getVerify(byte[] data) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(PUB_KEY);
signature.update(data);
return signature;
}
}
測試簽名:
/** * RSA 簽名測試 */
@Slf4j
class RSASignTest {
public static void main(String[] args) throws NoSuchAlgorithmException,
InvalidKeyException, SignatureException, DecoderException {
// 上面公鑰加密產生的密文
String ciphertext = "S1E4OieVAlGyrOB7CzKugseg/R9TGSdDhRSna64tTpxJubpndp2AwCrJ3myIoWEqXNBPKlPVo21vd+KQgzhQcX5WxoNXD5sLarmbZ5eFsBNQOuTzwIhSql+zaUKV6qq+RTSYNR4c/fqllh5Mviq73dB42bWbUY7nHlim+jGojPY=";
log.warn("私鑰簽名——公鑰驗證簽名");
// 產生簽名 16 進制簽名
String signToBase64 = RSASignature.signToBase64(ciphertext);
log.warn("簽名:[{}]", signToBase64);
// 驗證簽名
boolean verifyFromBase64 = RSASignature.verifyFromBase64(ciphertext, signToBase64);
log.warn("驗證簽名:[{}]", verifyFromBase64);
// 產生簽名 16 進制簽名
String signToHex = RSASignature.signToHex(ciphertext);
log.warn("簽名:[{}]", signToHex);
// 驗證簽名
boolean verifyFromHex = RSASignature.verifyFromHex(ciphertext, signToHex);
log.warn("驗證簽名:[{}]", verifyFromHex);
}
}
測試結果:
2、DSA 簽名
DSA-Digital Signature Algorithm 是Schnorr和ElGamal簽名算法的變種,被美國NIST作為DSS(DigitalSignature Standard)。簡單的說,這是一種更高級的驗證方式,用作數字簽名。不單單只有公鑰、私鑰,還有數字簽名。私鑰加密生成數字簽名,公鑰驗證數據及簽名。如果數據和簽名不匹配則認為驗證失敗!數字簽名的作用就是校驗數據在傳輸過程中不被修改。數字簽名,是單向加密的升級
具體使用看:https://blog.csdn.net/jianggujin/article/details/53440060