2010/07/28

[C# & Java] .Net 加密 (DESCryptoServiceProvider) VS. Java 解密

前言: 這邊只列出我工作上有遇到的情況, 並未對所有的加解密做很完整的舉例/說明.
        不過如果有人能提供其他情況, 我是可以試著去解看看.
情境: 在 .Net 中, 利用 DESCryptoServiceProvider 進行加密. 加密的 Mode 為 ECB, Padding 為 None, Zeros 與 PKCS7.

Java 用到的 import 如下:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import org.apache.commons.codec.binary.Base64;
.Net 加密的程式如下: PaddingMode.None
//建立一個Mode=ECB, Padding=None, Key為12345678的DESCryptoServiceProvider
DESCryptoServiceProvider objDESCryptoServiceProvider = new DESCryptoServiceProvider();
//Key的長度要64bits -> 8bytes;用ASCII編碼將Key轉為byte[]
objDESCryptoServiceProvider.Key = Encoding.ASCII.GetBytes("12345678");
objDESCryptoServiceProvider.Mode = CipherMode.ECB;
objDESCryptoServiceProvider.Padding = PaddingMode.None;

//用UTF-8編碼, 將字串轉為byte[]
byte[] bysData = Encoding.UTF8.GetBytes("我是一個PaddingMode.None的測試字串!");
//因為PaddingMode.None的關係, byte[]的長度要是8的倍數
byte[] bysFixSizeData = new byte[(int)Math.Ceiling(bysData.Length / 8.0) * 8];
//將資料複製到長度為8的倍數的byte[]
Array.Copy(bysData, bysFixSizeData, bysData.Length);

//進行加密
byte[] bysEncrypted = objDESCryptoServiceProvider.CreateEncryptor()
  .TransformFinalBlock(bysFixSizeData, 0, bysFixSizeData.Length);
//將byte[]轉為Base64的字串
Console.WriteLine(Convert.ToBase64String(bysEncrypted));
//輸出: xnygZZ+WkN4pmDVDjBJ41o9LePFibMJkfvLkf9phS9mW2tbtS6JcMSiwX2N1KbGp
Java 解密的程式如下:: 使用 org.apache.commons.codec.binary.Base64 這個類別做 Base64 字串的解碼.
try {
  //解密的Key
  String strKey = "12345678";
  //已加密的Base64字串
  String strEncrypted = "xnygZZ+WkN4pmDVDjBJ41o9LePFibMJkfvLkf9phS9mW2tbtS6JcMSiwX2N1KbGp";

  //先將Base64字串轉碼為byte[]
  Base64 objBase64 = new Base64();
  byte[] bysDecoded = objBase64.decode(strEncrypted.getBytes());

  //建立解密所需的Key. 因為加密時的key是用ASCII轉換, 所以這邊也用ASCII做
  DESKeySpec objDesKeySpec = new DESKeySpec(strKey.getBytes("ASCII"));
  SecretKeyFactory objKeyFactory = SecretKeyFactory.getInstance("DES");
  SecretKey objSecretKey = objKeyFactory.generateSecret(objDesKeySpec);

  //設定一個DES/ECB/NoPadding的Cipher
  //ECB對應到.Net的CipherMode.ECB
  //NoPadding對應到.Net的PaddingMode.None
  Cipher objCipher = Cipher.getInstance("DES/ECB/NoPadding");
  //設定為解密模式, 並設定解密的key
  objCipher.init(Cipher.DECRYPT_MODE, objSecretKey);

  //輸出解密後的字串. 因為加密是用UTF-8將字串轉為byte[], 所以這邊要用UTF-8轉回去
  //注意後面會多一些空字元. 因為加密前有因為資料長度的關係補上一些空的bytes
  //這邊用String的trim()將這些空字元刪掉
  String strDecrypted = new String(objCipher.doFinal(bysDecoded), "utf-8").trim();
  System.out.println("[" + strDecrypted + "]");
  //輸出:[我是一個PaddingMode.None的測試字串!]
} catch (Exception e) {
  e.printStackTrace(System.out);
}
接下來測試一下 .Net 中的 PaddingMode.Zeros:
//建立一個Mode=ECB, Padding=None, Key為12345678的DESCryptoServiceProvider
DESCryptoServiceProvider objDESCryptoServiceProvider = new DESCryptoServiceProvider();
//Key的長度要64bits -> 8bytes
objDESCryptoServiceProvider.Key = Encoding.ASCII.GetBytes("12345678");
objDESCryptoServiceProvider.Mode = CipherMode.ECB;
objDESCryptoServiceProvider.Padding = PaddingMode.Zeros;

//用UTF-8編碼, 將字串轉為byte[]
//因為PaddingMode.Zeros的關係, 不用像上一個Sample一樣, 另外弄一個byte[]去將資料補到8的倍數
byte[] bysData = Encoding.UTF8.GetBytes("我是一個PaddingMode.Zeros的測試字串!");

//進行加密
byte[] bysEncrypted = objDESCryptoServiceProvider.CreateEncryptor()
  .TransformFinalBlock(bysData, 0, bysData.Length);
//將byte[]轉為Base64的字串
Console.WriteLine(Convert.ToBase64String(bysEncrypted));
//輸出: xnygZZ+WkN4pmDVDjBJ41o9LePFibMJkYe6Sq5Y3mpq2JD91DLtxC/66itziI0rD
Java 的程式不用改變, 維持原狀即可.
try {
  //解密的Key
  String strKey = "12345678";
  //已加密的Base64字串
  String strEncrypted = "xnygZZ+WkN4pmDVDjBJ41o9LePFibMJkfvLkf9phS9mW2tbtS6JcMSiwX2N1KbGp";

  //先將Base64字串轉碼為byte[]
  Base64 objBase64 = new Base64();
  byte[] bysDecoded = objBase64.decode(strEncrypted.getBytes());

  //建立解密所需的Key. 因為加密時的key是用ASCII轉換, 所以這邊也用ASCII做
  DESKeySpec objDesKeySpec = new DESKeySpec(strKey.getBytes("ASCII"));
  SecretKeyFactory objKeyFactory = SecretKeyFactory.getInstance("DES");
  SecretKey objSecretKey = objKeyFactory.generateSecret(objDesKeySpec);

  //設定一個DES/ECB/NoPadding的Cipher
  //ECB對應到.Net的CipherMode.ECB
  //NoPadding對應到.Net的PaddingMode.None
  Cipher objCipher = Cipher.getInstance("DES/ECB/NoPadding");
  //設定為解密模式, 並設定解密的key
  objCipher.init(Cipher.DECRYPT_MODE, objSecretKey);

  //輸出解密後的字串. 因為加密是用UTF-8將字串轉為byte[], 所以這邊要用UTF-8轉回去
  //注意後面會多一些空字元. 因為加密前有因為資料長度的關係補上一些空的bytes
  //這邊用String的trim()將這些空字元刪掉
  String strDecrypted = new String(objCipher.doFinal(bysDecoded), "utf-8").trim();
  System.out.println("[" + strDecrypted + "]");
  //輸出:[我是一個PaddingMode.None的測試字串!]
} catch (Exception e) {
  e.printStackTrace(System.out);
}
最後測試 .Net 中的 PaddingMode.PKCS7:
//建立一個Mode=ECB, Padding=None, Key為12345678的DESCryptoServiceProvider
DESCryptoServiceProvider objDESCryptoServiceProvider = new DESCryptoServiceProvider();
//Key的長度要64bits -> 8bytes
objDESCryptoServiceProvider.Key = Encoding.ASCII.GetBytes("12345678");
objDESCryptoServiceProvider.Mode = CipherMode.ECB;
objDESCryptoServiceProvider.Padding = PaddingMode.PKCS7;

//用UTF-8編碼, 將字串轉為byte[]
//這邊也不用另外做補資料的動作
byte[] bysData = Encoding.UTF8.GetBytes("我是一個PaddingMode.PKCS7的測試字串!");

//進行加密
byte[] bysEncrypted = objDESCryptoServiceProvider.CreateEncryptor()
  .TransformFinalBlock(bysData, 0, bysData.Length);
//將byte[]轉為Base64的字串
Console.WriteLine(Convert.ToBase64String(bysEncrypted));
//輸出: xnygZZ+WkN4pmDVDjBJ41o9LePFibMJkt86hRLUM4/m2JD91DLtxC5+8Tqc7iB2f
Java 程式的差別就在於把 DES / ECB / NoPadding 改為 DES / ECB / PKCS5Padding.
try {
  //解密的Key
  String strKey = "12345678";
  //已加密的Base64字串
  String strEncrypted = "xnygZZ+WkN4pmDVDjBJ41o9LePFibMJkt86hRLUM4/m2JD91DLtxC5+8Tqc7iB2f";
  
  //先將Base64字串轉碼為byte[]
  Base64 objBase64 = new Base64();
  byte[] bysDecoded = objBase64.decode(strEncrypted.getBytes());
  
  //建立解密所需的Key. 因為加密時的key是用ASCII轉換, 所以這邊也用ASCII做
  DESKeySpec objDesKeySpec = new DESKeySpec(strKey.getBytes("ASCII"));
  SecretKeyFactory objKeyFactory = SecretKeyFactory.getInstance("DES");
  SecretKey objSecretKey = objKeyFactory.generateSecret(objDesKeySpec);

  //設定一個DES/ECB/PKCS5Padding的Cipher
  //ECB對應到.Net的CipherMode.ECB
  //用PKCS5Padding對應到.Net的PaddingMode.PKCS7
  Cipher objCipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
  //設定為解密模式, 並設定解密的key
  objCipher.init(Cipher.DECRYPT_MODE, objSecretKey);
  
  //輸出解密後的字串. 因為加密時指定PaddingMode.PKCS7, 所以可以不用處理空字元
  //不過若想保險點, 也是可以用trim()去處理過一遍
  String strDecrypted = new String(objCipher.doFinal(bysDecoded), "utf-8").trim();
  System.out.println("[" + strDecrypted + "]");
  //輸出:[我是一個PaddingMode.PKCS7的測試字串!]
} catch (Exception e) {
  e.printStackTrace(System.out);
}
因為目前只有這些加解密的需求, 所以就沒去測其他的.
也許以後有用到其他的, 就再補上.

沒有留言: