在?.NET?中使用?FixedTimeEquals?應(yīng)對(duì)計(jì)時(shí)攻擊的例子
計(jì)時(shí)攻擊
在計(jì)算機(jī)安全中,計(jì)時(shí)攻擊(Timing attack)是旁道攻擊 (Side-channel attack) 的一種,而旁道攻擊是根據(jù)計(jì)算機(jī)處理過(guò)程發(fā)出的信息進(jìn)行分析,包括耗時(shí),聲音,功耗等等,這和一般的暴力破解或者利用加密算法本身的弱點(diǎn)進(jìn)行攻擊是不一樣的。
舉個(gè)例子
假如您有一個(gè)后端 webapi, GetConfig 接口用來(lái)獲取配置信息,調(diào)用時(shí)需要在 Header 中傳入一個(gè)秘鑰,然后判斷是否正確并進(jìn)行返回,如下
X-Api-Key: x123
[HttpGet] public IActionResult GetConfig() { var key = Request.Headers["X-Api-Key"].FirstOrDefault(); if (key != "x123") { return Unauthorized(); } return Ok(configuration); }
注意,這里我們?yōu)榱伺袛鄡蓚€(gè)字符串相等,通常會(huì)使用 == 或者 != , 實(shí)際上背后使用了 String 的 Equals() 方法,如下
// Determines whether two Strings match. public static bool Equals(string? a, string? b) { if (object.ReferenceEquals(a, b)) { return true; } if (a is null || b is null || a.Length != b.Length) { return false; } return EqualsHelper(a, b); }
而內(nèi)部又使用了 SequenceEqual() 方法
[MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool EqualsHelper(string strA, string strB) { Debug.Assert(strA != null); Debug.Assert(strB != null); Debug.Assert(strA.Length == strB.Length); return SpanHelpers.SequenceEqual( ref Unsafe.As<char, byte>(ref strA.GetRawStringData()), ref Unsafe.As<char, byte>(ref strB.GetRawStringData()), ((uint)strA.Length) * sizeof(char)); }
大概的邏輯是,先判斷兩個(gè)字符串長(zhǎng)度是否一致,如果不是,直接返回 false,然后循環(huán)字符串進(jìn)行逐位對(duì)比,一旦發(fā)現(xiàn)不相同,直接返回 false,偽代碼如下
public bool Equals(string str1, string str2) { if (str1.Length != str2.Length) { return false; } for (var i = 0; i < str1.Length; i++) { if (str1[i] != str2[i]) { return false; } } return true; }
這里有一個(gè)問(wèn)題是,如果字符串第一位不相同,直接就返回 false,如果最后一位不相同,那就需要遍歷到最后,然后返回 false。不一樣的字符串,計(jì)算的時(shí)長(zhǎng)可能不一致。
嘗試破解
假如用戶(hù)知道了我們的秘鑰的固定長(zhǎng)度是 4 位。
GET /GetConfig X-Api-Key:a000 Cost: 2ns
本次耗時(shí)了 2ns, 接下來(lái)又輸入 b000, c000....
GET /GetConfig X-Api-Key:b000 Cost: 2ns GET /GetConfig X-Api-Key:c000 Cost: 2ns ... GET /GetConfig X-Api-Key:x000 Cost: 4ns
直到輸入了 x000, 發(fā)現(xiàn)其他的耗時(shí)都是 2ns, 而這里是 4ns,大概率判定第一位是 x。
注意,這里的測(cè)試進(jìn)行了放大,可能每個(gè) case 分別調(diào)用了 100 次,然后統(tǒng)計(jì)了 P50(中位數(shù))得出的結(jié)果。
然后用同樣的方法,測(cè)試第二位,第三位....., 最終破解拿到了秘鑰。
使用固定時(shí)間的算法
雖然看上去有點(diǎn)扯,但確實(shí)是真實(shí)存在的,包括大名鼎鼎的針對(duì) TLS 的 Lucky 13 攻擊,有興趣的同學(xué)可以看一下。在安全性要求比較高的場(chǎng)景中,確實(shí)要考慮到計(jì)時(shí)攻擊,當(dāng)涉及到安全時(shí),還是寧可信其有。
所以我們的算法的執(zhí)行耗時(shí)應(yīng)該是固定的,不應(yīng)該在不匹配時(shí),就立即返回,我們嘗試改造一下代碼
public bool Equals(string str1, string str2) { if (str1.Length != str2.Length) { return false; } bool reult = true; for (var i = 0; i < str1.Length; i++) { if (str1[i] != str2[i]) { reult = false; } } return reult; }
不管怎么樣,都會(huì)遍歷完整個(gè)字符串,然后返回結(jié)果,看上去沒(méi)什么問(wèn)題,時(shí)間總是固定的,但在現(xiàn)代的 CPU 和 .NET 上卻不是的,因?yàn)槲覀円紤]到分支預(yù)測(cè),特別是 if 條件。
好吧,那我們調(diào)整一下代碼
public bool Equals(string str1, string str2) { if (str1.Length != str2.Length) { return false; } bool reult = true; for (var i = 0; i < str1.Length; i++) { reult &= str1[i] == str2[i]; } return reult; }
我們用了運(yùn)算符 &,來(lái)代替 If, 只有全部為 true 時(shí),才會(huì)返回 true,其中任意一個(gè)字符不匹配,就會(huì)返回 false,看上去不錯(cuò)。
但是,還有一些問(wèn)題,對(duì)于bool類(lèi)型的 result (true/false), 我們的 .NET JIT 和 x86 指令執(zhí)行仍然會(huì)進(jìn)行一些優(yōu)化,我們?cè)僬{(diào)整一下代碼
public bool Equals(string str1, string str2) { if (str1.Length != str2.Length) { return false; } int reult = 0; for (var i = 0; i < str1.Length; i++) { reult |= str1[i] ^ str2[i]; } return reult == 0; }
我們把 bool 改成了 int 類(lèi)型,然后使用了運(yùn)算符 ^ 和 |,同樣的,只有字符串全部匹配時(shí),result 為 0,,才會(huì)返回 true, 其中任意一個(gè)不匹配,result 就不為 0,會(huì)返回 false。
最后,為了防止 JIT 對(duì)我們的代碼進(jìn)行其他的優(yōu)化,我們可以加一個(gè)特性,告訴 JIT 不要管它,就像這樣
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public bool Equals(string str1, string str2) { if (str1.Length != str2.Length) { return false; } int reult = 0; for (var i = 0; i < str1.Length; i++) { reult |= str1[i] ^ str2[i]; } return reult == 0; }
上面我們實(shí)現(xiàn)了一個(gè)針對(duì)字符串比較的固定時(shí)間的算法,來(lái)應(yīng)對(duì)計(jì)時(shí)攻擊。
實(shí)際上, 從 .NET Core 2.1 開(kāi)始就已經(jīng)做了內(nèi)置支持,我們可以直接使用 FixedTimeEquals 方法, 看一下它的實(shí)現(xiàn)
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static bool FixedTimeEquals(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right) { if (left.Length != right.Length) { return false; } int length = left.Length; int accum = 0; for (int i = 0; i < length; i++) { accum |= left[i] - right[i]; } return accum == 0; }
現(xiàn)在用起來(lái)也很方便:
var result = CryptographicOperations.FixedTimeEquals( Encoding.UTF8.GetBytes(str1), Encoding.UTF8.GetBytes(str2) );
總結(jié)
在安全性比較高的場(chǎng)景中,應(yīng)該要考慮到計(jì)時(shí)攻擊,可以使用固定時(shí)間的算法來(lái)應(yīng)對(duì)。在其他的開(kāi)發(fā)語(yǔ)言中,也都有本文中類(lèi)似的算法,而在 .NET 中,現(xiàn)在我們可以直接使用 CryptographicOperations.FixedTimeEquals。
到此這篇關(guān)于在 .NET 中使用 FixedTimeEquals 應(yīng)對(duì)計(jì)時(shí)攻擊的文章就介紹到這了,更多相關(guān).net計(jì)時(shí)攻擊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.NET中獲取Access新增記錄Id怪現(xiàn)象解決方法
寫(xiě)了一個(gè)函數(shù)獲取Access表中指定用戶(hù)Id,要求當(dāng)傳入的用戶(hù)名不存在時(shí),則在表中新增一條記錄并返回Id2012-03-03彈出窗口,點(diǎn)擊確定在刪除數(shù)據(jù)的實(shí)現(xiàn)方法
彈出窗口,點(diǎn)擊確定在刪除數(shù)據(jù)的實(shí)現(xiàn)方法,需要的朋友可以參考一下2013-04-04ASP.NET Core MVC解決控制器同名Action請(qǐng)求不明確的問(wèn)題
這篇文章主要介紹了ASP.NET Core MVC解決控制器同名Action請(qǐng)求不明確的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03.NET?Core支持Cookie和JWT混合認(rèn)證、授權(quán)的方法
這篇文章主要介紹了.NET?Core如何支持Cookie和JWT混合認(rèn)證、授權(quán),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01Convert.ToInt32與Int32.Parse區(qū)別及Int32.TryParse
2個(gè)方法都可以把string轉(zhuǎn)換為int,那么他們有什么區(qū)別?什么時(shí)候該用什么?性能如何。 其實(shí)在2.0里還有Int32.TryParse也實(shí)現(xiàn)了同樣的效果。2009-01-01.NET下文本相似度算法余弦定理和SimHash淺析及應(yīng)用實(shí)例分析
這篇文章主要介紹了.NET下文本相似度算法余弦定理和SimHash淺析及應(yīng)用,實(shí)例形式詳細(xì)講述了相似度算法余弦定理和SimHash的原理與用法,需要的朋友可以參考下2015-01-01