公私鑰加密及數字簽名(RSA)
阿新 • • 發佈:2018-12-11
公私鑰加密及數字簽名
1、公鑰與私鑰原理
1.1、私鑰演算法
私鑰加密演算法,又稱 對稱加密演算法,因為這種演算法解密金鑰和加密金鑰是相同的。也正因為同一金鑰既用於加密又用於解密,所以這個金鑰是不能公開的。常見的有《DES加密演算法》、《AES加密演算法》。
1.2、私鑰演算法
公鑰加密演算法,也就是 非對稱加密演算法,這種演算法加密和解密的密碼不一樣,一個是公鑰,另一個是私鑰:
-
公鑰和私鑰成對出現
-
公開的金鑰叫公鑰,只有自己知道的叫私鑰
-
用公鑰加密的資料只有對應的私鑰可以解密
-
用私鑰加密的資料只有對應的公鑰可以解密
-
如果可以用公鑰解密,則必然是對應的私鑰加的密
-
如果可以用私鑰解密,則必然是對應的公鑰加的密
-
公鑰和私鑰是相對的,兩者本身並沒有規定哪一個必須是公鑰或私鑰。
2、公鑰加密私鑰解密
公鑰加密私鑰解密應用場景:對資料加密安全性要求高。 Java實現的demo:
package com.tzt.common.util;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security. KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
/**
* @description RSA加密與解密公共工具類
* @author libb
* @since 2018年09月25日
*/
public class EncrypUtil {
/** 加密型別是RSA */
private static final String ALGORITHM_RSA = "RSA";
/** RSA最大加密明文大小 */
private static final int MAX_ENCRYPT_SIZE = 245;
/** RSA最大解密密文大小 */
private static final int MAX_DECRYPT_SIZE = 256;
/**
* @description RSA分段解密,每段256位長度
* @param decryptedMessage 需要解密的引數
* @param privateKey 解密私鑰
* @return String 解密後返回的引數
*/
public static String decrypt(String decryptedMessage, String privateKey) {
byte[] messageBytes = EncodingUtil.decodeBase64(decryptedMessage);
return new String(decrypt(messageBytes, privateKey), EncodingUtil.DEFAULT_CHARSET_CLASS);
}
/**
* @description RSA分段解密,每段256位長度
* @param decryptedMessage 需要解密的引數
* @param privateKey 解密私鑰
* @return byte[] 解密後返回的引數 @ 如果想返回String,直接對返回值進行new String(返回值)即可
*/
public static byte[] decrypt(byte[] decryptedMessage, String privateKey) {
byte[] keyBytes = EncodingUtil.decodeBase64(privateKey);
try {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
PrivateKey priK = keyFactory.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(ALGORITHM_RSA);
cipher.init(Cipher.DECRYPT_MODE, priK);
int decryptedMessageLen = decryptedMessage.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 對資料分段解密
while (decryptedMessageLen - offSet > 0) {
if (decryptedMessageLen - offSet > MAX_DECRYPT_SIZE) {
cache = cipher.doFinal(decryptedMessage, offSet, MAX_DECRYPT_SIZE);
} else {
cache = cipher.doFinal(decryptedMessage, offSet, decryptedMessageLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_SIZE;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
} catch (IOException e) {
throw new IllegalArgumentException(e);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException("failed to decrypt message, please check private key and message", e);
} catch (InvalidKeyException | InvalidKeySpecException e) {
throw new IllegalArgumentException(e);
}
}
/**
* @description RSA分段加密,每段245位長度
* @param encryptedMessage 需要加密的引數
* @param publicKey 加密公鑰
* @return String 加密後返回的引數
*/
public static String encrypt(String encryptedMessage, String publicKey) {
byte[] messageBytes = encryptedMessage.getBytes(EncodingUtil.DEFAULT_CHARSET_CLASS);
return EncodingUtil.base64(encrypt(messageBytes, publicKey));
}
/**
* @description RSA分段加密,每段245位長度
* @param encryptedMessage 需要加密的引數
* @param publicKey 加密公鑰
* @return byte[] 加密後返回的引數 @ 如果想返回String,直接對返回值進行new String(返回值)即可
*/
public static byte[] encrypt(byte[] encryptedMessage, String publicKey) {
byte[] keyBytes = EncodingUtil.decodeBase64(publicKey);
try {
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_RSA);
PublicKey pubK = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, pubK);
int encryptedMessageLen = encryptedMessage.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 對資料分段加密
while (encryptedMessageLen - offSet > 0) {
if (encryptedMessageLen - offSet > MAX_ENCRYPT_SIZE) {
cache = cipher.doFinal(encryptedMessage, offSet, MAX_ENCRYPT_SIZE);
} else {
cache = cipher.doFinal(encryptedMessage, offSet, encryptedMessageLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_SIZE;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
} catch (IOException e) {
throw new IllegalArgumentException(e);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidKeySpecException e) {
throw new IllegalArgumentException(e);
}
}
/**
* @description 獲取公鑰檔案內容
* @return String 公鑰檔案內容
*/
public static String getPublic(){
return read(Thread.currentThread().getContextClassLoader().getResource("cert/public.pem").getPath());
}
/**
* @description 獲取私鑰檔案內容
* @return String 私鑰檔案內容
*/
public static String getPrivate(){
return read(Thread.currentThread().getContextClassLoader().getResource("cert/private.pem").getPath());
}
/**
* @description 讀取檔案內容
* @param path 檔案路徑
* @return String 檔案內容
*/
public static String read(String path){
BufferedReader reader;
String data="";
try{
reader = new BufferedReader(new FileReader(path));
String result;
while ((result=reader.readLine())!=null) {
if(!result.startsWith("---")){
data=data+result.trim();
}
}
}catch(Exception e){
e.printStackTrace();
System.out.println(e.toString());
}
return data;
}
/**
* 測試RSA加密解密
* @param args
*/
public static void main(String[] args) {
String str = "12345";
String publickey = getPublic();
String priKey = getPrivate();
String encodeStr = EncrypUtil.encrypt(str, publickey);
System.out.println("明文:"+str);
System.out.println("加密:"+encodeStr);
String data="e2N8qpwVjwt2+HAzEss1jZ9jhBSu9JW0C+b3ifLa7WBjmAbIyJvJTNOj59JbhP/NA6MOwrTh/K3ushMg3PDe2ytbOgN8lX1HxU5wl5JoP+tqCG7YS7iCcpsZQeLvoNUS18lbEEyDn0D2bt+oKhq2mIAT7Lzw4nlnGv+GjIU7qXw=";
System.out.println("解密:"+EncrypUtil.decrypt(encodeStr,priKey));
}
}
3、私鑰加密公鑰解密
私鑰加密公鑰解密應用場景:通訊需要確保是我需要的服務方給的(數字證書)。 在實現之前,需要先準備對應公私鑰,生成公私鑰的方式及步驟: 用OpenSSL生成一對祕鑰:
#生成RSA私鑰,預設是編碼方式為PEM的PKCS#1格式
#PKCS#1格式是傳統的私鑰格式
openssl genrsa -out key.pem 1024
#從私鑰中生成公鑰,給OpenSSL驗籤用的
openssl rsa -in key.pem -out pub.pem -pubout
#把PEM編碼格式的私鑰轉換成DER編碼的私鑰,同時進行PKCS#1轉換成PKCS#8(Java預設只能處理PKCS#8的格式)
#-nocrypt 意思是不加密
#給Java用
openssl pkcs8 -topk8 -in key.pem -out pkcs8_prikey.der -inform PEM -outform DER -nocrypt
#從私鑰中匯出DER編碼的公鑰
#給Java用
openssl rsa -in key.pem -pubout -outform DER -out pubkey.der
Java實現的demo:
package com.tzt.common.util;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* Created by libb on 2018/9/25.
*/
public class SignatureUtil {
public static KeyPair getKeyPair() throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] publicKeyData = Files.readAllBytes(Paths.get("D:\\workspaces\\eclipse_other\\tzt-inner-common\\src\\main\\resources\\signature\\pubkey.der"));
byte[] privateKeyData = Files.readAllBytes(Paths.get("D:\\workspaces\\eclipse_other\\tzt-inner-common\\src\\main\\resources\\signature\\pkcs8_prikey.der"));
X509EncodedKeySpec publicKeySpec= new X509EncodedKeySpec(publicKeyData);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyData);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
return new KeyPair(publicKey, privateKey);
}
/**
* @description 私鑰生成簽名
* Signature.getInstance(algorithm) 演算法格式為 <digest>with<encryption>
* 支援的演算法有:MD5withRSA、SHA256withRSA、SHA256withDSA等等
* 全部支援的演算法見官方文件:
* https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#Signature
* @param signatureAlgorithm 簽名演算法
* @param privateKey 私鑰
* @param data 簽名源資料(位元組陣列)
* @return byte[] 已簽名資料
* @throws Exception 異常處理
*/
public static byte[] sign(String signatureAlgorithm, PrivateKey privateKey, byte[] data) throws Exception {
Signature sign = Signature.getInstance(signatureAlgorithm);
sign.initSign(privateKey);
sign.update(data);
byte[] result = sign.sign();
return result;
}
/**
* @description 公鑰驗籤
* @param signatureAlgorithm 簽名演算法
* @param publicKey 公鑰
* @param data 簽名源資料(位元組陣列)
* @param signature 已簽名資料
* @return boolean 驗籤是否成功的結果
* @throws Exception
*/
public static boolean verify(String signatureAlgorithm, PublicKey publicKey, byte[] data, byte[] signature) throws Exception {
Signature sign = Signature.getInstance(signatureAlgorithm);
sign.initVerify(publicKey);
sign.update(data);
return sign.verify(signature);
}
/**
* 測試
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
KeyPair keyPair = getKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
String signatureAlgorithm = "SHA256withRSA";
//需要簽名的資料
byte[] data = Files.readAllBytes(Paths.get("D:\\dapp_download\\資料庫\\MySQL\\SQLyog_Enterprise.zip"));
//資料+私鑰簽名
byte[] signatureData = sign(signatureAlgorithm, privateKey, data);
//資料+公鑰+簽名結果進行驗證
boolean result = verify(signatureAlgorithm, publicKey, data, signatureData);
System.out.println(result);
}
}