比特幣的加密算法(比特幣勒索病毒加密算法原理,rsa文件加密)
本文教你如何用Python與Java對文件進行不對稱加密,并且Python與Java共用一套密鑰,可以相互加解密對方的密文。本文僅作技術交流,請不要用于任何違法用途。
一、引言
最近有個項目需要做文件加密,并且要求python與java能夠相互加解密。對于文件加密,我第一時間想到了比特幣勒索病毒。于是收集了相關的信息,參考瑞星官網的《又一使用.net開發的勒索病毒出現——Prodecryptor勒索病毒》這篇文章,大致了解到程序先使用RSA對AES算法的密鑰進行加密,然后使用AES算法對文件進行加密。
二、RSA與AES簡介
RSA加密算法屬于非對稱加密算法,AES加密算法屬于對稱加密算法。這里我們不聊RSA與AES算法的具體實現,我們先聊下對稱加密與非對稱基本情況。
1、對稱加密速度快,但是只有一個密鑰進行加密和解密。當你的程序加密時,別人能反編譯你的程序獲取密鑰,導致密鑰泄露。
2、非對稱加密速度慢,在加密中采用兩個密鑰,使用公鑰進行加密,私鑰進行解密。在程序中使用公鑰進行加密,別人即使拿到了你的公鑰也無法對文件進行解密。但是每次加密有長度限制,如果加密信息較多,需分段加解密(不建議對大量信息rsa加密,效率低效)。
因此在比特幣勒索病毒程序中加密過程如下:1讀取文件字節數組(加密算法都是對字節數組進行加密)2創建一個隨機AES密鑰,作為session密鑰。3使用RSA算法對AES的session密鑰加密覆蓋寫入文件。4然后使用AES的session密鑰對文件內容進行加密寫入文件。
解密過程:1讀取加密后的文件字節數組,2使用私鑰對AES的密鑰進行解密,3使用AES對文件內容解密,4將解密后的文件內容覆蓋寫入文件中。
三、python與java交互遇到的一些問題
根據網上的例子,如果單獨使用python或者java按上述方式進行文件加解密都很容易找到實現方案,但是如果python和java進行交互可能會出現一些問題。由于我學藝不精,采用了一些投機取巧的方法。
在java代碼中我使用的是javax.crypto.Cipher模塊。python中使用的是Crypto.Cipher的AES模塊,以及RSA模塊(也可以直接使用Crypto.Cipher的RSA模塊)
問題1 Python與Java生成的RSA密鑰無法相互導入
JAVA的RSAKeyPairGenerator并沒有公開,因此我無法確定JAVA是如何導入相關的key。根據網上的一些方法,踩坑兩小時依舊無果。最后解決方法:在對JAVA代碼生成密鑰debug過程中找到相應的n ,e ,d ,p ,q參數的值,也可以使用反射的方法找到這些值(私有屬性,無法直接獲取)。在python的rsa.newkeys函數源碼中,發現可以通過 ( PublicKey(n, e), PrivateKey(n, e, d, p, q) )這種方式導入相應的key。在Crypto.PublicKey的RSA模塊中可以根據RSA.generate函數的源碼找到RsaKey(n=n, e=e, d=d, p=p, q=q, u=u)這段代碼導入相應的key。通過這種方式就可以python與Java使用同一套密鑰了。
問題2 Python使用AES加密后的內容,python可以解密但是Java無法解密。 后發現是數據進行padding時兩邊不一致導致的,后將函數改為以下得到解決
# 數據補空格padding = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()
四、python相關代碼
以下為python加密模塊內容,相應的密鑰是通過【內容三】中的問題1獲取得到。在實際項目中應該只保留公鑰部分,私鑰及解密部分應該剔除,下面java相關內容中也是如此。
"""信息加密模塊"""import rsafrom rsa.key import PublicKey, PrivateKeyfrom Crypto.Random import get_random_bytesfrom Crypto.Cipher import AESn = 125720733811994291169610359480915027242498332835983573581162897380161904809997273335081454438575169422082365180940876618145332064877333466150617673634912674691272686174191003876154229771482350657467474462371208540453058063685991322556840489981457202800248228247442801463255542794487793252246277195836743728807e = 65537d = 19137129262988160103575582437121142435130740930646384943553733986366406188634401922437288699196269132775282283977999986917585984698968195955239969973936686417032907212206649527011774897714725474886031049229542718237754549863294574561095602754592205273482485305289414428603551207636838864047226576028764734081p = 12485487274433155882855920331622116862134407135211164113507239786605989655388750120598556263544308444761407796178741288699689788828502980217890667645001833q = 10069349401319384440381523925066814369492626716870156920501823163204449469801788824691171814613081997769220084438138226773387889070138894859614591998248079(pubkey, privkey) = (PublicKey(n, e), PrivateKey(n, e, d, p, q))def rsa_encrypt_file(file_path): """公鑰加密文件""" with open(file_path, 'rb') as f: data = f.read() with open(file_path, 'wb') as out_file: session_key = get_random_bytes(16) # 這里是將文件最終搞成AES加密 cipher_session = rsa.encrypt(session_key, pubkey) # aes的臨時密鑰 out_file.write(cipher_session) # aes加密 mode = AES.MODE_ECB cryptos = AES.new(session_key, mode) # 數據補空格 padding = lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode() # AES加密后的密文字節數組 cipher_text = cryptos.encrypt(padding(data)) out_file.write(cipher_text)def rsa_decrypt_file(file_path): """私鑰解密文件""" with open(file_path, 'rb') as f: # 此處可以加passphrase, 類似鹽值,但通過rsa模塊生成的時候不支持此參數 # 因為rsa加密是128位,所以前128位為aes密鑰 enc_session_key, cipher_text = [f.read(x) for x in (128, -1)] # rsa解密aes密鑰 session_key = rsa.decrypt(enc_session_key, privkey) # aes對文件內容解密 cipher_aes = AES.new(session_key, AES.MODE_ECB) data = cipher_aes.decrypt(cipher_text) ''' 去除內容末尾的相同的字符,因為加密過程中對未滿16位的做了補充 具體可以參考lambda s: s + (16 - len(s) % 16) * chr(16 - len(s) % 16).encode()這段代碼 下面代碼應該還要校驗c是否等于16 - len(s) % 16 ''' c = data[-1] index = 0 if c < 16: for i, d in enumerate(data[::-1]): if d == c: index = i else: break data = data[:- 1 - index] with open(file_path, 'wb') as f: f.write(data)if __name__ == '__main__': rsa_encrypt_file(r'd:\1.txt') rsa_decrypt_file(r'd:\1.txt')
四、java相關代碼
最近寫python代碼寫得比較多,函數及屬性的命名都沒有按照Java的駝峰命名方式,大家隨意看看哈。
main.java
import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;public class Main { public static void main(String[] args) throws Exception { rsa_encrypt_file("d:\\1.txt"); rsa_decrypt_file("d:\\1.txt"); } /** * 字節數組復制 * @param original 原始的字節數組 * @param start 要復制的開始位置下標 * @param end 要復制的結束位置下標 * @return */ public static byte[] copy_array(byte[] original, int start, int end) { int newLength = end - start; byte[] copy = new byte[newLength]; System.arraycopy(original, start, copy, 0, Math.min(original.length, newLength)); return copy; } /** * rsa對文件解密,先解密aes密鑰,再解密文件 * * @param file_path * @throws Exception */ public static void rsa_decrypt_file(String file_path) throws Exception { // 讀取數據 File f = new File(file_path); int length = (int) f.length(); byte[] data = new byte[length]; FileInputStream fis = new FileInputStream(f); fis.read(data); fis.close(); // 解密 byte[] enc_session_key = copy_array(data, 0, 128); byte[] session_key = RSAUtils.decrypt(enc_session_key, RSAUtils.privateKeyString); byte[] cipher_text = copy_array(data, 128, data.length); byte[] text = AESUtil.aes_decrypt(cipher_text, session_key); // 寫入文件 FileOutputStream fos = new FileOutputStream(f); fos.write(text); fos.flush(); fos.close(); } /** * rsa對文件加密,先加密aes密鑰,再加密文件 * * @param file_path * @throws Exception */ public static void rsa_encrypt_file(String file_path) throws Exception { // 讀取數據 File f = new File(file_path); int length = (int) f.length(); byte[] data = new byte[length]; FileInputStream fis = new FileInputStream(f); fis.read(data); fis.close(); // aes的隨機密鑰 byte[] session_key = AESUtil.create_aes_Key(); // 使用rsa對aes的密鑰加密 byte[] cipher_session = RSAUtils.encrypt(session_key, RSAUtils.publicKeyString); // 使用aes算法對文件內容加密 byte[] cipher_data = AESUtil.aes_encrypt(data, session_key); // 將密鑰寫入文件 FileOutputStream fos = new FileOutputStream(f); fos.write(cipher_session); fos.write(cipher_data); fos.flush(); fos.close(); }}
AESUtil.java
import java.security.Key;import java.security.NoSuchAlgorithmException;import javax.crypto.Cipher;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;public class AESUtil { /** * 創建128位的隨機密鑰 * * @return * @throws NoSuchAlgorithmException */ public static byte[] create_aes_Key() throws NoSuchAlgorithmException { // 生成key KeyGenerator keyGenerator; // 構造密鑰生成器,指定為AES算法,不區分大小寫 keyGenerator = KeyGenerator.getInstance("AES"); // 生成一個128位的隨機源,根據傳入的字節數組 keyGenerator.init(128); // 產生原始對稱密鑰 SecretKey secretKey = keyGenerator.generateKey(); // 獲得原始對稱密鑰的字節數組 byte[] keyBytes = secretKey.getEncoded(); // key轉換,根據字節數組生成AES密鑰 // Key key = new SecretKeySpec(keyBytes, "AES"); return keyBytes; } /** * AES解密 * * @param cipherText 加密后的密文byte數組 * @param key_byte 解密用密鑰 */ public static byte[] aes_decrypt(byte[] cipherText, byte[] key_byte) { Cipher cipher; byte[] result = null; try { Key key = new SecretKeySpec(key_byte, "AES"); cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); // 初始化密碼器,第一個參數為加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二個參數為使用的KEY cipher.init(Cipher.DECRYPT_MODE, key); result = cipher.doFinal(cipherText); } catch (Exception e) { e.printStackTrace(); } return result; } /** * AES加密 * * @param context 需要加密的明文 KEYSTR 加密用密鑰 * @return */ public static byte[] aes_encrypt(byte[] context, byte[] key_byte) { try { Key key = new SecretKeySpec(key_byte, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key); // 將加密并編碼后的內容解碼成字節數組 return cipher.doFinal(context); } catch (Exception e) { e.printStackTrace(); return null; } }}
RSAUtils.java
此處密鑰是通過【內容三】中的問題1獲取得到的。生成的密鑰步驟可以參考genKeyPair函數
import javax.crypto.Cipher;import java.security.KeyFactory;import java.security.KeyPair;import java.security.KeyPairGenerator;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import java.util.Base64;public class RSAUtils { private static Base64.Decoder decoder = Base64.getDecoder(); private static Base64.Encoder encoder = Base64.getEncoder(); // 預先得到的公鑰和私鑰 public static String publicKeyString = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCzCEKyIaoSOCd+8XG/u6X9fGGlgmqygZPAWAYpSaPebX4kUm2yloxLdTAwYbCQmMhcgyOvxdo9H9Qjc/uw1SY43mvIAqXaRNQ9FYbzMCcV167ebjJF4xFjPICf5bQqBh4mt5vuf0CM1lpZazI7rsI2R5/pdVwmVXKFEVmqpuu+pwIDAQAB"; public static String privateKeyString = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALMIQrIhqhI4J37xcb+7pf18YaWCarKBk8BYBilJo95tfiRSbbKWjEt1MDBhsJCYyFyDI6/F2j0f1CNz+7DVJjjea8gCpdpE1D0VhvMwJxXXrt5uMkXjEWM8gJ/ltCoGHia3m+5/QIzWWllrMjuuwjZHn+l1XCZVcoURWaqm676nAgMBAAECgYAbQI6mfulcjJ+2expNjUrfIyfaAdgsA/1xsfR+JG+FVDV3YfTA0pnYgqYrNzOhTyBwtKWiBAQMeePY4bbWXBvNGsCSd7pwP4Io2B24fm4yKSIUbjJKx2jQMbLn+kvNu9Tw508ogmEfnHzUmVy0h2ePN+6hTUCZ7jjaNFlK2oACgQJBAO5jymEpoTdqFluIt2ETc6ElW9yPg1IrIJNT2QSPMS1i2xd/BmPP9PQMcV4hHv7knLFeLAjVaf0QFJ0SetlqYGkCQQDAQfRiii7xbt0sYMIrSWe4ygjSvxC9Job1dtgyI1Q2EjfO6wZzd+7Iu+pbC2sA2fCk40YMmxSmvCQoOuO/RESPAkEA1IjZfOi9mAcYKcFpJL5P38LL9IdqoA5dO5yMpjj3siwpgvg3/TMBg7e4NyC2Xq/5V1TLU5DZrsnwZt1782yYyQJBAL+4hcZGWm20yqZYjwivmNlzz7ypgD2/z9G0g//rryyEmlajlLlNHjfa/OdxyXD95LXpVo93ju5+q+faYgb4Qw0CQG6d0blzJS9qmnkP61Q49bBfiOPLF5MF9T+VyXe7zyjGKdrnT6WUucGTjjdVW1FX1mkMBtUdu9VPnbGiYzvoyuM="; public static void main(String[] args) throws Exception { genKeyPair(); // 加密字符串// String message = "df723820";// String messageEn = encrypt(message, publicKeyString);// System.out.println(message + "\t加密后的字符串為:" + messageEn);// String messageDe = decrypt(messageEn, privateKeyString);// System.out.println("還原后的字符串為:" + messageDe); } /** * 隨機生成密鑰對 * * @throws NoSuchAlgorithmException */ public static void genKeyPair() throws NoSuchAlgorithmException { // KeyPairGenerator類用于生成公鑰和私鑰對,基于RSA算法生成對象 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); // 初始化密鑰對生成器,密鑰大小為96-1024位 keyPairGen.initialize(1024, new SecureRandom()); // 生成一個密鑰對,保存在keyPair中 KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私鑰 RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公鑰 System.out.println(publicKey.getModulus()); System.out.println(publicKey.getPublicExponent()); publicKeyString = new String(Base64.getEncoder().encodeToString(publicKey.getEncoded())); // 得到私鑰字符串 privateKeyString = new String(Base64.getEncoder().encodeToString(privateKey.getEncoded())); System.out.println("生成的公鑰:" + publicKeyString); System.out.println("生成的私鑰:" + privateKeyString); } /** * RSA公鑰加密 * * @param str 加密字符串 * @param publicKey 公鑰 * @return 密文 * @throws Exception 加密過程中的異常信息 */ public static String encrypt(String str, String publicKey) throws Exception { // base64編碼的公鑰 byte[] decoded = decoder.decode(publicKey); RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(decoded)); // RSA加密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); String outStr = encoder.encodeToString(cipher.doFinal(str.getBytes("UTF-8"))); return outStr; } /** * RSA私鑰解密 * * @param str 加密字符串 * @param privateKey 私鑰 * @return 銘文 * @throws Exception 解密過程中的異常信息 */ public static String decrypt(String str, String privateKey) throws Exception { // 64位解碼加密后的字符串 byte[] inputByte = decoder.decode(str.getBytes("UTF-8")); // base64編碼的私鑰 byte[] decoded = decoder.decode(privateKey); RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA") .generatePrivate(new PKCS8EncodedKeySpec(decoded)); // RSA解密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, priKey); String outStr = new String(cipher.doFinal(inputByte)); return outStr; } public static byte[] decrypt(byte[] inputByte, String privateKey) throws Exception { // base64編碼的私鑰 byte[] decoded = decoder.decode(privateKey); // base64編碼的私鑰 RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA") .generatePrivate(new PKCS8EncodedKeySpec(decoded)); // RSA解密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, priKey); return cipher.doFinal(inputByte); } public static byte[] encrypt(byte[] inputByte, String publicKey) throws Exception { // base64編碼的公鑰 byte[] decoded = decoder.decode(publicKey); RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(decoded)); // RSA加密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, pubKey); return cipher.doFinal(inputByte); }}