1. 程式人生 > >公私鑰加密及數字簽名(RSA)

公私鑰加密及數字簽名(RSA)

公私鑰加密及數字簽名

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);
    }
    
}