快速理解RSA加密演算法
RSA公開金鑰密碼體制
所謂的公開金鑰密碼體制就是使用不同的加密金鑰與解密金鑰,是一種“由已知加密金鑰推匯出解密金鑰在計算上是不可行的”密碼體制。
在公開金鑰密碼體制中,加密金鑰(即公開金鑰)PK是公開資訊,而解密金鑰(即祕密金鑰)SK是需要保密的。加密演算法E和解密演算法D也都是公開的。雖然解密金鑰SK是由公開金鑰PK決定的,但卻不能根據PK計算出SK。
根據金鑰的使用方法,可以將密碼分為對稱密碼和公鑰密碼
對稱密碼:加密和解密使用同一種金鑰的方式
公鑰密碼:加密和解密使用不同的密碼的方式,因此公鑰密碼通常也稱為非對稱密碼。
RSA演算法實現過程
RSA演算法基於一個十分簡單的數論事實:將兩個大素數相乘十分容易,但那時想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密金鑰,即公鑰,而兩個大素數組合成私鑰。
公鑰是可釋出的供任何人使用,私鑰則為自己所有,供解密之用。
解密者擁有私鑰,並且將由私鑰計算生成的公鑰釋出給加密者。加密都使用公鑰進行加密,並將密文傳送到解密者,解密者用私鑰解密將密文解碼為明文。
示例:
以甲要把資訊發給乙為例,
首先確定角色:甲為加密者,乙為解密者。
然後由乙隨機確定一個KEY,稱之為密匙,將這個KEY始終儲存在機器B中而不發出來;
接著由這個 KEY計算出另一個KEY,稱之為公匙。這個公鑰的特性是幾乎不可能通過它自身計算出生成它的私鑰。
接下來通過網路把這個公鑰傳給甲,甲收到公鑰後,利用公鑰對資訊加密,並把密文通過網路傳送到乙,
最後乙利用已知的私鑰,就對密文進行解碼了。
以上就是RSA演算法的工作流程。
RSA演算法具體實現過程如下:
1、隨意選擇兩個大的質數p和q,p不等於q,計算N=pq。
2、根據尤拉函式,不大於N且與N互質的整數個數為(p-1)(q-1)。
3、選擇一個整數e與(p-1)(q-1)互質,並且e小於(p-1)(q-1)。
4、用以下這個公式計算d:d× e ≡ 1 (mod (p-1)(q-1))。
5、將p和q的記錄銷燬。
注:(N,e)是公鑰,(N,d)是私鑰。
RSA演算法的應用
RSA的公鑰和私鑰是由KeyPairGenerator生成的,獲取KeyPairGenerator的例項後還需要設定其金鑰位數。
設定金鑰位數越高,加密過程越安全,一般使用1024位。
如下程式碼:
java程式碼:
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA); // 金鑰位數 keyPairGen.initialize(1024);
公鑰和私鑰可以通過KeyPairGenerator執行generateKeyPair()後生成金鑰對KeyPair,通過KeyPair.getPublic()和KeyPair.getPrivate()來獲取。
如下程式碼:
java程式碼:
// 動態生成金鑰對,這是當前最耗時的操作,一般要2s以上。 KeyPair keyPair = keyPairGen.generateKeyPair(); // 公鑰 PublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 私鑰 PrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); byte[] publicKeyData = publicKey.getEncoded(); byte[] privateKeyData = publicKey.getEncoded();
公鑰和私鑰都有它們自己獨特的位元編碼,可以通過getEncoded()方法獲取,返回型別為byte[]。
通過byte[]可以再度將公鑰或私鑰還原出來。
具體程式碼如下:
java程式碼:
// 通過公鑰byte[]將公鑰還原,適用於RSA演算法 public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException,InvalidKeySpecException { X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); PublicKey publicKey = keyFactory.generatePublic(keySpec); return publicKey; } // 通過私鑰byte[]將公鑰還原,適用於RSA演算法 public static PrivateKey getPrivateKey(byte[] keyBytes) throws NoSuchAlgorithmException,InvalidKeySpecException { PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; }
在上面講到的RSA演算法實現過程中提到(N,e)是公鑰,(N,d)是私鑰。既然已經獲取到了PublicKey和PrivateKey了,那如何取到N、e、d這三個值呢。
要取到這三個值,首先要將PublicKey和PrivateKey強制轉換成RSAPublicKey和RSAPrivateKey。
共同的N值可以通過getModulus()獲取。
執行RSAPublicKey.getPublicExponent()可以獲取到公鑰中的e值,
執行RSAPrivateKey.getPrivateExponent()可以獲取私鑰中的d值。
這三者返回型別都是BigInteger。程式碼如下:
java程式碼:
// 列印公鑰資訊 public static void printPublicKeyInfo(PublicKey key){ RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey; Log.d(MainActivity.TAG, “RSAPublicKey:”); Log.d(MainActivity.TAG, “Modulus.length=” + rsaPublicKey.getModulus().bitLength()); Log.d(MainActivity.TAG, “Modulus=” + rsaPublicKey.getModulus().toString()); Log.d(MainActivity.TAG, “PublicExponent.length=” + rsaPublicKey.getPublicExponent().bitLength()); Log.d(MainActivity.TAG, “PublicExponent=” + rsaPublicKey.getPublicExponent().toString()); } // 列印私鑰資訊 public static void printPublicKeyInfo(PrivateKey key){ RSAPrivateKey rsaPublicKey = (RSAPrivateKey) privateKey; Log.d(MainActivity.TAG, “RSAPrivateKey:”); Log.d(MainActivity.TAG, “Modulus.length=” + rsaPrivateKey.getModulus().bitLength()); Log.d(MainActivity.TAG, “Modulus=” + rsaPrivateKey.getModulus().toString()); Log.d(MainActivity.TAG, “PublicExponent.length=” + rsaPrivateKey.getPrivateExponent().bitLength()); Log.d(MainActivity.TAG, “PublicExponent=” + rsaPrivateKey.getPrivateExponent().toString()); }
由於程式中動態生成KeyPair對明文加密後生成的密文是不可測的,所以在實際開發中通常在生成一個KeyPair後將公鑰和私鑰的N、e、d這三個特徵值記錄下來,在真實的開發中使用這三個特徵值再去將PublicKey和PrivateKey還原出來。
還原方法如下:
java程式碼:
// 使用N、e值還原公鑰 public static PublicKey getPublicKey(String modulus, String publicExponent) throws NoSuchAlgorithmException, InvalidKeySpecException { BigInteger bigIntModulus = new BigInteger(modulus); BigInteger bigIntPrivateExponent = new BigInteger(publicExponent); RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigIntModulus, bigIntPrivateExponent); KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); PublicKey publicKey = keyFactory.generatePublic(keySpec); return publicKey; } // 使用N、d值還原公鑰 public static PrivateKey getPrivateKey(String modulus, String privateExponent) throws NoSuchAlgorithmException, InvalidKeySpecException { BigInteger bigIntModulus = new BigInteger(modulus); BigInteger bigIntPrivateExponent = new BigInteger(privateExponent); RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(bigIntModulus, bigIntPrivateExponent); KeyFactory keyFactory = KeyFactory.getInstance(“RSA”); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; }
公鑰和私鑰都具備後,就可以使用加解密的工具類javax.crypto.Cipher對明文和密文進行處理了。
與所有的引擎類一樣,可以通過呼叫Cipher類中的getInstance(String transformation)靜態工廠方法得到Cipher物件。
該方法中的引數描述了由指定輸入產生輸出所進行的操作或操作集合,
可以是下列兩種形式之一:
“algorithm/mode/padding”或“algorithm”。
例如下面的例子就是有效的transformation形式:”DES/CBC/PKCS5Padding”或”DES”。
如果沒有指定模式或填充方式,就使用特定提供者指定的預設模式或預設填充方式。
Cipher的加密和解密方式所呼叫的方法和過程都一樣,只是傳參不同的區別。如下程式碼:
java程式碼:
// 編碼前設定編碼方式及金鑰 cipher.init(mode, key); // 傳入編碼資料並返回編碼結果 byte[] dataResult = cipher.doFinal(input);
Cipher.init(mode, key)方法中MODE指加密或解密模式,值為Cipher.ENCRYPT_MODE或Cipher.DECRYPT_MODE,引數key在加密時傳入PublicKey,在解密時以PrivateKey傳入。
Cipher.doFinal(byte[] data)則是將待編碼資料傳入後並返回編碼結果。
為了將編碼結果轉為可讀字串,通常最後還使用BASE 64演算法對最終的byte[]資料編碼後顯示給開發者。