1. 程式人生 > >微信小程式開放資料解密 AES-128-CBC 解密(C#版本)

微信小程式開放資料解密 AES-128-CBC 解密(C#版本)

最近朋友在弄微信小程式開發,需要跟微信服務端互動,微信敏感資料都有加密返回,需要在服務端接收進行解密後再返回給客戶端小程式,今天就通過C# 進行資料的解密,以下展示是C# 程式碼
如果你使用的Java,請訪問這個地址(Java版本) https://blog.csdn.net/jasonsong2008/article/details/83588666
我們先來看一下微信官方的說明文件,以下直接文件來自微信小程式官方:

加密資料解密演算法

介面如果涉及敏感資料(如wx.getUserInfo當中的 openId 和 unionId),介面的明文內容將不包含這些敏感資料。開發者如需要獲取敏感資料,需要對介面返回的加密資料(encryptedData)

 進行對稱解密。 解密演算法如下:

  1. 對稱解密使用的演算法為 AES-128-CBC,資料採用PKCS#7填充。
  2. 對稱解密的目標密文為 Base64_Decode(encryptedData)。
  3. 對稱解密祕鑰 aeskey = Base64_Decode(session_key), aeskey 是16位元組。
  4. 對稱解密演算法初始向量 為Base64_Decode(iv),其中iv由資料介面返回。


通過微信文件的說明編寫了如下解密方法供大家使用


    /// <summary>
    /// AES加解密(C#版本)
    /// Add by 成長的小豬(Jason.Song) on 2018/10/26
    /// http://blog.csdn.net/jasonsong2008
    ///
    /// AES,高階加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學中又稱Rijndael加密法,
    /// 是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的DES,已經被多方分析且廣為全世界所使用。
    /// 嚴格地說,AES和Rijndael加密法並不完全一樣(雖然在實際應用中二者可以互換),
    /// 因為Rijndael加密法可以支援更大範圍的區塊和金鑰長度:AES的區塊長度固定為128 位元,
    /// 金鑰長度則可以是128,192或256位元;而Rijndael使用的金鑰和區塊長度可以是32位的整數倍,
    /// 以128位為下限,256位元為上限。包括AES-ECB,AES-CBC,AES-CTR,AES-OFB,AES-CFB
    /// </summary>
    public class AesHelper
    {
        #region 微信小程式 開放資料解密


        /// <summary>
        /// 微信小程式 開放資料解密
        /// AES解密(Base64)
        /// Add by 成長的小豬(Jason.Song) on 2018/10/26
        /// </summary>
        /// <param name="encryptedData">已加密的資料</param>
        /// <param name="sessionKey">解密金鑰</param>
        /// <param name="iv">IV偏移量</param>
        /// <returns></returns>
        public static string DecryptForWeChatApplet(string encryptedData, string sessionKey, string iv) {
            var decryptBytes = Convert.FromBase64String(encryptedData);
            var keyBytes = Convert.FromBase64String(sessionKey);
            var ivBytes = Convert.FromBase64String(iv);
            var outputBytes = DecryptByAesBytes(decryptBytes, keyBytes, ivBytes);
            return Encoding.UTF8.GetString(outputBytes);
        }

        #endregion

        #region AES加密

        /// <summary>
        /// AES加密
        /// Add by 成長的小豬(Jason.Song) on 2018/10/26
        /// </summary>
        /// <param name="encryptedBytes">待加密的位元組陣列</param>
        /// <param name="keyBytes">加密金鑰位元組陣列</param>
        /// <param name="ivBytes">IV初始化向量位元組陣列</param>
        /// <param name="cipher">運算模式</param>
        /// <param name="padding">填充模式</param>
        /// <returns></returns>
        public static byte[] EncryptToAesBytes(byte[] encryptedBytes, byte[] keyBytes, byte[] ivBytes,
            CipherMode cipher = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) {
            if (encryptedBytes == null || encryptedBytes.Length <= 0)
                throw new ArgumentNullException(nameof(encryptedBytes));
            if (keyBytes == null || keyBytes.Length <= 0)
                throw new ArgumentNullException(nameof(keyBytes));
            if (ivBytes == null || ivBytes.Length <= 0)
                throw new ArgumentNullException(nameof(ivBytes));

            var des = new AesCryptoServiceProvider {
                Key = keyBytes,
                IV = ivBytes,
                Mode = cipher,
                Padding = padding
            };
            var outputBytes = des.CreateEncryptor().TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
            return outputBytes;
        }

        #endregion

        #region AES解密

        /// <summary>
        /// AES解密
        /// Add by 成長的小豬(Jason.Song) on 2018/10/26
        /// </summary>
        /// <param name="decryptedBytes">待解密的位元組陣列</param>
        /// <param name="keyBytes">解密金鑰位元組陣列</param>
        /// <param name="ivBytes">IV初始化向量位元組陣列</param>
        /// <param name="cipher">運算模式</param>
        /// <param name="padding">填充模式</param>
        /// <returns></returns>
        public static byte[] DecryptByAesBytes(byte[] decryptedBytes, byte[] keyBytes, byte[] ivBytes,
            CipherMode cipher = CipherMode.CBC, PaddingMode padding = PaddingMode.PKCS7) {
            if (decryptedBytes == null || decryptedBytes.Length <= 0)
                throw new ArgumentNullException(nameof(decryptedBytes));
            if (keyBytes == null || keyBytes.Length <= 0)
                throw new ArgumentNullException(nameof(keyBytes));
            if (ivBytes == null || ivBytes.Length <= 0)
                throw new ArgumentNullException(nameof(ivBytes));

            var aes = new AesCryptoServiceProvider {
                Key = keyBytes,
                IV = ivBytes,
                Mode = cipher,
                Padding = padding
            };
            var outputBytes = aes.CreateDecryptor().TransformFinalBlock(decryptedBytes, 0, decryptedBytes.Length);
            return outputBytes;
        }

        #endregion
    }


下面是通過以上解密方法進行測試,大家根據自己的情況進行相應呼叫即可


    /// <summary>
    /// 文章來源於成長的小豬
    /// http://blog.csdn.net/jasonsong2008
    /// </summary>
    [TestFixture()]
    public class AesHelperTests
    {
        /// <summary>
        /// 微信小程式 開放資料校驗與解密 加密資料解密演算法
        /// Add by 成長的小豬(Jason.Song) on 2018/10/26
        /// </summary>
        [Test()]
        public void DecryptForWeChatAppletTest() {
            //微信小程式返回的加密資料
            const string encryptedData =
                "tsyLVebikY1aLQ0aNpg10NHxCTV2Ar+FJHUZdwIchBXFbJU7hXyf5gbDibaLU+lT6bzzut/nVymRFp/U8MrF0c8yOCFbnK5aevyearR7vopeel2y929weVA/s16shDPnRMkIn9xiMfVY3LDmuptnBpy1loZfSW2CPfXFuKXQf2z+Kiruynve1cq2mnzAadNaw3/g/tjHRPzxBnTkMsu8sQ==";
            //會話金鑰
            const string sessionKey = "vBwBswWRVmD0WQvRbdbMZg==";
            //解密演算法初始向量
            const string iv = "8IzE0WUF0j5hXy4oIKuLHA==";

            var result = AesHelper.DecryptForWeChatApplet(encryptedData, sessionKey, iv);
            Console.Write(result);
        }
    }