基於RSA的文件加解密

摘要

因爲最近業務需要使用到openssl的rsa非對稱加密算法,研究了下它的使用方式,但是特殊在於前端分IOS和android兩端,所以前端部門要求使用java給他們做一個加密工具包,但是因爲服務端是python做的,所以需要兩端的數據能夠共通。研究了幾天終於搞定了,下面是一些重要的代碼以及一些我踩過的坑,分享一下。

歡迎訪問我的Github

相關詳細Java端代碼請查看 FileEncryptDecrypt

相關詳細Python端代碼請查看 FileDecryptEncrypt

OpenSSL官網

一. 編譯

  1. 下載:https://www.openssl.org/source/
  2. 安裝 OpenSSL, 請確保你已安裝以下組件:
  • make
  • Perl 5
  • an ANSI C compiler
  • a development environment in form of development libraries and C
  • header files
  • a supported Unix operating system

二. 安裝

$ ./config
$ make
$ make test
$ make install

三. 生成密鑰

生成的密鑰的路徑是你當前執行命令的路徑
這裏默認生成1024長度密鑰
公鑰是基於私鑰來生成的,所以必須先生成私鑰

# 進入openssl
root@VM-0-15-ubuntu:/home/ubuntu# openssl
# 生成一個1024位的私鑰文件rsa_private_key.pem
OpenSSL> genrsa -out rsa_private_key.pem 1024
# 從私鑰中提取公鑰rsa_public_key.pem
OpenSSL> rsa -in rsa_private_key.pem -out rsa_public_key.pem -outform PEM -pubout
# 將私鑰轉換成 DER 格式
OpenSSL> rsa -in rsa_private_key.pem -out rsa_private_key.der -outform der
# 將公鑰轉換成 DER 格式
OpenSSL> rsa -in rsa_public_key.pem -out rsa_public_key.der -pubin -outform der
# 把RSA私鑰轉換成PKCS8格式
OpenSSL> pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
# 從私鑰創建公鑰證書請求
OpenSSL> req -new -key rsa_private_key.pem -out rsa_public_key.csr
# 生成證書並簽名(有效期10年)
OpenSSL> x509 -req -days 3650 -in rsa_public_key.csr -signkey rsa_private_key.pem -out rsa_public_key.crt
# 把crt證書轉換爲der格式
OpenSSL> x509 -outform der -in rsa_public_key.crt -out rsa_public_key.der
# 把crt證書生成私鑰p12文件
OpenSSL> pkcs12 -export -out rsa_private_key.p12 -inkey rsa_private_key.pem -in rsa_public_key.crt

1.JAVA端加密

其實單一語言的加解密都還是比較簡單的,關鍵在於跨語言的兼容問題上。
而且需要特別注意的是我使用的RSA長度是1024的,也就是說我單個需要加密的數據的長度不能超過 1024/8-11=117,實際測試單條數據最大長度是 116 bytes
如果需要擴大需要加密的單條數據的長度,只需要在生成公鑰的時候設置對應的長度即可。((數據長度+12)*8=密鑰長度
其中RSA_ALGORITHM 設置的是算法的使用模式,因爲Python端使用的是OAEP所以這裏用的是RSA/ECB/OAEPWithSHA-256AndMGF1Padding
在Java端我們使用的公鑰格式是DER,如果使用PEM則會出現異常

package site.cnkj.util;

import org.apache.commons.io.FileUtils;
import sun.misc.BASE64Decoder;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

/*
 * @version 1.0 created by Carol on 2019/4/15 15:31
 */
public class FileEncryptDecrypt {

    private static final String RSA_ALGORITHM = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
    private static final Charset UTF8 = Charset.forName("UTF-8");

    static {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }

    /**
     * 加密文件
     * @param keyPath 公鑰路徑,DER
     * @param input 輸入文件地址
     * @param output 輸出文件地址
     */
    public static boolean encrypt(String keyPath, String input, String output){
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        List<String> list = new ArrayList();
        try {
            byte[] buffer = Files.readAllBytes(Paths.get(keyPath));
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);

            File inputFile = new File(input);
            File outputFile = new File(output);
            fileInputStream = new FileInputStream(inputFile);
            fileOutputStream = new FileOutputStream(outputFile);
            byte[] inputByte = new byte[116];
            int len;
            while((len = fileInputStream.read(inputByte)) != -1){
                list.add(new String(inputByte, 0, len));
            }
            for (String s : list) {
                byte [] encrypted = encrypt(publicKey, s);
                fileOutputStream.write(encrypted);
                fileOutputStream.flush();
            }
            fileOutputStream.close();
            fileInputStream.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    private static byte[] encrypt(PublicKey publicKey, String message) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM,"BC");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return Base64.getEncoder().encode(cipher.doFinal(message.getBytes(UTF8)));
    }

    /**
     * 從字符串中加載公鑰
     *
     */
    private static RSAPublicKey loadPublicKey(String publicKeyStr) throws Exception {
        try {
            byte[] buffer = new BASE64Decoder().decodeBuffer(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException(e);
        }
    }


    public static void main(String [] args) throws Exception {
        encrypt("F:\\dls\\chunk\\config\\rsa_public_key.der", "C:\\Users\\Carol\\Desktop\\file\\868663032830438_migu$user$login$_1554947856915.log","F:\\868663032830438_migu$user$login$_1554947856915.log");
    }

}

2.Python端解密

Python端可以使用的模塊是很多的,通常可以用rsa、Crypto、PyCryptodome、PyCrypt等,需要注意的是其中PyCrypt已經停止維護了不建議使用,但是幸運的是PyCrypt有一個分支叫PyCryptodome
爲了便於閱讀,Java端對加密後的數據進行了Base64的格式轉換,因此在Python端也同樣需要對獲取的加密後的數據進行Base64的轉換後再進行解密。
通過觀察,發現加密後進行Base64轉換後的數據結束符爲“==”,因此我在這裏是直接根據結束符進行拆分數據的,拆分後的數據分別解密後放在同一個字符串集合裏面,這裏你不需要但是如果出現換行的情況是否還需要自己手動設置換行,當解密後原有的換行符依然可用。
但是需要注意的是在windows環境下文件的換行符是"\r\n",加密後依然加密的是"\r\n",但是,但我們解密後寫入文件的時候應當替換"\r\n“爲”\n",否則你會發現多了一行換行,因爲當Python重寫寫入文件的時候認爲"\r\n"爲“回車換行”,所以體現在文件中就是兩次換行的效果了。
_init()方法中獲取私鑰的位置只需要把os.path.join(os.path.dirname(os.getcwd()), “config”, “rsa_private_key.pem”)替換成你自己的私鑰路徑即可
在Python端我們使用的私鑰格式是PEM

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    @Time    : 2019/4/12
    @Author  : Carol
    @Site    : 
    @File    : FileDecryptEncrypt.py
    @Software: PyCharm
    @Description: 
"""
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256
import base64
import traceback
import os


class FileDecryptEncrypt():
    def __init__(self):
        try:
            self.private_key = RSA.import_key(open(os.path.join(os.path.dirname(os.getcwd()), "config", "rsa_private_key.pem")).read())
        except Exception as e:
            traceback.print_exc()
            print("加載密鑰出現異常")

    def decrypt(self, root_path, res_path):
        """
        RSA的文件解密
        :param root_path: 加密後的文件
        :param res_path: 解密後的文件
        :return: 解密結果
        """
        new_line = b""
        cipher = PKCS1_OAEP.new(self.private_key, hashAlgo=SHA256)
        try:
            with open(root_path, "r") as rootf:
                lines = rootf.read().split("==")
            for line in lines:
                if len(line) > 0:
                    line = line + "=="
                    b64_decoded_message = base64.b64decode(line)
                    cipherContent = cipher.decrypt(b64_decoded_message)
                    new_line = new_line + cipherContent
            if not os.path.exists(os.path.dirname(res_path)):
                os.makedirs(os.path.dirname(res_path))
            with open(res_path, "w") as resf:
                resf.write(str(new_line, encoding="UTF-8").replace("\r\n", "\n"))
            return True
        except Exception as e:
            traceback.print_exc()
        return False
        
if __name__ == '__main__':
     FileDecryptEncrypt().decrypt("F:\\868663032830438_1554947856915.log","D:\\\\868663032830438_1554947856915.log")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章