java加解密之RSA使用
最近為了分析一段請求流,不得不去研究一下RSA加密。
首先,強調一點:金鑰的“鑰”讀“yue”,不是“yao”,額。。。
網上關於RSA的原理一抓一大把的,這裡只是簡單說說我的理解:
1. 兩個足夠大的互質數p, q;
2. 用於模運算的模 n=p*q;
3. 公鑰KU(e, n)中的e滿足 1<e< (p-1)(q-1),且與(p-1)(q-1)互質;
4. 金鑰KR(d, n)中的d滿足 d*e % (p-1)(q-1)= 1,%是取餘運算。
因為公鑰是公開的,所以我知道了e和n,那麼根據2,3,4式子的關係,我們只要從n的值推出p, q的值則可計算出d的值,也就能找到金鑰。
然而,關鍵就在這裡, n=p*q,如果兩個互質數p和q足夠大,那麼根據目前的計算機技術和其他工具,至今也沒能從n分解出p和q,這是數學上的一個難題,也正是這個難題成為了RSA加密至今被廣泛使用的原因。換句話說,只要金鑰長度n足夠大(一般1024足矣),基本上不可能從公鑰資訊推出私鑰資訊。
好了,這裡作為研究的隨筆,記錄一下java如何使用,以下主要有三種方法,基本大同小異,只是獲取公鑰私鑰的途徑不一樣就是了:
方法一:
利用KeyPairGenerator直接生成公鑰和金鑰,一般私鑰保留給服務端,公鑰交給客戶端。
public class RSACryptography { public static String data="hello world"; public static void main(String[] args) throws Exception { // TODO Auto-generated method stub KeyPair keyPair=genKeyPair(1024); //獲取公鑰,並以base64格式打印出來 PublicKey publicKey=keyPair.getPublic(); System.out.println("公鑰:"+new String(Base64.getEncoder().encode(publicKey.getEncoded()))); //獲取私鑰,並以base64格式打印出來 PrivateKey privateKey=keyPair.getPrivate(); System.out.println("私鑰:"+new String(Base64.getEncoder().encode(privateKey.getEncoded()))); //公鑰加密 byte[] encryptedBytes=encrypt(data.getBytes(), publicKey); System.out.println("加密後:"+new String(encryptedBytes)); //私鑰解密 byte[] decryptedBytes=decrypt(encryptedBytes, privateKey); System.out.println("解密後:"+new String(decryptedBytes)); } //生成金鑰對 public static KeyPair genKeyPair(int keyLength) throws Exception{ KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(1024); return keyPairGenerator.generateKeyPair(); } //公鑰加密 public static byte[] encrypt(byte[] content, PublicKey publicKey) throws Exception{ Cipher cipher=Cipher.getInstance("RSA");//java預設"RSA"="RSA/ECB/PKCS1Padding" cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(content); } //私鑰解密 public static byte[] decrypt(byte[] content, PrivateKey privateKey) throws Exception{ Cipher cipher=Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(content); } }
執行結果:
方法二:
實際上,方法一隻是用來生成金鑰就OK了,生成的金鑰需要儲存到本地檔案中,所以一般不會在客戶端呼叫KeyPairGenerator進行金鑰的生成操作。
這裡,我們可以將方法一得到的金鑰儲存到檔案,下次我們直接讀取就可以了。我假設以String的形式儲存在檔案內,那麼接下來直接使用讀取到的String生成金鑰即可。
當然,你也可以使用openssl來生成也可以,不過我覺得麻煩就不弄了。
執行結果:public class RSACryptography { public static String data="hello world"; public static String publicKeyString="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCISLP98M/56HexX/9FDM8iuIEQozy6kn2JMcbZS5/BhJ+U4PZIChJfggYlWnd8NWn4BYr2kxxyO8Qgvc8rpRZCkN0OSLqLgZGmNvoSlDw80UXq90ZsVHDTOHuSFHw8Bv//B4evUNJBB8g9tpVxr6P5EJ6FMoR/kY2dVFQCQM4+5QIDAQAB"; public static String privateKeyString="MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIhIs/3wz/nod7Ff/0UMzyK4gRCjPLqSfYkxxtlLn8GEn5Tg9kgKEl+CBiVad3w1afgFivaTHHI7xCC9zyulFkKQ3Q5IuouBkaY2+hKUPDzRRer3RmxUcNM4e5IUfDwG//8Hh69Q0kEHyD22lXGvo/kQnoUyhH+RjZ1UVAJAzj7lAgMBAAECgYAVh26vsggY0Yl/Asw/qztZn837w93HF3cvYiaokxLErl/LVBJz5OtsHQ09f2IaxBFedfmy5CB9R0W/aly851JxrI8WAkx2W2FNllzhha01fmlNlOSumoiRF++JcbsAjDcrcIiR8eSVNuB6ymBCrx/FqhdX3+t/VUbSAFXYT9tsgQJBALsHurnovZS1qjCTl6pkNS0V5qio88SzYP7lzgq0eYGlvfupdlLX8/MrSdi4DherMTcutUcaTzgQU20uAI0EMyECQQC6il1Kdkw8Peeb0JZMHbs+cMCsbGATiAt4pfo1b/i9/BO0QnRgDqYcjt3J9Ux22dPYbDpDtMjMRNrAKFb4BJdFAkBMrdWTZOVc88IL2mcC98SJcII5wdL3YSeyOZto7icmzUH/zLFzM5CTsLq8/HDiqVArNJ4jwZia/q6Fg6e8KO2hAkB0EK1VLF/ox7e5GkK533Hmuu8XGWN6I5bHnbYd06qYQyTbbtHMBrFSaY4UH91Qwd3u9gAWqoCZoGnfT/o03V5lAkBqq8jZd2lHifey+9cf1hsHD5WQbjJKPPIb57CK08hn7vUlX5ePJ02Q8AhdZKETaW+EsqJWpNgsu5wPqsy2UynO"; public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //獲取公鑰 PublicKey publicKey=getPublicKey(publicKeyString); //獲取私鑰 PrivateKey privateKey=getPrivateKey(privateKeyString); //公鑰加密 byte[] encryptedBytes=encrypt(data.getBytes(), publicKey); System.out.println("加密後:"+new String(encryptedBytes)); //私鑰解密 byte[] decryptedBytes=decrypt(encryptedBytes, privateKey); System.out.println("解密後:"+new String(decryptedBytes)); } //將base64編碼後的公鑰字串轉成PublicKey例項 public static PublicKey getPublicKey(String publicKey) throws Exception{ byte[ ] keyBytes=Base64.getDecoder().decode(publicKey.getBytes()); X509EncodedKeySpec keySpec=new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory=KeyFactory.getInstance("RSA"); return keyFactory.generatePublic(keySpec); } //將base64編碼後的私鑰字串轉成PrivateKey例項 public static PrivateKey getPrivateKey(String privateKey) throws Exception{ byte[ ] keyBytes=Base64.getDecoder().decode(privateKey.getBytes()); PKCS8EncodedKeySpec keySpec=new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory=KeyFactory.getInstance("RSA"); return keyFactory.generatePrivate(keySpec); } //公鑰加密 public static byte[] encrypt(byte[] content, PublicKey publicKey) throws Exception{ Cipher cipher=Cipher.getInstance("RSA");//java預設"RSA"="RSA/ECB/PKCS1Padding" cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(content); } //私鑰解密 public static byte[] decrypt(byte[] content, PrivateKey privateKey) throws Exception{ Cipher cipher=Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(content); } }
方法三:
除了儲存金鑰字串之外,其他的做法一般是隻儲存 模n(modulus),公鑰和私鑰的e和d(exponent)。
其中,n, e, d可以這樣獲取到,獲取到後可以儲存到本地檔案中。
//獲取公鑰
RSAPublicKey publicKey=(RSAPublicKey) getPublicKey(publicKeyString);
BigInteger modulus1=publicKey.getModulus();
BigInteger exponent1=publicKey.getPublicExponent();
//獲取私鑰
RSAPrivateKey privateKey=(RSAPrivateKey) getPrivateKey(privateKeyString);
BigInteger modulus2=privateKey.getModulus();
BigInteger exponent2=privateKey..getPrivateExponent();
這裡,假設我已經從檔案中讀取到了modulus和exponent:
public class RSACryptography {
public static String data="hello world";
public static String modulusString="95701876885335270857822974167577168764621211406341574477817778908798408856077334510496515211568839843884498881589280440763139683446418982307428928523091367233376499779842840789220784202847513854967218444344438545354682865713417516385450114501727182277555013890267914809715178404671863643421619292274848317157";
public static String publicExponentString="65537";
public static String privateExponentString="15118200884902819158506511612629910252530988627643229329521452996670429328272100404155979400725883072214721713247384231857130859555987849975263007110480563992945828011871526769689381461965107692102011772019212674436519765580328720044447875477151172925640047963361834004267745612848169871802590337012858580097";
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//由n和e獲取公鑰
PublicKey publicKey=getPublicKey(modulusString, publicExponentString);
//由n和d獲取私鑰
PrivateKey privateKey=getPrivateKey(modulusString, privateExponentString);
//公鑰加密
byte[] encryptedBytes=encrypt(data.getBytes(), publicKey);
System.out.println("加密後:"+new String(encryptedBytes));
//私鑰解密
byte[] decryptedBytes=decrypt(encryptedBytes, privateKey);
System.out.println("解密後:"+new String(decryptedBytes));
}
//將base64編碼後的公鑰字串轉成PublicKey例項
public static PublicKey getPublicKey(String modulusStr, String exponentStr) throws Exception{
BigInteger modulus=new BigInteger(modulusStr);
BigInteger exponent=new BigInteger(exponentStr);
RSAPublicKeySpec publicKeySpec=new RSAPublicKeySpec(modulus, exponent);
KeyFactory keyFactory=KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(publicKeySpec);
}
//將base64編碼後的私鑰字串轉成PrivateKey例項
public static PrivateKey getPrivateKey(String modulusStr, String exponentStr) throws Exception{
BigInteger modulus=new BigInteger(modulusStr);
BigInteger exponent=new BigInteger(exponentStr);
RSAPrivateKeySpec privateKeySpec=new RSAPrivateKeySpec(modulus, exponent);
KeyFactory keyFactory=KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(privateKeySpec);
}
//公鑰加密
public static byte[] encrypt(byte[] content, PublicKey publicKey) throws Exception{
Cipher cipher=Cipher.getInstance("RSA");//java預設"RSA"="RSA/ECB/PKCS1Padding"
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(content);
}
//私鑰解密
public static byte[] decrypt(byte[] content, PrivateKey privateKey) throws Exception{
Cipher cipher=Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(content);
}
}
執行結果:
這裡三種方式總結起來也就是
1,.KeyPairGenerator獲取key;
2. String獲取key;
3. modulus和exponent獲取key。
--------------------後來,我發現,資料太長拋異常了,好吧---------------------------
然而,當加密的資料太長的時候則需要分組加密,不然資料過長會拋異常,如“Encryt data is too much”,或者“data length is longer than 127”等。
上面三個方法使用的key的n值(modulus)是1024bit的,也就是128byte,根據RSA加密規則,加密1 byte位元組的資料,需要12 byte,即其他11byte可能用於記錄其他資訊什麼的,這裡我就不清楚了,而1024bit長度的key則最多可以加密128-11=117byte的資料,所以,對於超過117byte的資料,我們需要以117byte為一組進行資料分割。
public class RSACryptography {
public static String data="hello world";
public static String modulusString="95701876885335270857822974167577168764621211406341574477817778908798408856077334510496515211568839843884498881589280440763139683446418982307428928523091367233376499779842840789220784202847513854967218444344438545354682865713417516385450114501727182277555013890267914809715178404671863643421619292274848317157";
public static String publicExponentString="65537";
public static String privateExponentString="15118200884902819158506511612629910252530988627643229329521452996670429328272100404155979400725883072214721713247384231857130859555987849975263007110480563992945828011871526769689381461965107692102011772019212674436519765580328720044447875477151172925640047963361834004267745612848169871802590337012858580097";
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//由n和e獲取公鑰
PublicKey publicKey=getPublicKey(modulusString, publicExponentString);
//由n和d獲取私鑰
PrivateKey privateKey=getPrivateKey(modulusString, privateExponentString);
//公鑰加密
String encrypted=encrypt(data, publicKey);
System.out.println("加密後:"+encrypted);
//私鑰解密
String decrypted=decrypt(encrypted, privateKey);
System.out.println("解密後:"+new String(decrypted));
}
//將base64編碼後的公鑰字串轉成PublicKey例項
public static PublicKey getPublicKey(String modulusStr, String exponentStr) throws Exception{
BigInteger modulus=new BigInteger(modulusStr);
BigInteger exponent=new BigInteger(exponentStr);
RSAPublicKeySpec publicKeySpec=new RSAPublicKeySpec(modulus, exponent);
KeyFactory keyFactory=KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(publicKeySpec);
}
//將base64編碼後的私鑰字串轉成PrivateKey例項
public static PrivateKey getPrivateKey(String modulusStr, String exponentStr) throws Exception{
BigInteger modulus=new BigInteger(modulusStr);
BigInteger exponent=new BigInteger(exponentStr);
RSAPrivateKeySpec privateKeySpec=new RSAPrivateKeySpec(modulus, exponent);
KeyFactory keyFactory=KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(privateKeySpec);
}
//公鑰加密,並轉換成十六進位制字串打印出來
public static String encrypt(String content, PublicKey publicKey) throws Exception{
Cipher cipher=Cipher.getInstance("RSA");//java預設"RSA"="RSA/ECB/PKCS1Padding"
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int splitLength=((RSAPublicKey)publicKey).getModulus().bitLength()/8-11;
byte[][] arrays=splitBytes(content.getBytes(), splitLength);
StringBuffer sb=new StringBuffer();
for(byte[] array : arrays){
sb.append(bytesToHexString(cipher.doFinal(array)));
}
return sb.toString();
}
//私鑰解密,並轉換成十六進位制字串打印出來
public static String decrypt(String content, PrivateKey privateKey) throws Exception{
Cipher cipher=Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int splitLength=((RSAPrivateKey)privateKey).getModulus().bitLength()/8;
byte[] contentBytes=hexString2Bytes(content);
byte[][] arrays=splitBytes(contentBytes, splitLength);
StringBuffer sb=new StringBuffer();
for(byte[] array : arrays){
sb.append(new String(cipher.doFinal(array)));
}
return sb.toString();
}
//拆分byte陣列
public static byte[][] splitBytes(byte[] bytes, int splitLength){
int x; //商,資料拆分的組數,餘數不為0時+1
int y; //餘數
y=bytes.length%splitLength;
if(y!=0){
x=bytes.length/splitLength+1;
}else{
x=bytes.length/splitLength;
}
byte[][] arrays=new byte[x][];
byte[] array;
for(int i=0; i<x; i++){
if(i==x-1 && bytes.length%splitLength!=0){
array=new byte[bytes.length%splitLength];
System.arraycopy(bytes, i*splitLength, array, 0, bytes.length%splitLength);
}else{
array=new byte[splitLength];
System.arraycopy(bytes, i*splitLength, array, 0, splitLength);
}
arrays[i]=array;
}
return arrays;
}
//byte陣列轉十六進位制字串
public static String bytesToHexString(byte[] bytes) {
StringBuffer sb = new StringBuffer(bytes.length);
String sTemp;
for (int i = 0; i < bytes.length; i++) {
sTemp = Integer.toHexString(0xFF & bytes[i]);
if (sTemp.length() < 2)
sb.append(0);
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
//十六進位制字串轉byte陣列
public static byte[] hexString2Bytes(String hex) {
int len = (hex.length() / 2);
hex=hex.toUpperCase();
byte[] result = new byte[len];
char[] achar = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
}
return result;
}
private static byte toByte(char c) {
byte b = (byte) "0123456789ABCDEF".indexOf(c);
return b;
}
}
執行結果就不貼出來了。
最後,有一點必須強調,因為中間磨了我不少時間。
中間加密後,如果要打印出來,必須以十六進位制或者BCD碼的形式列印,不能new String(byte[])後,再從這個String裡getbytes(),也不要用base64,不然會破壞原資料。
比如,舉個例子:
byte[ ] bytes=new byte[ ]{108, -56, 111, 34, -67};
byte[ ] newBytes=new String(bytes).getBytes();
StringBuffer sb=new StringBuffer();
for(int i=0; i<newBytes.length; i++){
sb.append(newBytes[i]+"|");
}
System.out.println(sb.toString());
將一個byte陣列new String後再getbytes出來後,看看執行結果:
最後一個byte由-67變為了63,這個務必注意啊~