NET應用——你的數據安全有必要升級


最近又被【現場破解共享單車系統】刷了一臉,不得不開始后怕:如何防止類似的情況發生?

想來想去,始終覺得將程序加密是最簡單的做法。但是摩拜、ofo也有加密,為什么仍然被破解?那是因為請求在傳輸過程中被篡改了關鍵參數,從而導致服務器做出了錯誤的響應。

如何防止篡改?自然是使用加密,加密方式的不同會決定篡改的成本(開頭故事里的哥們大概用了2天),目前大多數的加密方法是暴露其他參數,只加密一個token,然后服務端驗證token是否有效。從我的角度來講,這種加密方式雖然破解難度很高,但多半是用MD5、SHA1、hmacMd5或AES等方式進行加密,結果往往是不可逆的,並且固定內容加密——不管多少次結果總是相同的。

對於部分人來講,只要你暴露出來的數據就可以做很多事情了,而且如果使用這種加密手段,那么服務端和客戶端必須約定好加密手段和參與加密的字段(規則),這就相當於:我(服務端)和你(客戶端)一起設計了鑰匙,然后在需要的時候(發起請求)你就往指定地方(服務器)丟一個上了鎖的箱子(加密后的數據),然后我制作鑰匙打開鎖。我也可以丟箱子,然后你用同樣方式去打開。

大家可以打開的鎖安全么?想想都不靠譜。

更好的是什么?我們設想一下:我(服務端)給你(客戶端)制造鎖的圖紙,我有制造鑰匙的圖紙,在你需要的時候(發起請求)就做一把鎖,然后把貨物(原始數據)放在箱子里鎖好(加密完成)丟在指定的地方(服務器),然后我制作出鑰匙,開鎖拿走貨物(解密)。

現在感覺如何?就像情報機構,偵察員只負責按照規則傳遞密碼,而總部按照另一套規則解讀,這個過程中就算被監聽到,第三方也無法解讀——這種方式就是今天要說的RSA。

該怎么做呢?很簡單:基於現在基本都是用http交互數據,相互傳輸數據用json再好不過,所以我會要求客戶端將所有參數以json格式全部拼裝起來,然后整串加密后傳輸到服務器,請求內容看上去是這樣的:

 

 

完全不知道這是一串什么鬼(如果我不是服務器的話):實際上這是一次登陸請求,里面包含賬戶、密碼、時間戳、執行方法等,在服務端解密后是這樣的:

 

{"msg":{"method":"log_in","time":"2017-09-28 10:30:27","user_name":"test","user_pwd":"test"}}

 

這就完成了一次正確的數據提交。並且如果有人修改了密文都會導致服務端解密失敗從而駁回請求。這種情況下,就算你破譯了整套客戶端,也僅僅能拿到一個公鑰,而遠在服務端的私鑰還是好好的(除非你已經攻破了人家的服務器),從而阻止參數在傳遞過程中被篡改(下面上點干貨)。

 

生成公私鑰:

    /// <summary>
    /// RSA 的密鑰產生 產生私鑰 和公鑰 
    /// </summary>
    /// <param name="priKeys">私鑰</param>
    /// <param name="pubKey">公鑰</param>
    public static void rsaKey(out string priKeys, out string pubKey)
    {
        RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        priKeys = rsa.ToXmlString(true);
        pubKey = rsa.ToXmlString(false);
    }

 

RSA加密:

    /// <summary>
    /// RSA加密(不限長度)
    /// </summary>
    /// <param name="publicKey">公鑰</param>
    /// <param name="text">要加密的文本</param>
    /// <param name="enc">編碼方式</param>
    /// <returns>密文</returns>
    public static string rsaEncrypt(string pubKey, string text, Encoding enc)
    {
        using (var rsaProvider = new RSACryptoServiceProvider())
        {
            var inputBytes = enc.GetBytes(text);
            rsaProvider.FromXmlString(pubKey);
            int bufferSize = (rsaProvider.KeySize / 8) - 11;
            var buffer = new byte[bufferSize];
            using (MemoryStream inputStream = new MemoryStream(inputBytes), outputStream = new MemoryStream())
            {
                while (true)
                {
                    int readSize = inputStream.Read(buffer, 0, bufferSize);
                    if (readSize <= 0)
                    {
                        break;
                    }
                    var temp = new byte[readSize];
                    Array.Copy(buffer, 0, temp, 0, readSize);
                    var encryptedBytes = rsaProvider.Encrypt(temp, false);
                    outputStream.Write(encryptedBytes, 0, encryptedBytes.Length);
                }
                return Convert.ToBase64String(outputStream.ToArray());
            }
        }
    }

 

RSA解密:

    /// <summary>
    /// rsa解密
    /// </summary>
    /// <param name="priKey">私鑰</param>
    /// <param name="encrypted">要解密的字符串</param>
    /// <param name="enc">編碼方式</param>
    /// <returns>明文</returns>
    public static string rsaDecrypt(string priKey, string encrypted, Encoding enc)
    {
        if (string.IsNullOrEmpty(encrypted))
        {
            return string.Empty;
        }

        if (string.IsNullOrWhiteSpace(priKey))
        {
            throw new ArgumentException("Invalid Private Key");
        }

        using (var rsaProvider = new RSACryptoServiceProvider())
        {
            var inputBytes = Convert.FromBase64String(encrypted);
            rsaProvider.FromXmlString(priKey);
            int bufferSize = rsaProvider.KeySize / 8;
            var buffer = new byte[bufferSize];
            using (MemoryStream inputStream = new MemoryStream(inputBytes),
                 outputStream = new MemoryStream())
            {
                while (true)
                {
                    int readSize = inputStream.Read(buffer, 0, bufferSize);
                    if (readSize <= 0)
                    {
                        break;
                    }

                    var temp = new byte[readSize];
                    Array.Copy(buffer, 0, temp, 0, readSize);
                    var rawBytes = rsaProvider.Decrypt(temp, false);
                    outputStream.Write(rawBytes, 0, rawBytes.Length);
                }
                return enc.GetString(outputStream.ToArray());
            }
        }
    }

 

因為本人對js也有一些興趣,所以嘗試着利用js與服務器交互,然鵝發現js需要java格式的公私鑰,於是找到了轉換公私鑰的代碼,親測有效,在這里搬運下,利己利他:

    /// <summary>
    /// RSA公鑰格式轉換(net轉java)
    /// </summary>
    /// <param name="pubKey">net格式公鑰</param>
    /// <returns>java格式的公鑰</returns>
    public static string rsaPubKeyNet2Java(string pubKey)
    {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(pubKey);
        BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText));
        BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText));
        RsaKeyParameters pub = new RsaKeyParameters(false, m, p);
        SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pub);
        byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
        return Convert.ToBase64String(serializedPublicBytes);
    }

    /// <summary>
    /// RSA私鑰格式轉換(net轉java)
    /// </summary>
    /// <param name="priKey">net格式私鑰</param>
    /// <returns>java格式的私鑰</returns>
    public static string rsaPriKeyNet2Java(string priKey)
    {
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(priKey);
        BigInteger m = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Modulus")[0].InnerText));
        BigInteger exp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Exponent")[0].InnerText));
        BigInteger d = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("D")[0].InnerText));
        BigInteger p = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("P")[0].InnerText));
        BigInteger q = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("Q")[0].InnerText));
        BigInteger dp = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DP")[0].InnerText));
        BigInteger dq = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("DQ")[0].InnerText));
        BigInteger qinv = new BigInteger(1, Convert.FromBase64String(doc.DocumentElement.GetElementsByTagName("InverseQ")[0].InnerText));
        RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(m, exp, d, p, q, dp, dq, qinv);
        PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKeyParam);
        byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetEncoded();
        return Convert.ToBase64String(serializedPrivateBytes);
    }

 

進行轉換需要用到一個三方類庫,我的百度網盤里可以下載 http://pan.baidu.com/s/1b9Wy42,可以直接使用。

js的RSA加密也要用到一個庫,在這里捎帶貼一下:

    <script type="text/javascript" src="https://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
    <script>
        //實例化對象
        var crypt = new JSEncrypt();
        //設置私鑰
        crypt.setPrivateKey('你的RSA公鑰');

        $('#log_in').click(function () {
            //拼裝發送數據
            var post_data = crypt.encrypt('{"msg":{"method":"log_in","time":"' + Tool.getNowTime() + '","user_name":"' + $('.name').val() + '","user_pwd":"' + $('.pwd').val() + '"}}');
            //在這里發起你的請求
        });
    </script>

 

因為js可以被看到,所以你更需要RSA,因為全部被看到也無所謂了。希望對大家有幫助~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM