Java RSA 加密

什麼是Rsa加密?

RSA算法是最流行的公鑰密碼算法,使用長度可以變化的密鑰。RSA是第一個既能用於數據加密也能用於數字簽名的算法。

RSA算法原理如下:

1.隨機選擇兩個大質數p和q,p不等於q,計算N=pq; 
2.選擇一個大於1小於N的自然數e,e必須與(p-1)(q-1)互素。 
3.用公式計算出d:d×e = 1 (mod (p-1)(q-1)) 。
4.銷燬p和q。

最終得到的N和e就是“公鑰”,d就是“私鑰”,發送方使用N去加密數據,接收方只有使用d才能解開數據內容。

RSA的安全性依賴於大數分解,小於1024位的N已經被證明是不安全的,而且由於RSA算法進行的都是大數計算,使得RSA最快的情況也比DES慢上倍,這是RSA最大的缺陷,因此通常只能用於加密少量數據或者加密密鑰,但RSA仍然不失爲一種高強度的算法。

 

該如何使用呢?

package com.wallet.util;

 

import java.math.BigInteger;

import java.security.KeyFactory;

import java.security.KeyPair;

import java.security.KeyPairGenerator;

import java.security.NoSuchAlgorithmException;

import java.security.SecureRandom;

import java.security.Signature;

import java.security.interfaces.RSAPrivateKey;

import java.security.interfaces.RSAPublicKey;

import java.security.spec.InvalidKeySpecException;

import java.security.spec.PKCS8EncodedKeySpec;

import java.security.spec.RSAPrivateKeySpec;

import java.security.spec.RSAPublicKeySpec;

import java.security.spec.X509EncodedKeySpec;

import java.util.Base64;

import java.util.HashMap;

import java.util.Map;

 

import javax.crypto.Cipher;

 

import org.apache.commons.codec.DecoderException;

import org.apache.commons.codec.binary.Hex;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

 

/**

 * RSA 加解密

 * 

 * 參數在經過Http傳輸後會對部分特殊字符進行轉義, 所以在接收參數後需進行解碼    String value = URLDecoder.decode("param", "UTF-8");

 * 參數在進行Http傳輸前需對參數進行編碼轉義操作, String value = URLEncoder.encode("param", "UTF-8");

 * Java 1.8+ 原生支持 Base64, 1.7及以下需導包    安卓原生支持 Base64    

 */

public class RSA {

 

// 創建日誌記錄對象

private static final Logger LOGGER = LoggerFactory.getLogger(RSA.class);

 

// 聲明非對稱加密密鑰算法

public static final String RSA = "RSA";

// 聲明編碼集

public static final String CODE = "UTF-8";

// 祕鑰長度

public static final Integer KEY_SIZE = 2048;

// 聲明簽名標準

public static final String SHA1WITHRSA = "SHA1WithRSA";

// 加密填充方式, 該屬性安卓有效, Java使用RSA加密後, 安卓使用該屬性解密, 因安卓和 Java RSA 協議不同

public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";

 

// 聲明祕鑰工廠對象

private static KeyFactory keyFactory = null;

// 聲明公鑰對象

private static RSAPublicKey publicKey = null;

// 聲明私鑰對象

private static RSAPrivateKey privateKey = null;

 

/**

* 靜態代碼塊, 初始化祕鑰工廠對象

*/

static {

try {

// 創建祕鑰工廠

keyFactory = KeyFactory.getInstance(RSA);

} catch (NoSuchAlgorithmException e) {

e.printStackTrace();

}

}

 

/**

* 使用公鑰字符獲取公鑰對象

* @param publicKeyString 公鑰字符串

* @return 公鑰對象

*/

public static RSAPublicKey getPublicKey(String publicKeyString) {

// 把公鑰字符串數據加載到祕鑰對象中

X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyString));

try {

// 獲取公鑰對象

publicKey = (RSAPublicKey) keyFactory.generatePublic(x509EncodedKeySpec);

} catch (InvalidKeySpecException e) {

e.printStackTrace();

}

// 將結果返回

return publicKey;

}

 

/**

* 使用私鑰字符獲取私鑰對象

* @param publicKeyString 私鑰字符串

* @return 私鑰對象

*/

public static RSAPrivateKey getPrivateKey(String privateKeyString) {

// 把私鑰字符串數據加載到祕鑰對象中

PKCS8EncodedKeySpec pKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyString));

try {

// 獲取私鑰對象

privateKey = (RSAPrivateKey) keyFactory.generatePrivate(pKCS8EncodedKeySpec);

} catch (InvalidKeySpecException e) {

e.printStackTrace();

}

// 將結果返回

return privateKey;

}

 

/**

* 隨機生成密鑰對

* */

public static Map<String, String> genKeyPair() {

// 創建返回值類型

Map<String, String> map = new HashMap<String, String>();

try {

// KeyPairGenerator類用於生成公鑰和私鑰對,基於RSA算法生成對象

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA);

// 初始化密鑰對生成器,設置祕鑰長度

keyPairGen.initialize(KEY_SIZE, new SecureRandom());

// 生成一個密鑰對,保存在keyPair中

KeyPair keyPair = keyPairGen.generateKeyPair();

 

// 得到公鑰

publicKey = (RSAPublicKey) keyPair.getPublic();

// 將公鑰轉換爲 String 類型

String pubKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());

 

// 得到私鑰

privateKey = (RSAPrivateKey) keyPair.getPrivate();

// 將私鑰轉換爲 String 類型

String priKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());

 

// 添加公鑰信息

map.put("publicKey", pubKey);

// 添加私鑰信息

map.put("privateKey", priKey);

} catch (Exception e) {

e.printStackTrace();

}

// 將結果返回

return map;

}

 

/**

* 公鑰加密 適用於明文過長

* @param publicKeyString 公鑰

* @param plainTextData 明文數據

* @return 密文

* @throws Exception 加密過程中的異常信息

* */

public static String publicEncrypts(String publicKeyString, String plainTextData) throws Exception {

// 聲明變量接收密文

String pubKey = "";

// 聲明起始下邊

Integer startIndex = 0;

// 獲取明文長度

Integer strLength = plainTextData.length();

// 獲取單次可加密長度

Integer size = publicKeyString.length() / 8 - 11;

// 遍歷參數 條件爲起始下邊小於明文長度

while(startIndex < plainTextData.length()) {

// 獲取當前循環結束下標

Integer endIndex = (startIndex + size) > strLength ? strLength : startIndex + size;

// 執行加密操作, 累加密文

pubKey += publicEncrypt(publicKeyString, plainTextData.substring(startIndex, endIndex));

// 起始位置增長

startIndex += size;

}

// 將結果返回

return pubKey;

}

 

/**

* 公鑰加密過程, 明文長度小於 (公鑰長度 / 8) - 11

* @param publicKeyString 公鑰

* @param plainTextData 明文數據

* @return 密文

* @throws Exception 加密過程中的異常信息

* */

public static String publicEncrypt(String publicKeyString, String plainTextData) throws Exception {

try {

// 創建加解密對象, 安卓需使用 ECB_PKCS1_PADDING 不能使用 RSA

Cipher cipher = Cipher.getInstance(RSA);

// 使用公鑰字符串獲取公鑰對象

publicKey = getPublicKey(publicKeyString);

// 初始化數據

cipher.init(Cipher.ENCRYPT_MODE, publicKey);

// 執行加密操作獲取結果

byte[] output = cipher.doFinal(plainTextData.getBytes(CODE));

// 將加密後的結果轉換爲字符串 並返回

return Base64.getEncoder().encodeToString(output);

} catch (Exception e) {

e.printStackTrace();

throw e;

}

}

 

/**

* 私鑰加密 適用於明文過長

* @param privateKeyString 私鑰

* @param plainTextData 明文數據

* @return 密文

* @throws Exception 加密過程中的異常信息

* */

public static String privateEncrypts(String privateKeyString, String plainTextData) throws Exception {

// 聲明變量接收密文

String priKey = "";

// 聲明起始下邊

Integer startIndex = 0;

// 獲取明文長度

Integer strLength = plainTextData.length();

// 獲取單次可加密長度

Integer size = privateKeyString.length() / 8 - 11;

// 遍歷參數 條件爲起始下邊小於明文長度

while(startIndex < plainTextData.length()) {

// 獲取當前結束下標

Integer endIndex = (startIndex + size) > strLength ? strLength : startIndex + size;

// 執行加密操作, 累加密文

priKey += privateEncrypt(privateKeyString, plainTextData.substring(startIndex, endIndex));

// 起始位置增長

startIndex += size;

}

// 將結果返回

return priKey;

}

 

/**

* 私鑰加密過程, 明文長度小於 (私鑰長度 / 8) - 11

* @param privateKeyString 私鑰

* @param plainTextData 明文數據

* @return 密文

* @throws Exception 加密過程中的異常信息

* */

public static String privateEncrypt(String privateKeyString, String plainTextData) throws Exception {

try {

// 創建加解密對象, 安卓需使用 ECB_PKCS1_PADDING 不能使用 RSA

Cipher cipher = Cipher.getInstance(RSA);

// 使用私鑰字符串獲取私鑰對象

privateKey = getPrivateKey(privateKeyString);

// 初始化數據

cipher.init(Cipher.ENCRYPT_MODE, privateKey);

// 執行加密操作

byte[] output = cipher.doFinal(plainTextData.getBytes(CODE));

// 將加密後的結果轉換爲字符串 並返回

return Base64.getEncoder().encodeToString(output);

} catch (Exception e) {

e.printStackTrace();

throw e;

}

}

 

/**

* 私鑰解密 適用於密文過長

* @param privateKeyString 私鑰

* @param plainTextData 密文數據

* @return 明文

* @throws Exception 解密過程中的異常信息

* */

public static String privateDecrypts(String privateKeyString, String plainTextData) throws Exception {

// 聲明變量接收明文

String plaintext = "";

// 聲明起始下邊

Integer startIndex = 0;

// 獲取密文長度

Integer strLength = plainTextData.length();

// 遍歷參數 條件爲起始下邊小於密文長度

while(startIndex < plainTextData.length()) {

// 獲取當前結束下標

Integer endIndex = (startIndex + 344) > strLength ? strLength : startIndex + 344;

// 執行解密操作, 累加明文

plaintext += privateDecrypt(privateKeyString, plainTextData.substring(startIndex, endIndex));

// 起始位置增長

startIndex += 344;

}

// 將結果返回

return plaintext;

}

 

/**

* 私鑰解密過程

* @param privateKey 私鑰

* @param cipherData 密文數據

* @return 明文

* @throws Exception 解密過程中的異常信息

* */

public static String privateDecrypt(String privateKeyString, String sign) throws Exception {

try {

// 創建加解密對象, 安卓需使用 ECB_PKCS1_PADDING 不能使用 RSA

Cipher cipher = Cipher.getInstance(RSA);

// 使用私鑰字符串獲取私鑰對象

privateKey = getPrivateKey(privateKeyString);

// 初始化數據

cipher.init(Cipher.DECRYPT_MODE, privateKey);

// 執行解密操作

byte[] output = cipher.doFinal(Base64.getDecoder().decode(sign));

// 轉換解密後結果編碼集 並返回

return new String(output, CODE);

} catch (Exception e) {

e.printStackTrace();

throw e;

}

}

 

/**

* 公鑰解密 適用於密文過長

* @param publicKeyString 公鑰

* @param plainTextData 密文數據

* @return 明文

* @throws Exception 解密過程中的異常信息

* */

public static String publicDecrypts(String publicKeyString, String sign) throws Exception {

// 聲明變量接收明文

String plaintext = "";

// 聲明起始下邊

Integer startIndex = 0;

// 獲取密文長度

Integer strLength = sign.length();

// 遍歷參數 條件爲起始下邊小於密文長度

while(startIndex < sign.length()) {

// 獲取當前結束下標

Integer endIndex = (startIndex + 344) > strLength ? strLength : startIndex + 344;

// 執行解密操作, 累加明文

plaintext += publicDecrypt(publicKeyString, sign.substring(startIndex, endIndex));

// 起始位置增長

startIndex += 344;

}

// 將結果返回

return plaintext;

}

 

/**

* 公鑰解密過程

* @param publicKeyString 公鑰字符串

* @param cipherData 密文數據

* @return 明文

* @throws Exception 解密過程中的異常信息 

* */

public static String publicDecrypt(String publicKeyString, String sign) throws Exception {

try {

// 創建加解密對象, 安卓需使用 ECB_PKCS1_PADDING 不能使用 RSA

Cipher cipher = Cipher.getInstance(RSA);

// 使用公鑰字符串獲取公鑰對象

publicKey = getPublicKey(publicKeyString);

// 初始化數據

cipher.init(Cipher.DECRYPT_MODE, publicKey);

// 執行解密操作

byte[] output = cipher.doFinal(Base64.getDecoder().decode(sign));

// 轉換解密後結果編碼集 並返回

return new String(output, CODE);

} catch (Exception e) {

e.printStackTrace();

throw e;

}

}

 

/**

* RSA簽名

* @param content 待簽名數據

* @return 簽名值

* @throws Exception 簽名過程中出現的異常

* */

public static String sign(String privateKeyString, String content) throws Exception {

try {

// 創建簽名對象

Signature signature = Signature.getInstance(SHA1WITHRSA);

// 使用私鑰字符串獲取私鑰對象

privateKey = getPrivateKey(privateKeyString);

// 初始化此對象的簽名

signature.initSign(privateKey);

// 更新要簽名或驗證的數據

signature.update(content.getBytes(CODE));

return Base64.getEncoder().encodeToString(signature.sign());

} catch (Exception e) {

e.printStackTrace();

throw e;

}

}

 

/**

* RSA驗簽名檢查

* @param content 待簽名數據

* @param sign 簽名值

* @return 布爾值

* @throws Exception 驗簽過程中出現的異常

* */

public static boolean verify(String publicKeyString, String content, String sign) throws Exception {

try {

// 創建簽名對象

Signature signature = Signature.getInstance(SHA1WITHRSA);

// 使用公鑰字符串獲取公鑰對象

publicKey = getPublicKey(publicKeyString);

// 初始化此對象的簽名

signature.initVerify(publicKey);

// 更新要簽名或驗證的數據

signature.update(content.getBytes( ));

// 校驗簽名結果

return signature.verify(Base64.getDecoder().decode(sign));

} catch (Exception e) {

e.printStackTrace();

throw e;

}

}

 

    /**

     * 根據給定的係數和專用指數構造一個RSA專用的公鑰對象。

     *

     * @param modulus 係數。

     * @param publicExponent 專用指數。

     * @return RSA專用公鑰對象。

     */

    public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) {

        // 構造 RSAPublicKeySpec(RSA公鑰規範) 對象

    RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(new BigInteger(modulus),

                new BigInteger(publicExponent));

        try {

        // 獲取公鑰對象並返回

            return (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);

        } catch (InvalidKeySpecException ex) {

            LOGGER.error("RSAPublicKeySpec is unavailable.", ex);

        } catch (NullPointerException ex) {

            LOGGER.error("RSAUtils#KEY_FACTORY is null, can not generate KeyFactory instance.", ex);

        }

        // 構造失敗

        return null;

    }

 

    /**

     * 根據給定的係數和專用指數構造一個RSA專用的私鑰對象。

     *

     * @param modulus 係數。

     * @param privateExponent 專用指數。

     * @return RSA專用私鑰對象。

     */

    public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) {

    // 構造 RSAPrivateKeySpec(RSA私鑰規範) 對象

        RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus),

                new BigInteger(privateExponent));

        try {

        // 獲取私鑰對象並返回

            return (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec);

        } catch (InvalidKeySpecException ex) {

            LOGGER.error("RSAPrivateKeySpec is unavailable.", ex);

        } catch (NullPointerException ex) {

            LOGGER.error("RSAUtils#KEY_FACTORY is null, can not generate KeyFactory instance.", ex);

        }

        // 構造失敗

        return null;

    }

     

    /**

     * 根據給定的16進制係數和專用指數字符串構造一個RSA專用的私鑰對象。

     *

     * @param modulus 係數。

     * @param privateExponent 專用指數。

     * @return RSA專用私鑰對象。

     */

    public static RSAPrivateKey getRSAPrivateKey(String hexModulus, String hexPrivateExponent) {

        // 非空校驗

    if(hexModulus.isEmpty() || hexPrivateExponent.isEmpty()) {

            // 判斷是否開啓 debug 級別日誌記錄

    if(LOGGER.isDebugEnabled()) {

    // 打印日誌

                LOGGER.debug("hexModulus and hexPrivateExponent cannot be empty. RSAPrivateKey value is null to return.");

            }

    // 終止方法

            return null;

        }

    // 聲明變量

        byte[] modulus = null;

        byte[] privateExponent = null;

        try {

        // 解碼, 調用 org.apache.commons.codec.binary.Hex.decodeHex()方法進行解碼

        // 編碼, 調用 org.apache.commons.codec.binary.StringUtils.newStringUtf16()等方法進行編碼

            modulus = Hex.decodeHex(hexModulus.toCharArray());

            privateExponent = Hex.decodeHex(hexPrivateExponent.toCharArray());

        } catch(DecoderException ex) {

            // 編碼轉換錯誤

        LOGGER.error("hexModulus or hexPrivateExponent value is invalid. return null(RSAPrivateKey).");

        }

        // 非空校驗

        if(modulus != null && privateExponent != null) {

            // 調用 根據給定的係數和專用指數構造一個RSA專用的私鑰對象 方法獲取私鑰對象

        return generateRSAPrivateKey(modulus, privateExponent);

        }

        // 構造失敗

        return null;

    }

     

    /**

     * 根據給定的16進制係數和專用指數字符串構造一個RSA專用的公鑰對象。

     *

     * @param modulus 係數。

     * @param publicExponent 專用指數。

     * @return RSA專用公鑰對象。

     */

    public static RSAPublicKey getRSAPublidKey(String hexModulus, String hexPublicExponent) {

        // 非空校驗

    if(hexModulus.isEmpty() || hexPublicExponent.isEmpty()) {

    // 判斷是否開啓 debug 級別日誌記錄

    if(LOGGER.isDebugEnabled()) {

    // 打印日誌

                LOGGER.debug("hexModulus and hexPublicExponent cannot be empty. return null(RSAPublicKey).");

            }

    // 終止方法

            return null;

        }

    // 聲明變量

        byte[] modulus = null;

        byte[] publicExponent = null;

        try {

        // 解碼, 調用 org.apache.commons.codec.binary.Hex.decodeHex()方法進行解碼

        // 編碼, 調用 org.apache.commons.codec.binary.StringUtils.newStringUtf16()等方法進行編碼

            modulus = Hex.decodeHex(hexModulus.toCharArray());

            publicExponent = Hex.decodeHex(hexPublicExponent.toCharArray());

        } catch(DecoderException ex) {

        // 編碼轉換錯誤

            LOGGER.error("hexModulus or hexPublicExponent value is invalid. return null(RSAPublicKey).");

        }

        if(modulus != null && publicExponent != null) {

            // 調用 根據給定的係數和專用指數構造一個RSA專用的公鑰對象 方法獲取私鑰對象

        return generateRSAPublicKey(modulus, publicExponent);

        }

        // 構造失敗

        return null;

    }

    

}

關於加密填充方式:之前以爲上面這些操作就能實現rsa加解密,以爲萬事大吉了,呵呵,這事還沒完,悲劇還是發生了,Android這邊加密過的數據,服務器端死活解密不了,原來android系統的RSA實現是"RSA/None/NoPadding",而標準JDK實現是"RSA/None/PKCS1Padding" ,這造成了在android機上加密後無法在服務器上解密的原因,所以在實現的時候這個一定要注意。

實現分段加密:搞定了填充方式之後又自信的認爲萬事大吉了,可是意外還是發生了,RSA非對稱加密內容長度有限制,1024位key的最多隻能加密127位數據,否則就會報錯(javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes) , RSA 是常用的非對稱加密算法。最近使用時卻出現了“不正確的長度”的異常,研究發現是由於待加密的數據超長所致。RSA 算法規定:待加密的字節數不能超過密鑰的長度值除以 8 再減去 11(即:KeySize / 8 - 11),而加密後得到密文的字節數,正好是密鑰的長度值除以 8(即:KeySize / 8)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章