使用System.Security.Cryptography加密速度过慢的优化咨询
加密操作导致性能瓶颈的优化方案
问题背景
实际业务应用运行缓慢,排查后确认是加密操作耗时过长。为复现问题,制作了一个仅含按钮的简单表单,加密Northwind数据库Orders表中的数据:
- 本地环境下加密830条记录耗时约20秒,移除加密后耗时不足1秒
- 尝试将加密函数嵌入表单而非调用外部类,速度无改善
- 实际场景虽无需加密830次,但性能差异明显,且必须完成加密操作
原表单代码
Dim cTools As New cTools Dim objConn As New SqlConnection(ConfigurationManager.ConnectionStrings("conNorth").ToString) objConn.Open() Dim s As String = "" Dim sSQL As String = "SELECT OrderID FROM Orders ORDER BY OrderID" Dim objCmd As New SqlCommand(sSQL, objConn) objCmd.CommandType = CommandType.Text Dim objRdr As SqlDataReader objRdr = objCmd.ExecuteReader Dim dtStart As Date = System.DateTime.Now Response.Write("<br>Before With Encrypt " & System.DateTime.Now & "<br>") Do While objRdr.Read() s = cTools.Encrypt(objRdr("OrderID")) Loop Dim dtEnd As Date = System.DateTime.Now Response.Write("After With Encrypt " & System.DateTime.Now & "<br>") Response.Write("Duration With Encrypt " & DateDiff(DateInterval.Second, dtStart, dtEnd) & "<br>") objRdr.Close() objConn.Close() objConn.Dispose()
原加密类代码
Imports System.IO Imports System.Security.Cryptography Public Class cTools Public Function Encrypt(clearText As String) As String Dim EncryptionKey As String = "MAKV2SPBNI99212" Dim clearBytes As Byte() = Encoding.Unicode.GetBytes(clearText) Using encryptor As Aes = Aes.Create() Dim pdb As New Rfc2898DeriveBytes(EncryptionKey, New Byte() {&H49, &H76, &H61, &H6E, &H20, &H4D, &H65, &H64, &H76, &H65, &H64, &H65, &H76}) encryptor.Key = pdb.GetBytes(32) encryptor.IV = pdb.GetBytes(16) Using ms As New MemoryStream() Using cs As New CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write) cs.Write(clearBytes, 0, clearBytes.Length) cs.Close() End Using clearText = Convert.ToBase64String(ms.ToArray()) End Using End Using Return clearText End Function Public Function Decrypt(cipherText As String) As String Dim EncryptionKey As String = "MAKV2SPBNI99212" cipherText = cipherText.Replace(" ", "+") Dim cipherBytes As Byte() = Convert.FromBase64String(cipherText) Using encryptor As Aes = Aes.Create() Dim pdb As New Rfc2898DeriveBytes(EncryptionKey, New Byte() {&H49, &H76, &H61, &H6E, &H20, &H4D, &H65, &H64, &H76, &H65, &H64, &H65, &H76}) encryptor.Key = pdb.GetBytes(32) encryptor.IV = pdb.GetBytes(16) Using ms As New MemoryStream() Using cs As New CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write) cs.Write(cipherBytes, 0, cipherBytes.Length) cs.Close() End Using cipherText = Encoding.Unicode.GetString(ms.ToArray()) End Using End Using Return cipherText End Function End Class
优化方案
1. 预计算密钥和IV,避免重复推导
Rfc2898DeriveBytes(PBKDF2)是计算密集型操作,每次加密都重新推导密钥和IV是核心性能浪费点。可以在类初始化时一次性计算完成,后续加密直接复用:
修改后的cTools类:
Imports System.IO Imports System.Security.Cryptography Public Class cTools Private ReadOnly _aesKey As Byte() Private ReadOnly _aesIV As Byte() Private Const EncryptionKey As String = "MAKV2SPBNI99212" Private ReadOnly _salt As Byte() = New Byte() {&H49, &H76, &H61, &H6E, &H20, &H4D, &H65, &H64, &H76, &H65, &H64, &H65, &H76} Public Sub New() ' 初始化时一次性推导密钥和IV Dim pdb As New Rfc2898DeriveBytes(EncryptionKey, _salt) _aesKey = pdb.GetBytes(32) _aesIV = pdb.GetBytes(16) End Sub Public Function Encrypt(clearText As String) As String Dim clearBytes As Byte() = Encoding.Unicode.GetBytes(clearText) Using encryptor As Aes = Aes.Create() encryptor.Key = _aesKey encryptor.IV = _aesIV Using ms As New MemoryStream() Using cs As New CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write) cs.Write(clearBytes, 0, clearBytes.Length) cs.FlushFinalBlock() ' 替代Close,更安全且性能更优 End Using clearText = Convert.ToBase64String(ms.ToArray()) End Using End Using Return clearText End Function Public Function Decrypt(cipherText As String) As String cipherText = cipherText.Replace(" ", "+") Dim cipherBytes As Byte() = Convert.FromBase64String(cipherText) Using encryptor As Aes = Aes.Create() encryptor.Key = _aesKey encryptor.IV = _aesIV Using ms As New MemoryStream() Using cs As New CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write) cs.Write(cipherBytes, 0, cipherBytes.Length) cs.FlushFinalBlock() End Using cipherText = Encoding.Unicode.GetString(ms.ToArray()) End Using End Using Return cipherText End Function End Class
2. 复用AES实例(单线程场景可选)
单线程环境下,可以复用AES实例减少对象创建开销,进一步提升性能:
' 在cTools类中添加 Private ReadOnly _aes As Aes Public Sub New() Dim pdb As New Rfc2898DeriveBytes(EncryptionKey, _salt) _aesKey = pdb.GetBytes(32) _aesIV = pdb.GetBytes(16) _aes = Aes.Create() _aes.Key = _aesKey _aes.IV = _aesIV End Sub ' 修改Encrypt方法,直接复用_aes实例 Public Function Encrypt(clearText As String) As String Dim clearBytes As Byte() = Encoding.Unicode.GetBytes(clearText) Using ms As New MemoryStream() Using cs As New CryptoStream(ms, _aes.CreateEncryptor(), CryptoStreamMode.Write) cs.Write(clearBytes, 0, clearBytes.Length) cs.FlushFinalBlock() End Using clearText = Convert.ToBase64String(ms.ToArray()) End Using Return clearText End Function
注意:Aes实例不是线程安全的,多线程场景下不要复用,保持每个方法创建实例+预计算密钥的方案即可。
3. 批量读取数据优化流程
原代码逐条读取数据库记录,可一次性将所有数据加载到内存后再加密,减少数据库连接持有时间:
修改后的表单代码:
Dim cTools As New cTools Dim objConn As New SqlConnection(ConfigurationManager.ConnectionStrings("conNorth").ToString) objConn.Open() Dim sSQL As String = "SELECT OrderID FROM Orders ORDER BY OrderID" Dim objCmd As New SqlCommand(sSQL, objConn) objCmd.CommandType = CommandType.Text ' 一次性读取所有数据到DataTable Dim dt As New DataTable() dt.Load(objCmd.ExecuteReader()) Dim dtStart As Date = System.DateTime.Now Response.Write("<br>Before With Encrypt " & System.DateTime.Now & "<br>") ' 批量加密 For Each row As DataRow In dt.Rows Dim s = cTools.Encrypt(row("OrderID").ToString()) Next Dim dtEnd As Date = System.DateTime.Now Response.Write("After With Encrypt " & System.DateTime.Now & "<br>") Response.Write("Duration With Encrypt " & DateDiff(DateInterval.Second, dtStart, dtEnd) & "<br>") objConn.Close() objConn.Dispose()
4. 异步并行加密(Web场景适用)
Web应用中可使用异步+并行加密,避免阻塞主线程,提升用户体验:
' 按钮点击事件改为异步处理 Async Sub YourButton_Click(sender As Object, e As EventArgs) Handles YourButton.Click Dim cTools As New cTools Dim objConn As New SqlConnection(ConfigurationManager.ConnectionStrings("conNorth").ToString) Await objConn.OpenAsync() Dim sSQL As String = "SELECT OrderID FROM Orders ORDER BY OrderID" Dim objCmd As New SqlCommand(sSQL, objConn) objCmd.CommandType = CommandType.Text Dim dt As New DataTable() dt.Load(Await objCmd.ExecuteReaderAsync()) Dim dtStart As Date = System.DateTime.Now Response.Write("<br>Before With Encrypt " & System.DateTime.Now & "<br>") ' 并行加密(预计算密钥的方案线程安全,可放心使用) Await Task.WhenAll(dt.AsEnumerable().Select(Async row => Dim s = cTools.Encrypt(row("OrderID").ToString()) ' 此处可处理加密后的数据(如写入数据库) )) Dim dtEnd As Date = System.DateTime.Now Response.Write("After With Encrypt " & System.DateTime.Now & "<br>") Response.Write("Duration With Encrypt " & DateDiff(DateInterval.Second, dtStart, dtEnd) & "<br>") objConn.Close() objConn.Dispose() End Sub
内容的提问来源于stack exchange,提问作者Corobori
相关产品推荐
相关产品推荐

