對稱加密、非對稱加密、數字簽名

  • 2021 年 2 月 26 日
  • 筆記

1.對稱加密

在對稱加密演算法中,數據發送方將明文(原始數據)和加密密鑰一起經過特殊加密演算法處理後,使其變成複雜的加密密文發送出去。接受方收到密文後,若想解讀原文,則需要使用加密用過的密文及相同演算法的逆演算法對密文進行解密,才能使其恢復成可讀明文。在對稱加密演算法中,使用的密鑰只有一個,雙方都使用這個密鑰對數據進行加密和解密,這就要求解密方事先必須知道加密密鑰。

常見的對稱加密演算法:

  • DES(data encryption standard):數據加密標準,一種使用密鑰加密的塊演算法
  • AES(advanced encryption standard):高級加密標準,又稱Rijndael加密法。

特點:

  • 加密速度塊,可以加密大文件
  • 密文可逆,一旦密鑰泄露,可能會導致數據爆露
  • 加密後在編碼表找不到的字元,會出現亂碼
  • 一般與base64結合使用

加密模式:

  • ECB(electronic code book):電子密碼本,需要加密的消息按照塊密碼的塊大小被分為數個塊,並對每個塊進行獨立加密。優點是可以可以並行處理數據,缺點是同樣的原文加密生成的密文是一樣的,不能很好的保護數據
  • CBC(cipher block chaining):密碼塊連接,每個明文塊先與前一個密文塊進行異或運算,再進行加密,在這種方式中,每個密文塊都依賴於它前面的所有的明文塊。優點是同樣的原文生成的密文不一樣,缺點是如果一個分組丟失,那麼後面的分組將全部作廢(同步錯誤)
  • CFB(cipher feedback mode):加密回饋模式,類似於自動同步序列密碼,分組加密後,按8位分組將密文和明文進行移位異或後得到輸出同時回饋回移位暫存器,優點是可以按位元組進行加解密,缺點是明文的一個錯誤回影響後面的密文(錯誤擴散)
  • OFB(Output feedback mode):輸出回饋模式,將分組密碼作為同步序列密碼運行,和CFB相似,不過OFB用的是前一個n位密文輸出分組回饋回移位暫存器。OFB沒有錯誤擴散問題。

填充模式:當需要按塊處理的數據,數據長度不符合時,按照一定的方法填充滿塊的長度的規則

  • NoPadding:不填充,在DES加密演算法下,要求原文的長度必須時8個位元組的整數倍;在AES加密演算法下,要求原文長度必須時16個位元組的整數倍
  • PKCS5Padding:數據塊不足8個位元組的時候就補足

Demo程式碼:

package encryptAndDecrypt;

import com.sun.org.apache.xml.internal.security.utils.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * @className: AesDesDemo
 * @description: 使用DES加密解密,加密和解密都是使用同一個密鑰
 * @author: charon
 * @create: 2021-02-25 08:27
 */
public class AesDesDemo {

    public static void main(String[] args) throws Exception {
        // 需要加密的內容,如果是不使用填充模式,那麼密文的位元組數必須是8的整數倍
        String input = "charon周1234567";
        // 密鑰,DES加密的密鑰必須是8位,如果是AES加密的密鑰必須是16位
        String key = "12345678";
        // 加密演算法,DES表示加密類型,CBC表示加密模式,NoPadding表示填充模式。
        // 默認情況下是使用 DES/ECB/PKCS5padding
        String algorithm = "DES/CBC/NoPadding";
        String algorithmType = "DES";
        //偏移量,是使用cbc的加密模式,在使用iv向量進行加密的時候,IV也必須是8位
        String offset = "12345678";
        // 加密
        String encryptDes = encryptDES(input, key, algorithm, algorithmType,offset);
        System.out.println("密文: " + encryptDes);
        // 解密
        String decryptDES = decryptDES(encryptDes, key, algorithm, algorithmType,offset);
        System.out.println("解密: "+ decryptDES);
    }

    /**
     * 解密
     * @param encryptDes 密文
     * @param key 密鑰
     * @param algorithm 解密演算法
     * @param algorithmType 解密類型
     * @param offset 偏移量
     * @return
     */
    private static String decryptDES(String encryptDes, String key, String algorithm, String algorithmType,String offset) throws Exception {
        // 獲取解密對象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 創建加密規則
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithmType);
        // 創建iv向量
        IvParameterSpec ivParameterSpec = new IvParameterSpec(offset.getBytes());
        // 初始化解密規則
        cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,ivParameterSpec);
        // 加密
        byte[] bytes = cipher.doFinal(Base64.decode(encryptDes));
        return new String(bytes);
    }

    /**
     * 加密
     * @param input 加密內容
     * @param key 密鑰
     * @param algorithm 加密演算法
     * @param algorithmType 加密類型
     * @param  offset 偏移量
     * @return 密文
     */
    private static String encryptDES(String input, String key, String algorithm, String algorithmType,String offset) throws Exception {
        // 獲取加密對象
        Cipher cipher = Cipher.getInstance(algorithm);
        // 創建加密規則
        SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(),algorithmType);
        // 創建iv變數,iv向量,是使用cbc的加密模式
        // 在使用iv向量進行加密的時候,IV也必須是8位
        IvParameterSpec ivParameterSpec = new IvParameterSpec(offset.getBytes());
        // 初始化加密
        cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,ivParameterSpec);
        // 加密
        byte[] bytes = cipher.doFinal(input.getBytes());
        // 返回加密後的數據
        return Base64.encode(bytes);
    }
}

2.非對稱加密

與對稱加密演算法不同,非對稱加密演算法需要兩個密鑰:公鑰和私鑰。公鑰與私鑰是一對,如果使用公鑰對數據進行加密,那麼只能有對應的私鑰才能解密;如果使用私鑰進行加密,那麼只能用對應的公鑰才能解密。因為加密和解密是使用的兩個不同的密鑰。

常見的非對稱加密演算法:

  • RSA:根據數論,尋求兩個大素數比較簡單,而將它們的乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。公鑰是可發布的供任何人使用,私鑰則為自己所有,供解密之用。
  • ECC:橢圓加密演算法

特點:

  • 加密和解密使用不同的密鑰
  • 如果使用私鑰加密, 只能使用公鑰解密
  • 如果使用公鑰加密, 只能使用私鑰解密
  • 處理數據的速度較慢, 因為安全級別高

demo程式碼:

package encryptAndDecrypt;

import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.apache.commons.io.FileUtils;

import javax.crypto.Cipher;
import java.io.File;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * @className: RsaDemo
 * @description: 非對稱加密之RSA
 * @author: charon
 * @create: 2021-02-25 11:05
 */
public class RsaDemo {

    public static void main(String[] args) throws Exception {
        String algorithm = "RSA";
        // 生成密鑰對並保存在本地文件中,這樣就可以不用每次都重新申請了。
        // 也有系統為了更安全,每次都會新生成密鑰,然後將公鑰返回給客戶端,私鑰存入redis中,解密的時候從根據session從redis中取對應的私鑰
        generateKeyToFile(algorithm, "pub.txt", "pri.txt");
        // 獲取公鑰
        PublicKey publicKey = getPublicKey("pub.txt", "RSA");
        // 獲取私鑰
        PrivateKey privateKey = getPrivateKey("pri.txt", "RSA");
        // 原文
        String input = "charon周";
        // 加密 
        String encryptRSA = encryptRSA(input,algorithm,publicKey);
        System.out.println("密文:"+ encryptRSA);
        // 解密
        String decryptRSA = decryptRSA(encryptRSA,algorithm,privateKey);
        System.out.println("原文:"+ decryptRSA);

    }

    /**
     * 解密
     * @param encryptRSA 密文
     * @param algorithm 演算法
     * @param privateKey 私鑰
     * @return 原文
     */
    private static String decryptRSA(String encryptRSA, String algorithm, PrivateKey privateKey) throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE,privateKey);
        byte[] bytes = cipher.doFinal(Base64.decode(encryptRSA));
        return new String(bytes);
    }

    /**
     * 加密
     * @param input 原文
     * @param algorithm 加密演算法
     * @param publicKey 公鑰
     * @return 密文
     */
    private static String encryptRSA(String input, String algorithm, PublicKey publicKey) throws Exception{
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE,publicKey);
        byte[] bytes = cipher.doFinal(input.getBytes());
        return Base64.encode(bytes);
    }

    /**
     * 獲取私鑰
     * @param privateKeyPath 私鑰文件路徑
     * @param algorithm 演算法
     * @return 私鑰
     */
    public static PrivateKey getPrivateKey(String privateKeyPath, String algorithm) throws Exception {
        // 讀取文件內容
        String strPrivateKey = FileUtils.readFileToString(new File(privateKeyPath), Charset.defaultCharset());
        // 創建key工廠
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 創建私鑰規則
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decode(strPrivateKey));
        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 獲取公鑰
     * @param publicKeyPath 公鑰文件路徑
     * @param algorithm  演算法
     * @return 公鑰
     */
    public static PublicKey getPublicKey(String publicKeyPath, String algorithm) throws Exception{
        // 讀取文件內容
        String strPublicKey = FileUtils.readFileToString(new File(publicKeyPath), Charset.defaultCharset());
        // 創建key工廠
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        // 創建公鑰規則
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decode(strPublicKey));
        return keyFactory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 生成密鑰對,並放入本地文件
     * @param algorithm
     * @param publicKeyPath
     * @param privateKeyPath
     */
    private static void generateKeyToFile(String algorithm, String publicKeyPath, String privateKeyPath) throws Exception{
        // 生成實例
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        // 生成密鑰對
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 生成私鑰
        PrivateKey privateKey = keyPair.getPrivate();
        // 獲取私鑰位元組數組
        byte[] privateKeyEncoded = privateKey.getEncoded();
        // 生成公鑰
        PublicKey publicKey = keyPair.getPublic();
        // 獲取公鑰的位元組數組
        byte[] publicKeyEncoded = publicKey.getEncoded();
        // base64編碼
        String strPrivateKeyEncoded = Base64.encode(privateKeyEncoded);
        String strPublicKeyEncoded = Base64.encode(publicKeyEncoded);
        // 存到文件中
        FileUtils.write(new File(privateKeyPath),strPrivateKeyEncoded, Charset.forName("UTF-8"));
        FileUtils.write(new File(publicKeyPath),strPublicKeyEncoded, Charset.forName("UTF-8"));
    }
}

3.數字簽名

數字簽名(又稱公鑰數字簽名)是只有資訊的發送者才能產生的別人無法偽造的一段數字串,這段數字串同時也是對資訊的發送者發送資訊真實性的一個有效證明。它是一種類似寫在紙上的普通的物理簽名,但是使用了公鑰加密領域的技術來實現的,用於鑒別數字資訊的方法。一套數字簽名通常定義兩種互補的運算,一個用於簽名,另一個用於驗證。數字簽名是非對稱密鑰加密技術與數字摘要技術的應用。

demo程式碼:

package encryptAndDecrypt;

import com.sun.org.apache.xml.internal.security.utils.Base64;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;

/**
 * @className: SignatureDemo
 * @description: 數字簽名
 * @author: charon
 * @create: 2021-02-25 15:40
 */
public class SignatureDemo {
    public static void main(String[] args) throws Exception{
        // 這裡直接使用之前的公鑰和私鑰
        // 獲取公鑰
        PublicKey publicKey = RsaDemo.getPublicKey("pub.txt", "RSA");
        // 獲取私鑰
        PrivateKey privateKey = RsaDemo.getPrivateKey("pri.txt", "RSA");
        String input = "charon周";
        // 獲取簽名
        String signature = getSignature(input, "Sha256withrsa", privateKey);
        System.out.println("簽名:" + signature);
        // 校驗簽名
        boolean verifySignature = verifySignature(input, "Sha256withrsa", publicKey, signature);
        System.out.println("結果: "+ verifySignature);
    }

    /**
     * 校驗簽名
     * @param input 原文
     * @param algorithm 演算法
     * @param publicKey 公鑰
     * @param signatureData 簽名
     * @return
     */
    private static boolean verifySignature(String input, String algorithm, PublicKey publicKey, String signatureData) throws Exception{
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(publicKey);
        signature.update(input.getBytes());
        // 校驗數據
        boolean verify = signature.verify(Base64.decode(signatureData));
        return verify;
    }

    /**
     * 簽名
     * @param input 原文
     * @param algorithm 演算法
     * @param privateKey 私鑰
     * @return 簽名
     */
    private static String getSignature(String input, String algorithm, PrivateKey privateKey) throws Exception{
        // 獲取簽名對象
        Signature signature = Signature.getInstance(algorithm);
        // 初始化簽名
        signature.initSign(privateKey);
        signature.update(input.getBytes());
        // 簽名
        byte[] bytes = signature.sign();
        return Base64.encode(bytes);
    }
}

參考文章:

//blog.csdn.net/u014294681/article/details/86705999