問題
為了安全性起見,客戶要求客戶端必須將數據加密后才能傳給服務端。
起先,准備使用非對稱加密(RSA)方式,但是發現它對原始文本長度有限制。
而對稱加密(AES)沒有長度限制,但是使用固定密鑰存在暴露的風險。
有沒有兩全其美的辦法呢?
思路
密鑰肯定每個用戶不同,而要驗證用戶則必須登錄。
因此,唯一可以安全獲取密鑰的時機,只能是在登錄時。
而為了保證用戶名密碼傳輸安全,可以使用RSA公鑰加密后傳輸,所有客戶端使用同一公鑰也沒問題。
登錄成功后,服務端將生成token和AES密鑰返回給客戶端。但是,返回的AES密鑰是經過加密的,而加密密鑰則是“用戶名+密碼”。
這樣保證了,只有剛才成功登錄的客戶端才能解密出AES密鑰。
以后的傳輸,全部使用AES加密,服務端可以根據token從緩存獲取AES密鑰解密。
整體流程如下圖:
服務端實現
下面是示例服務端的實現代碼:
[HttpPost("Login")] public LoginOutput Login(LoginInput input) { var userName = RsaHelper.Decrypt(input.UserName, privateKey); var password = RsaHelper.Decrypt(input.Password, privateKey); (byte[] tmpKey, byte[] tmpIV) = AesHelper.CreateKeyIV(userName + password, password + userName); var token = Guid.NewGuid().ToString("N"); (byte[] key, byte[] iv) = AesHelper.CreateKeyIV(); _cache.Add(token, (key, iv)); return new LoginOutput { Token = token, Key = AesHelper.Encrypt(Convert.ToBase64String(key), tmpKey, tmpIV), IV = AesHelper.Encrypt(Convert.ToBase64String(iv), tmpKey, tmpIV) }; } [HttpPost("TestMethod")] public string TestMethod([FromQuery]string token, [FromBody]string cipherText) { (byte[] key, byte[] iv) = _cache[token]; return AesHelper.Decrypt(cipherText, key, iv); }
Login
用於驗證用戶密碼並返回token和AES密鑰.
TestMethod
用於演示接收客戶端數據如何解密,為了演示方便,直接在URL傳遞token。
客戶端實現
使用xunit測試項目演示客戶端操作,代碼如下:
[Fact] public async void Test1() { //登錄獲得AES密鑰 var response = await _httpClient.PostAsync( "/Demo/Login", JsonContent.Create(new WebApplication1.LoginInput{ UserName = RsaHelper.Encrypt(userName, publicKey), Password = RsaHelper.Encrypt(password, publicKey) })); var loginResult = await response.Content.ReadFromJsonAsync<WebApplication1.LoginOutput>(); (byte[] tmpKey, byte[] tmpIV) = AesHelper.CreateKeyIV(userName + password, password + userName); byte[] key =Convert.FromBase64String(AesHelper.Decrypt(loginResult.Key, tmpKey, tmpIV)); byte[] iv = Convert.FromBase64String(AesHelper.Decrypt(loginResult.IV, tmpKey, tmpIV)); //使用AES密鑰加密 var cipherText = AesHelper.Encrypt(PlainText, key, iv); _output.WriteLine(cipherText); response = await _httpClient.PostAsync("/Demo/TestMethod?token=" + loginResult.Token, JsonContent.Create(cipherText)); var decryptResult = await response.Content.ReadAsStringAsync(); _output.WriteLine(decryptResult); Assert.Equal(PlainText, decryptResult); }
將大量數據(千字文)加密后傳給服務。
可以看到,返回了正確的原始數
結論
通過同時使用RSA+AES,保證了密鑰和數據的安全性。