天天看點

AES/GCM/NoPadding分組加密不填充的對稱加密算法 + RSA非對稱加密。資料加密、傳輸加密簡介AesGcmUtilRsaUtil

文章目錄

  • 簡介
  • AesGcmUtil
  • RsaUtil

簡介

RSA其實是非對稱加密的一種,它可以生成一個公鑰和私鑰,使用RSA+公鑰加密的資料,可以使用RSA+私鑰進行解密。這就是非對稱加密的一種。我們可以擷取請求方的公鑰,再用RSA+對方公鑰進行資料加密,再把資料傳回給請求方,請求方拿到資料後,用自己手上的私鑰進行解密,而且隻有公鑰對應的私鑰才能解開加密。這是非常安全的一種資料方式。在JAVA裡,我們使用RSA實作。

雖然RSA的非對稱加密比對稱加密的方式要更安全,但是非對稱加密的開銷更大,特别是資料量大的時候對性能的影響比較大。在java中,我們不會直接就使用RSA對資料進行加密;而是配合對稱加密算法一起使用,對稱加密我打算使用AES加密算法,至于為什麼選擇使用AES,請看下方。

AES優先使用 AES/GCM/NoPadding模式,以下是來自SonarLint的提示:

AES/GCM/NoPadding分組加密不填充的對稱加密算法 + RSA非對稱加密。資料加密、傳輸加密簡介AesGcmUtilRsaUtil

具體翻譯是:

要進行安全加密,操作模式和填充方案是必須的,并應根據加密算法正确使用:

對于分組密碼加密算法(如AES),建議使用GCM (Galois計數器模式)模式,該模式在内部使用零/無填充模式。相反,這些模式和/或方案是非常不受歡迎的:

電子密碼本(ECB)模式很容易受到攻擊,因為它沒有提供嚴格的消息機密性:在給定密鑰下,任何給定的明文塊總是被加密到相同的密文塊。

帶有PKCS#5填充(或PKCS#7)的密碼塊連結(CBC)容易受到填充oracle攻擊。

RSA加密算法應與推薦的填充方案(OAEP)一起使用

是以在AES中選中使用 GCM模式/并且無填充,來實作資料加密。

那問題來了,我們該使用RSA+AES對資料實作加密呢?下面我來簡單的介紹一下該怎麼使用RSA+AES實作資料傳輸加密:

  1. A 使用者使用RSA生成公鑰和私鑰
  2. B 使用者使用RSA生成公鑰和私鑰
  3. A使用者 和 B使用者 交換對方的公鑰
  4. A 使用者生成一個Key,使用AES+key對資料進行加密
  5. A 使用者使用RSA+B公鑰對key進行加密,得到一個加密key(enKey)
  6. A 使用者把 加密資料 + enKey傳送給 B使用者
  7. B 使用者接收資料
  8. B 使用者使用RSA+自己的私鑰解密enKey,得到key
  9. B 使用者使用AES+key對加密資料進行解密,得到明文資料

以上就是資料傳輸加密的實作思路。RSA的性能開銷要比AES大的多,是以都是使用RSA來加密AES的加密key,資料加密實質上還是使用AES去完成。更多相關的内容請查閱更專業的書籍及資料。以下就是我封裝的AES和RSA的加密類,可以直接拿來用,如果有什麼不足,請大佬留言告知,我進行改進。

AesGcmUtil

import lombok.extern.slf4j.Slf4j;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * AES對稱加密
 * GCM模式
 * NoPadding不填充
 *
 * @author a_fig [email protected]
 * @version V0.1
 * @className EncryptAES
 * @date 2020/5/12
 */
@Slf4j
public class AesGcmUtil {
    /**
     * AES 加密算法
     *
     * @author a_fig [email protected]
     * @date 2020/5/13 11:27
     */
    private static final String AES = "AES";

    /**
     * 指定加解密需要使用的 算法/模式/填充
     * 以下指: 使用AES算法 / GCM分組加密模式 / 不填充
     *
     * @author a_fig [email protected]
     * @date 2020/5/13 11:31
     */
    private static final String AES_CIPHER_GCM_NO_PADDING = "AES/GCM/NoPadding";

    private AesGcmUtil() {
    }

    /**
     * 初始化AES的GCM模式 加密/解密 操作器
     *
     * @param content 内容
     * @param initKey 初始key
     * @param modes 加密/解密 操作類型
     * @return java.lang.String
     * @author a_fig [email protected]
     * @date 2020/5/13 11:24
     */
    private static byte[] init(byte[] content, byte[] initKey, int modes) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // key生成器
        KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
        // 加密因子
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        secureRandom.setSeed(initKey);
        keyGenerator.init(secureRandom);
        SecretKey secretKey = keyGenerator.generateKey();
        // 初始化GCM
        GCMParameterSpec encryptSpec = new GCMParameterSpec(128, initKey);
        Cipher cipher = Cipher.getInstance(AES_CIPHER_GCM_NO_PADDING);
        cipher.init(modes, secretKey, encryptSpec);
        return cipher.doFinal(content);
    }

    /**
     * 使用AES的GCM模式進行資料加密
     *
     * @param content 原資料
     * @param initKey 初始key
     * @return java.lang.String
     * @author a_fig [email protected]
     * @date 2020/5/13 11:24
     */
    public static String encrypt(String content, String initKey) {
        String cipher = null;
        try {
            byte[] cipherBytes = init(content.getBytes(StandardCharsets.UTF_8), initKey.getBytes(StandardCharsets.UTF_8), Cipher.ENCRYPT_MODE);
            cipher = Base64.getEncoder().encodeToString(cipherBytes);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return cipher;
    }

    /**
     * 使用AES的GCM模式進行資料解密
     *
     * @param cipherStr 密文
     * @param initKey 初始key
     * @return java.lang.String
     * @author a_fig [email protected]
     * @date 2020/5/13 11:24
     */
    public static String decrypt(String cipherStr, String initKey) {
        String data = null;
        try {
            byte[] cipherBytes = Base64.getDecoder().decode(cipherStr);
            byte[] dataBytes = init(cipherBytes, initKey.getBytes(StandardCharsets.UTF_8), Cipher.DECRYPT_MODE);
            data = new String(dataBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return data;
    }
}

           

RsaUtil

import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RSA非對稱加密
 * 使用publicKey進行加密
 * 使用privateKey進行解密
 *
 * @author a_fig [email protected]
 * @version V0.1
 * @className EncryptAesUtil
 * @date 2020/5/12
 */
@Slf4j
public class RsaUtil {

    private final KeyPair keyPair;

    /**
     * 指定加密算法RSA非對稱加密
     *
     * @author a_fig [email protected]
     * @date 2020/5/13 12:20
     */
    private static final String RSA = "RSA";

    /**
     * 指定RSA非對稱算法/ECB模式/填充方式
     *
     * @author a_fig [email protected]
     * @date 2020/5/13 12:20
     */
    private static final String RSA_CIPHER = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";

    /**
     * 初始化密碼器
     *
     * @author a_fig [email protected]
     * @date 2020/5/13 12:14
     */
    public AesRsaUtil() {
        KeyPairGenerator keyPairGenerator = null;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        keyPair = keyPairGenerator == null ? null : keyPairGenerator.generateKeyPair();
    }

    /**
     * 擷取私鑰(String形式)
     *
     * @return java.lang.String
     * @author a_fig [email protected]
     * @date 2020/5/13 12:15
     */
    public String getPrivateKeyStr() {
        return Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
    }

    /**
     * 擷取公鑰(String形式)
     *
     * @return java.lang.String
     * @author a_fig [email protected]
     * @date 2020/5/13 12:15
     */
    public String getPublicKeyStr() {
        return Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
    }

    /**
     * 把String的公鑰轉換為PublicKey
     *
     * @param key 字元串類型的公鑰
     * @return java.security.PublicKey
     * @author a_fig [email protected]
     * @date 2020/5/13 12:18
     */
    private PublicKey toPublicKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory kf = KeyFactory.getInstance(RSA);
        return kf.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(key)));
    }

    /**
     * 把String的私鑰轉換為PrivateKey
     *
     * @param key 字元串類型的私鑰
     * @return java.security.PrivateKey
     * @author a_fig [email protected]
     * @date 2020/5/13 12:18
     */
    private PrivateKey toPrivateKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyFactory kf = KeyFactory.getInstance(RSA);
        return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key)));
    }

    /**
     * 使用公鑰對資料進行加密
     *
     * @param content 原資料
     * @param publicKey 公鑰(字元串)
     * @return java.lang.String
     * @author a_fig [email protected]
     * @date 2020/5/13 12:20
     */
    public String encrypt(String content, String publicKey) {
        String result = null;
        try {
            byte[] dataByte = content.getBytes(StandardCharsets.UTF_8);
            Cipher cipher = Cipher.getInstance(RSA_CIPHER);
            cipher.init(Cipher.ENCRYPT_MODE, toPublicKey(publicKey));
            result = Base64.getEncoder().encodeToString(cipher.doFinal(dataByte));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 使用私鑰對密文進行解密
     *
     * @param cipherStr 密文
     * @param privateKey 私鑰(字元串)
     * @return java.lang.String
     * @author a_fig [email protected]
     * @date 2020/5/13 12:20
     */
    public String decrypt(String cipherStr, String privateKey) {
        String result = null;
        try {
            Cipher cipher = Cipher.getInstance(RSA_CIPHER);
            cipher.init(Cipher.DECRYPT_MODE, toPrivateKey(privateKey));
            result =  new String(cipher.doFinal(Base64.getDecoder().decode(cipherStr)), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }
}