精品秘无码一区二区三区老师-精品秘一区二三区免费雷安-精品蜜桃秘一区二区三区-精品蜜桃秘一区二区三区粉嫩-精品蜜桃一区二区三区-精品蜜臀国产aⅴ一区二区三区

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

Sdcb Chats 技術(shù)博客:數(shù)據(jù)庫 ID 選型的曲折之路 - 從 Guid 到自增 ID,再到 Guid

freeflydom
2025年2月5日 9:49 本文熱度 142

在軟件開發(fā)中,數(shù)據(jù)庫主鍵的選擇,Guid 還是自增整數(shù) ID,一直是一個(gè)備受開發(fā)者關(guān)注和討論的經(jīng)典話題。作為開源 ChatGPT 前端項(xiàng)目 Sdcb Chats 的開發(fā)者,我們?cè)谶@個(gè)問題上也經(jīng)歷了一系列探索和演進(jìn),頗具代表性。Sdcb Chats 項(xiàng)目致力于打造一個(gè)強(qiáng)大、易用、可高度定制的 ChatGPT 及大語言模型前端,幫助用戶輕松連接、管理和使用各種主流的大語言模型。 總的來說,Sdcb Chats 的 ID 策略經(jīng)歷了從最初使用 Guid,到遷移至自增 ID,再到界面顯示加密 ID,最終又回歸到界面顯示 Guid 的過程,其中蘊(yùn)含著許多有趣的思考和實(shí)踐經(jīng)驗(yàn),也反映了我們?cè)陧?xiàng)目迭代過程中對(duì)性能、安全和用戶體驗(yàn)的不斷權(quán)衡與優(yōu)化。

第一階段 - 擁抱 Guid:“一步到位”的方案

項(xiàng)目初期,我的好友 G 負(fù)責(zé)總體系統(tǒng)設(shè)計(jì),包括前端和數(shù)據(jù)庫。他果斷選擇了 Guid 作為主鍵方案。這在當(dāng)時(shí)是很自然的選擇,因?yàn)樵谄毡榈募夹g(shù)認(rèn)知中,自增 ID 在分布式系統(tǒng)中似乎存在諸多不便,而 Guid(全局唯一標(biāo)識(shí)符)則被視為一種更現(xiàn)代、更通用的解決方案。Guid 的核心優(yōu)勢(shì)在于其全局唯一性,能夠在不同的數(shù)據(jù)庫和服務(wù)器之間獨(dú)立生成,無需擔(dān)心 ID 沖突問題。

以下是項(xiàng)目初期基于 PostgreSQL 設(shè)計(jì)的數(shù)據(jù)庫創(chuàng)建腳本鏈接(如果您感興趣可以查看):
https://github.com/sdcb/chats/blob/raw/prisma/postgresql/migrations/20240627111401_init/migration.sql

例如,這是 Message 表的結(jié)構(gòu)定義:

CREATE TABLE "ChatMessages" (
    "id" UUID NOT NULL,
    "userId" UUID NOT NULL,
    "chatId" UUID NOT NULL,
    "parentId" UUID,
    "chatModelId" UUID,
    "role" TEXT NOT NULL,
    "messages" TEXT NOT NULL,
    "inputTokens" INTEGER NOT NULL DEFAULT 0,
    "outputTokens" INTEGER NOT NULL DEFAULT 0,
    "inputPrice" DECIMAL(65,30) NOT NULL DEFAULT 0,
    "outputPrice" DECIMAL(65,30) NOT NULL DEFAULT 0,
    "duration" INTEGER NOT NULL DEFAULT 0,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT "ChatMessages_pkey" PRIMARY KEY ("id")
);

然而,隨著項(xiàng)目的深入發(fā)展,Guid 方案的一些局限性逐漸顯現(xiàn)。首先,Guid 較長(zhǎng)的長(zhǎng)度和復(fù)雜的結(jié)構(gòu)在某些場(chǎng)景下給數(shù)據(jù)庫性能帶來了一定負(fù)擔(dān)。尤其是在數(shù)據(jù)量快速增長(zhǎng)的情況下,索引體積增大,查詢速度變慢等問題開始凸顯。作為負(fù)責(zé)維護(hù)公司內(nèi)部 Chats 數(shù)據(jù)庫服務(wù)器的人,我注意到核心表 Chats 的索引碎片率持續(xù)偏高。為了避免性能下降,我不得不設(shè)置每日任務(wù),定期重建索引。這讓我開始認(rèn)真考慮對(duì) Chats 項(xiàng)目的數(shù)據(jù)庫進(jìn)行大規(guī)模重構(gòu)。

第二階段 - 性能至上:遷移至自增 ID

Chats 項(xiàng)目的重構(gòu)是一項(xiàng)系統(tǒng)性工程,數(shù)據(jù)庫的大規(guī)模重構(gòu)只是其中關(guān)鍵環(huán)節(jié)之一。實(shí)際上,軟件重構(gòu)是一個(gè)持續(xù)迭代的過程,貫穿于項(xiàng)目的整個(gè)生命周期。

在我看來,項(xiàng)目重構(gòu)如同軟件的自我革新,需要開發(fā)者具備“刀刃向內(nèi)”的勇氣和決心。當(dāng)我們審視代碼,發(fā)現(xiàn)不足之處,就如同在前進(jìn)的道路上遇到了障礙。我們當(dāng)然可以選擇繞行,暫時(shí)規(guī)避問題,但這些技術(shù)債務(wù)會(huì)像隱患一樣潛伏下來,并在未來某個(gè)時(shí)刻影響系統(tǒng)的穩(wěn)定性和可維護(hù)性。特別是對(duì)于數(shù)據(jù)庫這種核心模塊,開發(fā)者往往出于謹(jǐn)慎,傾向于避免改動(dòng)既有結(jié)構(gòu)和數(shù)據(jù)。但長(zhǎng)此以往,問題會(huì)逐漸累積,最終侵蝕系統(tǒng)的健康。因此,正視并解決這些問題才是負(fù)責(zé)任的做法。

當(dāng)然,在 Chats 項(xiàng)目的重構(gòu)中,我也有著得天獨(dú)厚的優(yōu)勢(shì)。作為后端設(shè)計(jì)的主導(dǎo)者和核心開發(fā)者,我對(duì)系統(tǒng)的每一個(gè)細(xì)節(jié)都了如指掌。正所謂“船小好調(diào)頭”,即使是數(shù)據(jù)庫大規(guī)模遷移這樣的“大手術(shù)”,我也能快速?zèng)Q策、高效執(zhí)行。事實(shí)上,Chats 數(shù)據(jù)庫已經(jīng)經(jīng)歷過多次重要的數(shù)據(jù)遷移,各位可以通過項(xiàng)目倉庫中的數(shù)據(jù)庫遷移腳本了解詳情:https://github.com/sdcb/chats/tree/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/scripts/db-migration。

將主鍵從 Guid 切換為自增 int ID,最直接的好處就是性能的提升,具體體現(xiàn)在以下幾個(gè)方面:

  • 更小的索引尺寸和更快的查詢速度:相比 Guid,自增整數(shù) ID 在存儲(chǔ)空間上占用更少字節(jié),這意味著數(shù)據(jù)庫索引的體積也隨之減小。更小的索引意味著數(shù)據(jù)庫在執(zhí)行查詢時(shí),特別是面對(duì)海量數(shù)據(jù)時(shí),能夠更快地遍歷索引,從而顯著提升查詢速度。這對(duì)于像 Chats 這種消息量可能快速增長(zhǎng)的應(yīng)用至關(guān)重要。

  • 減少索引碎片:Guid 的無序性導(dǎo)致在插入新數(shù)據(jù)時(shí),索引頁分裂的概率大大增加,從而產(chǎn)生索引碎片。而自增整數(shù) ID 的順序遞增特性,可以保證新插入的數(shù)據(jù)大概率追加在索引的末尾,最大限度地減少索引頁分裂,降低索引碎片的產(chǎn)生。正如我在維護(hù) Chats 數(shù)據(jù)庫時(shí)觀察到的,Guid 主鍵的表索引碎片問題尤為突出。切換到自增 ID 后,索引維護(hù)工作將大大簡(jiǎn)化。(當(dāng)然,如果您的系統(tǒng)情況特殊,難以完全拋棄 Guid,也可以考慮使用 Uuid v7,它是一種基于時(shí)間戳的連續(xù)性 Guid,也能有效減少索引碎片)

  • 節(jié)省存儲(chǔ)空間:雖然單個(gè) Guid 僅比整數(shù) ID 多幾個(gè)字節(jié),但當(dāng)數(shù)據(jù)量累積到百萬、千萬甚至億級(jí)別時(shí),Guid 額外占用的存儲(chǔ)空間將非常可觀。對(duì)于長(zhǎng)期運(yùn)行的應(yīng)用,節(jié)省存儲(chǔ)空間也意味著降低了硬件成本。

當(dāng)然,從 Guid 遷移到自增 ID 并非一帆風(fēng)順,最大的挑戰(zhàn)在于數(shù)據(jù)遷移的復(fù)雜性。我們需要編寫嚴(yán)謹(jǐn)?shù)臄?shù)據(jù)遷移腳本,確保數(shù)據(jù)遷移過程中數(shù)據(jù)的一致性和完整性不受破壞。同時(shí),還需要仔細(xì)評(píng)估遷移可能帶來的業(yè)務(wù)影響,例如外鍵關(guān)聯(lián)的更新,以及應(yīng)用程序代碼的調(diào)整。幸運(yùn)的是,正如前面提到的,Chats 項(xiàng)目已經(jīng)積累了多次數(shù)據(jù)庫遷移的經(jīng)驗(yàn),這為我們這次從 Guid 到自增 ID 的遷移奠定了堅(jiān)實(shí)的基礎(chǔ)。

例如,這段 339 行的 C# 數(shù)據(jù)庫遷移腳本(在 LINQPad 中編寫):
https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/scripts/db-migration/2024/20240902-db-migration.linq

在遷移腳本中,我們使用了類似 GuidInt32Mapping 這樣的類來維護(hù) Guid 和自增 ID 之間的映射關(guān)系:

public class GuidInt32Mapping
{
	int _nextId = 1;
	Dictionary<Guid, int> _mapping = new();
	public void Add(Guid guid)
	{
		_mapping.Add(guid, _nextId++);
	}
	public int this[Guid guid]
	{
		get
		{
			return _mapping[guid];
		}
	}
}

通過這樣的映射,我們可以在數(shù)據(jù)遷移過程中,將舊的 Guid 主鍵平滑地轉(zhuǎn)換為新的自增 ID,并確保數(shù)據(jù)關(guān)聯(lián)關(guān)系的正確性。

第三階段 - 安全升級(jí):界面 ID 加密

完成數(shù)據(jù)庫主鍵從 Guid 到自增 ID 的遷移后,我們又面臨了新的問題:如何在用戶界面上安全地展示 ID。最初,我們直接沿用了數(shù)據(jù)庫的自增 ID,將其暴露在前端界面和 API 接口中。然而,這種做法很快引發(fā)了一些安全性和用戶體驗(yàn)方面的問題。

例如,當(dāng)創(chuàng)建一個(gè)新的聊天會(huì)話時(shí),界面 URL 可能會(huì)顯示為 https://chats-dev.starworks.cc:88/#/1,其中的 1 代表系統(tǒng)中第一個(gè)聊天。當(dāng)您新建聊天時(shí),URL 就會(huì)變?yōu)?nbsp;https://chats-dev.starworks.cc:88/#/2。以此類推,ID 會(huì)順序遞增。這種連續(xù)的數(shù)字 ID 存在潛在的安全風(fēng)險(xiǎn):任何人都可以通過簡(jiǎn)單地修改 URL 中的數(shù)字,嘗試猜測(cè)和訪問其他聊天會(huì)話(當(dāng)然,后端服務(wù)會(huì)進(jìn)行嚴(yán)格的權(quán)限驗(yàn)證)。更重要的是,這種連續(xù)的數(shù)字 ID 容易暴露系統(tǒng)的一些敏感信息,例如通過 ID 的大致范圍,外界可以推測(cè)出系統(tǒng)用戶和聊天會(huì)話的大概規(guī)模,這在某些場(chǎng)景下是我們不希望泄露的。

此外,從用戶體驗(yàn)的角度來看,連續(xù)的數(shù)字 ID 也顯得不夠?qū)I(yè)和優(yōu)雅。用戶可能會(huì)覺得這些 ID 過于簡(jiǎn)單和隨意,與他們對(duì)現(xiàn)代聊天應(yīng)用的期望不符。

為了解決這些問題,我們決定對(duì)界面上顯示的 ID 進(jìn)行加密處理。

最初,我使用了這段 C# 代碼來實(shí)現(xiàn)整數(shù) ID 的加密:
https://github.com/sdcb/chats/blob/r-287/src/BE/Services/UrlEncryption/Utils.cs#L22-L36

/// <summary>
/// 加密數(shù)據(jù)結(jié)構(gòu)為 base64url([1:version + encryptedData])
/// </summary>
public static string Encrypt(ReadOnlySpan<byte> input, byte[] key, byte[] iv)
{
    using Aes aes = Aes.Create();
    aes.Key = key;
    byte[] encryptedIdBytes = aes.EncryptCbc(input, iv);
    byte[] encryptedIdBytesWithIV = new byte[1 + encryptedIdBytes.Length];
    encryptedIdBytesWithIV[0] = 0; // 版本號(hào)
    Array.Copy(encryptedIdBytes, 0, encryptedIdBytesWithIV, 1, encryptedIdBytes.Length);
    return WebEncoders.Base64UrlEncode(encryptedIdBytesWithIV);
}

在這個(gè)實(shí)現(xiàn)中,整數(shù) ID 首先被轉(zhuǎn)換為小端序的字節(jié)數(shù)組,然后使用 AES 算法的 CBC 模式進(jìn)行加密。加密后的數(shù)據(jù)被編碼為 Base64 URL 格式,以便在 URL 中安全傳輸。最終,URL 可能呈現(xiàn)為如下形式:
https://chats-dev.starworks.cc:88/#/AFo-sKz8LTPvDBMvau1dKfA

通過這種加密方式,我們不僅提升了系統(tǒng)的安全性,也改善了用戶體驗(yàn)。加密后的 ID 看起來更加復(fù)雜和專業(yè),有效避免了簡(jiǎn)單數(shù)字序列帶來的潛在問題。

其中,初始化向量 IV 的生成方式如下。我定義了一個(gè) EncryptionPurpose 枚舉:

// https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/BE/Services/UrlEncryption/EncryptionPurpose.cs#L4
public enum EncryptionPurpose
{
    ChatId,
    FileId,
    MessageId,
    ChatGroupId,
    ChatShareId,
}

https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/BE/Services/UrlEncryption/UrlEncryptionService.cs#L15

foreach (EncryptionPurpose purpose in Enum.GetValues<EncryptionPurpose>())
{
    _ivs[purpose] = Utils.GenerateIdHasherKey(idHasherPassword + purpose, keyLength: 16, iterations: 200);
}

https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/BE/Services/UrlEncryption/Utils.cs#L8

public static byte[] GenerateIdHasherKey(string idHasherPassword, int keyLength, int iterations)
{
    // PBKDF2 參數(shù)
    byte[] salt = new byte[16];
    using Rfc2898DeriveBytes rfc2898DeriveBytes = new(idHasherPassword, salt, iterations, HashAlgorithmName.SHA256);
    return rfc2898DeriveBytes.GetBytes(keyLength);
}

每個(gè)枚舉值代表一種加密目的。代碼會(huì)根據(jù)不同的目的生成不同的 IV。這樣做的目的是確保即使同一個(gè)整數(shù) ID 在不同的上下文(例如 ChatId 和 FileId)中被加密,也會(huì)產(chǎn)生不同的加密結(jié)果。這種做法提升了安全性和靈活性,我們可以在不同的場(chǎng)景下復(fù)用相同的加密機(jī)制,而無需擔(dān)心 ID 重復(fù)或沖突。

可能有朋友會(huì)問,為什么不使用隨機(jī) IV,并將 IV 添加到加密后的 ID 中,這樣安全性不是更高嗎?這主要是基于以下兩點(diǎn)考慮:

首先,前端的某些計(jì)算邏輯依賴于穩(wěn)定的 ID。在我們的前端代碼中,特別是在聊天會(huì)話管理和消息渲染方面,我們大量使用了基于 ID 的緩存和狀態(tài)管理機(jī)制。例如,當(dāng)用戶在一個(gè)聊天窗口中滾動(dòng)瀏覽消息時(shí),前端會(huì)根據(jù)消息的 ID 來渲染消息之間的父子關(guān)系。如果使用隨機(jī) IV,即使是同一個(gè)聊天會(huì)話,在不同的時(shí)間或不同的上下文中被加密,生成的 ID 都會(huì)不同。這會(huì)導(dǎo)致前端緩存失效,狀態(tài)管理混亂,最終引發(fā)難以追蹤的 bug。想象一下,用戶明明還在同一個(gè)聊天中,但由于 ID 變化,前端卻認(rèn)為這是一個(gè)新的聊天,之前的消息緩存全部失效,這無疑會(huì)造成糟糕的用戶體驗(yàn)。為了保證前端邏輯的穩(wěn)定性和可預(yù)測(cè)性,我們需要確保在同一上下文中,同一個(gè)整數(shù) ID 加密后的結(jié)果始終一致。

其次,不固定的 IV 會(huì)顯著增加 ID 的長(zhǎng)度。如果將隨機(jī)生成的 IV 也附加到加密后的 ID 中,最終的 ID 長(zhǎng)度會(huì)大大增加。AES 算法的 IV 通常為 16 字節(jié),轉(zhuǎn)換為 Base64 URL 編碼后,會(huì)增加約 21 個(gè)字符的長(zhǎng)度(16 * 4 / 3 ≈ 21.3)。原本加密后的 ID 已經(jīng)比純數(shù)字 ID 長(zhǎng)了不少,如果再加上 20 多個(gè)字符的 IV,整個(gè) ID 會(huì)顯得非常臃腫,尤其是在 URL 中展示時(shí),既不美觀,也增加了 URL 的長(zhǎng)度負(fù)擔(dān)。我們希望在保證安全性的前提下,盡可能保持 ID 的簡(jiǎn)潔易用。

因此,綜合考慮前端的穩(wěn)定性和 ID 長(zhǎng)度,我們最終選擇了使用基于 EncryptionPurpose 枚舉的固定 IV 方案。這樣既保證了在不同上下文中加密結(jié)果的差異性,又避免了隨機(jī) IV 帶來的不穩(wěn)定性和長(zhǎng)度增加問題。

第四階段 - 兼顧用戶感知:界面顯示為 Guid(當(dāng)前方案)

經(jīng)過一段時(shí)間的實(shí)際運(yùn)行,我們意識(shí)到,雖然加密 ID 解決了安全性問題,但在某些場(chǎng)景下,用戶仍然希望看到一種更具辨識(shí)度的 ID 格式。因此,我們最終決定在界面上將 ID 顯示為 Guid 格式。

這種做法的優(yōu)點(diǎn)在于,Guid 格式的 ID 看起來更加隨機(jī)和復(fù)雜,更符合用戶對(duì)現(xiàn)代應(yīng)用的普遍認(rèn)知,同時(shí)也有效避免了直接暴露自增 ID 的問題。在具體實(shí)現(xiàn)上,我們將加密后的 ID 轉(zhuǎn)換為 Guid 格式進(jìn)行展示。這樣一來,用戶在界面上看到的 ID 既安全又專業(yè)。

細(xì)心的朋友可能已經(jīng)注意到,由于我的輸入長(zhǎng)度為 4 字節(jié)或 8 字節(jié)(分別對(duì)應(yīng) int32 和 int64 類型的 ID),AES CBC 加密后的輸出長(zhǎng)度固定為 16 字節(jié)(但前端代碼額外增加了一個(gè)字節(jié)作為版本號(hào)前綴,固定為 0)。而一個(gè) Guid 的長(zhǎng)度恰好也是 16 字節(jié)。因此,只需將 Base64Url 序列化方式替換為 Guid 序列化,即可輕松將加密 ID 轉(zhuǎn)換為 Guid 形式:

https://github.com/sdcb/chats/blob/r-407/src/BE/Services/UrlEncryption/Utils.cs#L50-L60

    public static string Encrypt(ReadOnlySpan<byte> input, byte[] key, byte[] iv)
    {
        using Aes aes = Aes.Create();
        aes.Key = key;
        byte[] encryptedIdBytes = aes.EncryptCbc(input, iv);
        return Serialize(encryptedIdBytes);
    }
    private static string Serialize(byte[] encryptedIdBytes)
    {
        if (encryptedIdBytes.Length == 16)
        {
            return new Guid(encryptedIdBytes).ToString();
        }
        else
        {
            return WebEncoders.Base64UrlEncode(encryptedIdBytes);
        }
    }

可能有朋友會(huì)進(jìn)一步追問,為什么堅(jiān)持使用 AES CBC 算法,而不是現(xiàn)在更流行的 AES GCM 算法呢?這又可以展開一篇長(zhǎng)文討論。簡(jiǎn)單來說:

首先,AES GCM 的隨機(jī)性高度依賴于 nonce 的唯一性。Nonce(Number used once)是一個(gè)一次性使用的隨機(jī)數(shù),在 AES GCM 中扮演著至關(guān)重要的角色,類似于 AES CBC 中的 IV(Initialization Vector,初始化向量)。如果 nonce 在多次加密中重復(fù)使用,尤其是在加密序列化的、遞增的 ID 時(shí),AES GCM 的安全性會(huì)大打折扣,甚至可能暴露出加密模式的規(guī)律性。

在我們的場(chǎng)景中,雖然我們?yōu)槊糠N EncryptionPurpose 生成了不同的 IV(在 AES CBC 中)或者說 nonce(如果我們使用 AES GCM),但如果我們?cè)谕粋€(gè) EncryptionPurpose 下連續(xù)加密遞增的整數(shù) ID,例如聊天 ID 1, 2, 3...,并且每次都使用相同的 nonce,那么 AES GCM 的輸出結(jié)果就會(huì)呈現(xiàn)出可預(yù)測(cè)的模式。更具體地說,后一個(gè)加密后的 ID 很可能與前一個(gè)加密后的 ID 存在某種簡(jiǎn)單的數(shù)學(xué)關(guān)系,比如僅僅是最后幾個(gè)字節(jié)的差異。這種可預(yù)測(cè)性對(duì)于安全性來說是致命的,攻擊者可能會(huì)利用這種規(guī)律來猜測(cè)或破解 ID。

為了更直觀地說明問題,請(qǐng)看以下 C# 代碼示例:

// 示例密鑰和 nonce(通常 nonce 應(yīng)該是隨機(jī)生成的)
byte[] key = new byte[16]; // 128-bit key
byte[] nonce = new byte[12]; // 96-bit nonce
RandomNumberGenerator.Fill(key);
RandomNumberGenerator.Fill(nonce);
Console.WriteLine("Nonce: " + BitConverter.ToString(nonce));
// 加密連續(xù)的整數(shù) ID
for (int id = 1; id <= 5; id++)
{
	byte[] plaintext = BitConverter.GetBytes(id);
	using AesGcm aesGcm = new AesGcm(key, tagSizeInBytes: 16);
	byte[] ciphertext = new byte[plaintext.Length];
	byte[] tag = new byte[16]; // 128-bit tag
	aesGcm.Encrypt(nonce, plaintext, ciphertext, tag);
	Console.WriteLine($"ID: {id}");
	Console.WriteLine("Ciphertext: " + BitConverter.ToString(ciphertext));
	Console.WriteLine("Tag: " + BitConverter.ToString(tag));
	Console.WriteLine();
}

輸出結(jié)果如下:

Nonce: 58-8F-39-89-8C-AD-45-44-F8-C6-F0-FC
ID: 1
Ciphertext: AB-DE-99-E8
Tag: 54-C5-52-BE-3A-0E-E9-9F-EF-7F-CB-F5-09-31-7A-61
ID: 2
Ciphertext: A8-DE-99-E8
Tag: 0D-18-B3-1B-0B-07-C7-0C-90-C9-F7-40-D1-DC-26-B3
ID: 3
Ciphertext: A9-DE-99-E8
Tag: 3A-53-EC-78-1B-FF-22-82-45-A4-1C-D3-99-87-12-FD
ID: 4
Ciphertext: AE-DE-99-E8
Tag: BE-A3-70-51-69-15-9A-2A-6F-A5-8E-2B-60-06-9F-17
ID: 5
Ciphertext: AF-DE-99-E8
Tag: 89-E8-2F-32-79-ED-7F-A4-BA-C8-65-B8-28-5D-AB-59

請(qǐng)注意觀察 Ciphertext 的部分,可以發(fā)現(xiàn)它們之間只有極小的差異(只有一個(gè)字節(jié)不同)。

相比之下,AES CBC 雖然也依賴 IV,但即使 IV 固定,只要密鑰安全,其加密結(jié)果的隨機(jī)性依然能得到較好的保證。尤其是在我們使用了填充模式(Padding)的情況下,即使輸入數(shù)據(jù)存在一定的規(guī)律性,也能有效地隱藏這種規(guī)律。

其次,AES GCM 的輸出長(zhǎng)度會(huì)顯著增加,難以適配 Guid 格式。AES GCM 在提供加密功能的同時(shí),還提供了數(shù)據(jù)完整性校驗(yàn)功能,這是通過附加一個(gè)認(rèn)證標(biāo)簽(Authentication Tag,簡(jiǎn)稱 Tag)來實(shí)現(xiàn)的。這個(gè) Tag 通常是 12 到 16 字節(jié),用于驗(yàn)證數(shù)據(jù)的完整性和真實(shí)性,防止數(shù)據(jù)被篡改。除了 Tag 之外,AES GCM 還需要一個(gè)顯式的 nonce 作為輸入。對(duì)于我們來說,nonce 至少需要 12 字節(jié)才能保證足夠的安全性。

這意味著,如果我們使用 AES GCM 加密一個(gè) 4 字節(jié)的 int32 ID,最終的輸出長(zhǎng)度將至少是:4 字節(jié)(密文) + 12 字節(jié)(nonce) + 12 字節(jié)(最小 Tag 大?。?= 28 字節(jié)。即使我們加密一個(gè) 8 字節(jié)的 int64 ID,輸出長(zhǎng)度也會(huì)超過 32 字節(jié)。這樣的長(zhǎng)度,無論如何都無法直接塞到一個(gè) 16 字節(jié)的 Guid 中。而且,為了將 nonce 和 tag 都塞進(jìn)去,我們勢(shì)必需要設(shè)計(jì)更復(fù)雜的序列化方案,這會(huì)增加前端和后端的處理復(fù)雜度,也可能導(dǎo)致 ID 格式的不統(tǒng)一,例如一部分 ID 是 Guid,一部分是更長(zhǎng)的 Base64 編碼字符串,這會(huì)給前端開發(fā)帶來額外的困擾。

我們之所以最終選擇將加密后的 ID 展示為 Guid 格式,一個(gè)重要的考量就是希望保持 ID 的統(tǒng)一性和簡(jiǎn)潔性。Guid 作為一個(gè) 16 字節(jié)的固定長(zhǎng)度標(biāo)識(shí)符,在很多場(chǎng)景下都非常方便使用和處理。如果我們?yōu)榱俗非?AES GCM 的“更高安全性”而犧牲了 ID 的簡(jiǎn)潔性和統(tǒng)一性,反而可能會(huì)得不償失。

最后,AES GCM 的額外安全優(yōu)勢(shì)在我們的應(yīng)用場(chǎng)景下并非不可或缺。AES GCM 最主要的優(yōu)勢(shì)在于它提供的認(rèn)證加密(Authenticated Encryption)功能,即在加密的同時(shí),也保證了數(shù)據(jù)的完整性和真實(shí)性。這意味著,如果數(shù)據(jù)在傳輸過程中被篡改,解密時(shí)會(huì)立即發(fā)現(xiàn)并報(bào)錯(cuò)。這種認(rèn)證功能對(duì)于一些對(duì)數(shù)據(jù)完整性要求極高的場(chǎng)景非常重要,例如金融交易、電子簽名等。

然而,在我們的 Chats 應(yīng)用中,我們對(duì) ID 的安全性需求主要集中在防止惡意猜測(cè)和未經(jīng)授權(quán)的訪問,而不是防止數(shù)據(jù)篡改。即使加密后的 ID 在傳輸過程中被篡改,最終解密出來的 ID 也大概率無法在數(shù)據(jù)庫中找到對(duì)應(yīng)的記錄,或者即使找到了,后續(xù)的業(yè)務(wù)邏輯也會(huì)進(jìn)行權(quán)限驗(yàn)證,確保用戶只能訪問自己擁有的聊天或消息。

更重要的是,即使我們使用 AES CBC,也并非完全沒有數(shù)據(jù)完整性驗(yàn)證機(jī)制。首先,AES CBC 配合填充模式(例如 PKCS7 Padding)本身就提供了一定程度的完整性校驗(yàn)。對(duì)于 int32 類型的 ID,AES CBC 加密后會(huì)生成 16 字節(jié)的密文,其中有 12 字節(jié)實(shí)際上是填充數(shù)據(jù)。如果密文被篡改,解密時(shí)填充校驗(yàn)會(huì)失敗,從而可以檢測(cè)到數(shù)據(jù)損壞。雖然這種校驗(yàn)強(qiáng)度不如 AES GCM 的 Tag 那么高,但也足以應(yīng)對(duì)一般的篡改嘗試。

其次,在我們的系統(tǒng)中,解密后的 ID 最終會(huì)用于數(shù)據(jù)庫查詢。即使攻擊者能夠繞過 AES CBC 的填充校驗(yàn),篡改了加密后的 ID,解密出來的錯(cuò)誤 ID 在數(shù)據(jù)庫中大概率也找不到對(duì)應(yīng)的記錄。即使碰巧找到了記錄,我們也會(huì)在數(shù)據(jù)庫層面和業(yè)務(wù)邏輯層面進(jìn)行多重權(quán)限驗(yàn)證,確保數(shù)據(jù)的安全性。

因此,綜合考慮以上三點(diǎn),我們最終權(quán)衡之后,仍然選擇了 AES CBC 算法。它在保證足夠安全性的前提下,能夠生成 16 字節(jié)的密文,完美適配 Guid 格式,并且實(shí)現(xiàn)相對(duì)簡(jiǎn)單,性能也更優(yōu)。當(dāng)然,技術(shù)選型永遠(yuǎn)是一個(gè)不斷演進(jìn)的過程,未來如果我們的安全需求發(fā)生變化,或者 AES GCM 在性能和易用性方面有了新的提升,我們也不排除會(huì)重新評(píng)估并切換到 AES GCM 的可能性。

總結(jié)與展望

回顧 Sdcb Chats 項(xiàng)目 ID 演進(jìn)的四個(gè)階段,從最初擁抱 Guid 的“一步到位”,到為了性能考量轉(zhuǎn)向自增 ID,再到為了安全和體驗(yàn)在界面上加密 ID,最終又回歸到使用 Guid 形式展示,這的確是一段曲折而又充滿思考的旅程。

在這個(gè)過程中,我們不斷地在性能、安全性、用戶體驗(yàn)和開發(fā)效率之間權(quán)衡取舍。沒有一勞永逸的完美方案,只有在持續(xù)迭代和演進(jìn)中,才能找到最適合當(dāng)前階段的最佳實(shí)踐。每一次看似“倒退”的改動(dòng),實(shí)際上都基于更深入的理解和更全面的考量。例如,從 Guid 到自增 ID 的轉(zhuǎn)變,是為了解決實(shí)際存在的數(shù)據(jù)庫性能瓶頸;而界面上從加密 ID 到 Guid 的回歸,則是在安全性得到保障的前提下,更好地滿足用戶對(duì)“現(xiàn)代感”和“專業(yè)性”的用戶感知。

這段經(jīng)歷也印證了軟件開發(fā)中一個(gè)重要的理念:沒有銀彈。技術(shù)選型需要結(jié)合具體的應(yīng)用場(chǎng)景和需求,持續(xù)地監(jiān)控和評(píng)估,并根據(jù)實(shí)際情況靈活調(diào)整。我們不能因?yàn)椤按蠹叶颊f Guid 好”就盲目跟風(fēng),也不能因?yàn)椤靶阅苤辽稀本秃雎园踩院陀脩趔w驗(yàn)。只有深入理解各種方案的優(yōu)缺點(diǎn),才能做出最明智的選擇。

而 Sdcb Chats 項(xiàng)目的 ID 演進(jìn)之路,也正是開源項(xiàng)目不斷迭代、持續(xù)進(jìn)化的一個(gè)縮影。我們始終秉持著開放、務(wù)實(shí)的態(tài)度,積極擁抱變化,勇于嘗試新的技術(shù)方案,并不斷地從實(shí)踐中總結(jié)經(jīng)驗(yàn)教訓(xùn)。

如果您對(duì)我們這曲折的 ID 選型故事,以及 Sdcb Chats 項(xiàng)目本身感興趣,歡迎繼續(xù)了解!

Sdcb Chats:一個(gè)強(qiáng)大的開源 ChatGPT 前端

我是開源項(xiàng)目 Sdcb Chats 的作者。Sdcb Chats 定位為一個(gè)強(qiáng)大且易于部署的 ChatGPT 前端,旨在幫助用戶輕松接入和管理各種主流的大語言模型。

Sdcb Chats 的主要特性包括:

  • 廣泛的大語言模型支持: 目前已支持 15 種不同的大語言模型提供商。只需配置簡(jiǎn)單的 API Key 等連接信息,即可無縫切換和體驗(yàn)來自不同廠商的強(qiáng)大 AI 能力。
  • 靈活的數(shù)據(jù)庫選擇: 支持 SQLite、SQL Server 和 PostgreSQL 三種數(shù)據(jù)庫,您可以根據(jù)自身需求和環(huán)境選擇最合適的數(shù)據(jù)庫方案。
  • 多樣化的部署方式: 提供 Docker 鏡像 部署方式,方便快捷地在各種容器環(huán)境中部署;同時(shí)提供多種操作系統(tǒng)的 二進(jìn)制文件 下載,無需復(fù)雜的編譯過程,即可快速啟動(dòng)和使用。
  • 完善的管理功能: 內(nèi)置 多用戶管理 功能,方便團(tuán)隊(duì)協(xié)作使用;提供 Token 消耗統(tǒng)計(jì) 和 付費(fèi)管理 功能,幫助您更好地控制和管理大語言模型的使用成本。

無論您是個(gè)人開發(fā)者、技術(shù)愛好者,還是企業(yè)用戶,Sdcb Chats 都能為您提供一個(gè)強(qiáng)大、靈活、易用的 ChatGPT 前端解決方案。

如果您覺得 Sdcb Chats 對(duì)您有所幫助,或者您認(rèn)同我們的技術(shù)理念和開源精神,請(qǐng)?jiān)?GitHub 上給我們一個(gè) Star ?。您的支持是我們持續(xù)前進(jìn)的最大動(dòng)力!

GitHub 倉庫地址: https://github.com/sdcb/chats

希望這篇博客和項(xiàng)目介紹能幫助您對(duì) Sdcb Chats 項(xiàng)目有更深入的了解。期待您的關(guān)注和參與,讓我們一起打造更優(yōu)秀的開源項(xiàng)目!

轉(zhuǎn)自https://www.cnblogs.com/sdcb/p/18691585/sdcb-chats-id-in-url



該文章在 2025/2/5 9:49:38 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉儲(chǔ)管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 宅男噜噜噜国产在线观看 | 免费无码国产v片 | 亚洲啪啪综合?v一区综合精品区 | 久久中文字幕人妻熟av女蜜柚m | 国产人妻人伦精品 | 欧美国产日本精品一区二区三 | 三上悠亚高清无码观看 | 久久无码人妻精品一区二区三区 | 国产成人精品一区二区三区免费看 | 性爱视频在线观看 | 无码AⅤ精品一区二区三区 无码aⅴ网站在线观看 | 欧美日本精品免费 | 三上悠亚免费观看av网站 | 护士的小嫩嫩好紧好爽电影详情手机在线播放 | 2025中文字字幕在线不卡 | 欧美成人se01短视频在线看 | 国产网红无码福利在线播放 | 国产日韩av免费无码一区二区三区 | 亚洲AV无码一区二区三区性色学 | 精品欧美一区手机在线观看 | 亚洲性粉嫩无码日韩人妻无码三级 | 国产91丝袜在线播放网站 | 国产女人的高潮大叫毛片 | 麻豆国产精品va在线观看不卡 | 毛片无码高潮喷白浆视频 | 99热青青草超碰在线 | 亚洲一区无码制服丝袜 | 久久精彩视频在线播放 | 国产成人无码精品一区 | 色偷偷噜噜噜亚洲男人 | 日韩免费一级a毛片在线播放一级 | 国产成人无码免费精品果冻传媒 | 亚洲九九精品一区二区三区 | 欧美性A片又硬又粗又大全集 | 亚洲精品乱码久久久久久蜜桃图片 | 国产麻豆天美果 | 亚洲欧洲国产成人综合一本 | 中文日本永久精品国视频 | 欧美人妻一区二区三区 | 四虎影院国产精品 | 自拍亚洲中文字幕一区二区 |