探討NET Core資料進行3DES加密或解密弱金鑰問題
前言
之前寫過一篇《探討.NET Core資料進行3DES加密和解密問題》,最近看到有人提出弱金鑰問題,換個強金鑰不就完了嗎,猜測可能是與第三方對接導致很無奈不能更換金鑰,所以產生本文解決.NET Core中3DES弱金鑰問題,寫下本文,希望對碰到此問題的童鞋有所幫助。
3DES加密或解密弱金鑰
在基於.NET Framework中,我們可以使用反射獲取到TripleDESCryptoServiceProvider的“_NewEncryptor”私有方法,從而規避判斷弱祕鑰問題,但在.NET Core中沒有這個方法,我們首先來看看問題的產生,如下為.NET Core中加密和解密的方法實現
public static string DesEncrypt(string input, string key)
{
byte[] inputArray = Encoding.UTF8.GetBytes(input);
var tripleDES = TripleDES.Create();
var byteKey = Encoding.UTF8.GetBytes(key);
byte[] allKey = new byte[];
Buffer.BlockCopy(byteKey, , allKey, , );
Buffer.BlockCopy(byteKey, , allKey, , );tripleDES.Key = allKey;
tripleDES.Mode = CipherMode.ECB;
tripleDES.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tripleDES.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(inputArray, , inputArray.Length);
return Convert.ToBase64String(resultArray, , resultArray.Length);} public static string DesDecrypt(string input, string key)
{
byte[] inputArray = Convert.FromBase64String(input);
var tripleDES = TripleDES.Create();
var byteKey = Encoding.UTF8.GetBytes(key);
byte[] allKey = new byte[];
Buffer.BlockCopy(byteKey, , allKey, , );
Buffer.BlockCopy(byteKey, , allKey, , );
tripleDES.Key = byteKey;
tripleDES.Mode = CipherMode.ECB;
tripleDES.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = tripleDES.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(inputArray, , inputArray.Length);
return Encoding.UTF8.GetString(resultArray);
}
接下來我們呼叫上述加密方法對資料進行加密,當然這裡的金鑰很簡單為16位1,NET Framework中對弱金鑰的具體判斷邏輯這裡不做深入分析,如下:
var desEncryptData = DesEncrypt("Jeffcky", "");
為解決這個問題我們下載BouncyCastle.NetCore包(https://github.com/chrishaly/bc-csharp),此包有針對基本所有加密演演算法實現,你會發現通過該包實現和Java中加密演演算法實現非常相似,若與第三方Java對接,對方所傳資料可能利用.NET Core無法解密或通過加密導致對方無法解密,因為無論是C#還是Java對於演演算法的實現還是有所差異,利用此包可以進行互操作。
在C#中3DES名稱定義為TripleDES,而在Java中名稱則是DESede,同時C#中的填充模式PKCS7對應Java中的PKCS5Padding,接下來你將看到如下C#程式碼幾乎就是從Java中翻譯過來,如下:
static IBufferedCipher CreateCipher(bool forEncryption, string key,
string cipMode = "DESede/ECB/PKCS5Padding")
{
var algorithmName = cipMode;
if (cipMode.IndexOf('/') >= )
{
algorithmName = cipMode.Substring(, cipMode.IndexOf('/'));
} var cipher = CipherUtilities.GetCipher(cipMode); var keyBytes = Encoding.UTF8.GetBytes(key); var keyParameter = ParameterUtilities.CreateKeyParameter(algorithmName, keyBytes); cipher.Init(forEncryption, keyParameter); return cipher;
}
如上主要是建立加密演演算法介面(預設為3DES),若forEncryption為true表示加密,否則解密,具體細節這裡就不再詳細解釋,有興趣的童鞋可自行研究。接下來我們實現加密和解密方法:
static string EncryptData(string input, string key)
{
var inCipher = CreateCipher(true, key); var inputArray = Encoding.UTF8.GetBytes(input); byte[] cipherData = inCipher.DoFinal(inputArray); return Convert.ToBase64String(cipherData);
} static string DecryptData(string input, string key)
{
var inputArrary = Convert.FromBase64String(input); var outCipher = CreateCipher(false, key); var encryptedDataStream = new MemoryStream(inputArrary, false); var dataStream = new MemoryStream(); var outCipherStream = new CipherStream(dataStream, null, outCipher); int ch;
while ((ch = encryptedDataStream.ReadByte()) >= )
{
outCipherStream.WriteByte((byte)ch);
} outCipherStream.Close();
encryptedDataStream.Close(); var dataBytes = dataStream.ToArray(); return Encoding.UTF8.GetString(dataBytes);
}
雖然金鑰是16位,但在內建具體實現時也會如.NET Core中一樣填充到24位,接下來我們再來呼叫上述加密和解密方法,看看資料加密和解密是否正確
var data = EncryptData("Jeffcky", ""); var decryptData = DecryptData(data, "");
那麼問題來了,為何在C#中會丟擲弱金鑰異常,但是在這個包中卻沒能丟擲異常呢?內建是基於Schneier pp281的弱和半弱鍵表進行查詢可能與C#實現邏輯有所不同(個人猜測),如下:
public const int DesKeyLength = ; private const int N_DES_WEAK_KEYS = ; //基於Schneier pp281的弱和半弱鍵表
private static readonly byte[] DES_weak_keys =
{
/* 弱鍵 */
(byte)0x01,(byte)0x01,(byte)0x01,(byte)0x01, (byte)0x01,(byte)0x01,(byte)0x01,(byte)0x01,
(byte)0x1f,(byte)0x1f,(byte)0x1f,(byte)0x1f, (byte)0x0e,(byte)0x0e,(byte)0x0e,(byte)0x0e,
(byte)0xe0,(byte)0xe0,(byte)0xe0,(byte)0xe0, (byte)0xf1,(byte)0xf1,(byte)0xf1,(byte)0xf1,
(byte)0xfe,(byte)0xfe,(byte)0xfe,(byte)0xfe, (byte)0xfe,(byte)0xfe,(byte)0xfe,(byte)0xfe, /* 半弱鍵 */
(byte)0x01,(byte)0xfe,(byte)0x01,(byte)0xfe, (byte)0x01,(byte)0xfe,(byte)0x01,(byte)0xfe,
(byte)0x1f,(byte)0xe0,(byte)0x1f,(byte)0xe0, (byte)0x0e,(byte)0xf1,(byte)0x0e,(byte)0xf1,
(byte)0x01,(byte)0xe0,(byte)0x01,(byte)0xe0, (byte)0x01,(byte)0xf1,(byte)0x01,(byte)0xf1,
(byte)0x1f,(byte)0xfe,(byte)0x1f,(byte)0xfe, (byte)0x0e,(byte)0xfe,(byte)0x0e,(byte)0xfe,
(byte)0x01,(byte)0x1f,(byte)0x01,(byte)0x1f, (byte)0x01,(byte)0x0e,(byte)0x01,(byte)0x0e,
(byte)0xe0,(byte)0xfe,(byte)0xe0,(byte)0xfe, (byte)0xf1,(byte)0xfe,(byte)0xf1,(byte)0xfe,
(byte)0xfe,(byte)0x01,(byte)0xfe,(byte)0x01, (byte)0xfe,(byte)0x01,(byte)0xfe,(byte)0x01,
(byte)0xe0,(byte)0x1f,(byte)0xe0,(byte)0x1f, (byte)0xf1,(byte)0x0e,(byte)0xf1,(byte)0x0e,
(byte)0xe0,(byte)0x01,(byte)0xe0,(byte)0x01, (byte)0xf1,(byte)0x01,(byte)0xf1,(byte)0x01,
(byte)0xfe,(byte)0x1f,(byte)0xfe,(byte)0x1f, (byte)0xfe,(byte)0x0e,(byte)0xfe,(byte)0x0e,
(byte)0x1f,(byte)0x01,(byte)0x1f,(byte)0x01, (byte)0x0e,(byte)0x01,(byte)0x0e,(byte)0x01,
(byte)0xfe,(byte)0xe0,(byte)0xfe,(byte)0xe0, (byte)0xfe,(byte)0xf1,(byte)0xfe,(byte)0xf1
}; public static bool IsWeakKey(byte[] key, int offset)
{
if (key.Length - offset < DesKeyLength)
throw new ArgumentException("key material too short."); //nextkey:
for (int i = ; i < N_DES_WEAK_KEYS; i++)
{
bool unmatch = false;
for (int j = ; j < DesKeyLength; j++)
{
if (key[j + offset] != DES_weak_keys[i * DesKeyLength + j])
{
//continue nextkey;
unmatch = true;
break;
}
} if (!unmatch)
{
return true;
}
} return false;
}
如果第三方為Java,當利用.NET Core實在走投無路無法進行解密時,那就使用上述提供的解密方法進行解密,理論上都可以解密,不能解密的情況大多出現於對C#和Java實現原理不瞭解導致,如下:
總結
本文重點在於解決.NET Core中3DES弱金鑰問題,同時和第三方對接時實在懶得去理解各語言實現加密演演算法原理,可嘗試採用上述包來進行互操作,看到有幾位童鞋在文章下提出這個問題而苦於沒找到解決方案,這裡提供一種可選擇的方案,都已封裝好,拿去用吧。