Dot-Net

TSQL md5 散列不同於 C# .NET md5

  • January 12, 2015

我生成了一個 md5 雜湊,如下所示:

DECLARE @varchar varchar(400) 

SET @varchar = 'è'

SELECT CONVERT(VARCHAR(2000), HASHBYTES( 'MD5', @varchar ), 2)

哪個輸出:

785D512BE4316D578E6650613B45E934

但是使用以下方法生成 MD5 雜湊:

System.Text.Encoding.UTF8.GetBytes("è")

生成:

0a35e149dbbb2d10d744bf675c7744b1

C# .NET 方法中的編碼設置為 UTF8,我曾假設 varchar 也是 UTF8,關於我做錯了什麼有什麼想法嗎?

如果您正在處理NVARCHAR/NCHAR數據(儲存為UTF-16 Little Endian),那麼您將使用Unicode編碼,而不是BigEndianUnicode. 在 .NET 中,UTF-16 被稱為Unicode,而其他 Unicode 編碼則通過它們的實際名稱來引用:UTF7、UTF8 和 UTF32。因此,Unicode其本身Little Endian與 相對BigEndianUnicode。**更新:**請參閱最後關於 UCS-2 和補充字元的部分。

在數據庫方面:

SELECT HASHBYTES('MD5', N'è') AS [HashBytesNVARCHAR]
-- FAC02CD988801F0495D35611223782CF

在 .NET 方面:

System.Text.Encoding.ASCII.GetBytes("è")
// D1457B72C3FB323A2671125AEF3EAB5D

System.Text.Encoding.UTF7.GetBytes("è")
// F63A0999FE759C5054613DDE20346193

System.Text.Encoding.UTF8.GetBytes("è")
// 0A35E149DBBB2D10D744BF675C7744B1

System.Text.Encoding.UTF32.GetBytes("è")
// 86D29922AC56CF022B639187828137F8

System.Text.Encoding.BigEndianUnicode.GetBytes("è")
// 407256AC97E4C5AEBCA825DEB3D2E89C

System.Text.Encoding.Unicode.GetBytes("è")  // this one matches HASHBYTES('MD5', N'è')
// FAC02CD988801F0495D35611223782CF

但是,這個問題與VARCHAR/ CHARdata有關,它是ASCII,所以事情有點複雜。

在數據庫方面:

SELECT HASHBYTES('MD5', 'è') AS [HashBytesVARCHAR]
-- 785D512BE4316D578E6650613B45E934

我們已經在上面看到了 .NET 方面。從這些散列值應該有兩個問題:

  • 為什麼它們中的任何一個都不匹配該HASHBYTES值?
  • 為什麼@Eric J. 的答案中連結的“sqlteam.com”文章顯示其中三個(ASCII、、UTF7UTF8)都匹配HASHBYTES值?

有一個答案涵蓋了這兩個問題:程式碼頁。在“sqlteam”文章中完成的測試使用了 0 - 127 範圍內的“安全”ASCII 字元(就 int / decimal 值而言),這些字元在程式碼頁之間沒有變化。但是 128 - 255 範圍——我們在其中找到“è”字元——是擴展集,確實因程式碼頁而異(這是有道理的,因為這是有程式碼頁的原因)。

現在嘗試:

SELECT HASHBYTES('MD5', 'è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [HashBytes]
-- D1457B72C3FB323A2671125AEF3EAB5D

這與ASCII散列值匹配(同樣,因為“sqlteam”文章/測試使用了 0 - 127 範圍內的值,他們在使用時沒有看到任何變化COLLATE)。太好了,現在我們終於找到了匹配VARCHAR/CHAR數據的方法。都好?

嗯,不是真的。讓我們看一下我們實際雜湊的內容:

SELECT 'è' AS [TheChar],
      ASCII('è') AS [TheASCIIvalue],
      'è' COLLATE SQL_Latin1_General_CP1255_CI_AS AS [CharCP1255],
      ASCII('è' COLLATE SQL_Latin1_General_CP1255_CI_AS) AS [TheASCIIvalueCP1255];

回報:

TheChar TheASCIIvalue   CharCP1255  TheASCIIvalueCP1255
è       232             ?           63

一個??只是為了驗證,執行:

SELECT CHAR(63) AS [WhatIs63?];
-- ?

啊,所以 Code Page 1255 沒有這個è字元,所以它被翻譯為每個人的最愛?。但是,為什麼在使用 ASCII 編碼時,它與 .NET 中的 MD5 散列值匹配?可能我們實際上沒有匹配 的雜湊值è,而是匹配 的雜湊值?

SELECT HASHBYTES('MD5', '?') AS [HashBytesVARCHAR]
-- 0xD1457B72C3FB323A2671125AEF3EAB5D

對。真正的ASCII字元集只是前 128 個字元(值 0 - 127)。正如我們剛剛看到的,它è是 232。因此,ASCII在 .NET 中使用編碼並沒有太大幫助。也沒有COLLATE在 T-SQL 端使用。

是否有可能在 .NET 端獲得更好的編碼?是的,通過使用Encoding.GetEncoding(Int32),它允許指定程式碼頁。可以使用以下查詢發現要使用的程式碼頁(在使用sys.columns列而不是文字或變數時使用):

SELECT sd.[collation_name],
      COLLATIONPROPERTY(sd.[collation_name], 'CodePage') AS [CodePage]
FROM   sys.databases sd
WHERE  sd.[name] = DB_NAME(); -- replace function with N'{db_name}' if not running in the DB

上面的查詢返回(對我來說):

Latin1_General_100_CI_AS_SC    1252

所以,讓我們試試程式碼頁 1252:

System.Text.Encoding.GetEncoding(1252).GetBytes("è") // Matches HASHBYTES('MD5', 'è')
// 785D512BE4316D578E6650613B45E934

嗚呼!我們有一個VARCHAR使用我們預設 SQL Server 排序規則的數據匹配:)。當然,如果數據來自數據庫或設置為不同排序規則的欄位,則GetEncoding(1252) 可能無法正常工作,您必須使用上面顯示的查詢找到實際匹配的程式碼頁(程式碼頁用於許多排序規則,所以不同的排序規則不一定意味著不同的程式碼頁)。

要查看可能的程式碼頁值是什麼,以及它們所屬的文化/區域設置,請在此處查看程式碼頁列表(列表位於“備註”部分)。


NVARCHAR與/NCHAR欄位中實際儲存的內容相關的附加資訊:

可以儲存任何UTF-16字元(2 或 4 字節),儘管內置函式的預設行為假定所有字元都是 UCS-2(每個 2 字節),它是 UTF-16 的子集。從 SQL Server 2012 開始,可以訪問一組支持稱為補充字元的 4 字節字元的 Windows 排序規則。使用這些以 結尾的 Windows 排序規則之一(_SC為列指定或直接在查詢中指定)將允許內置函式正確處理 4 字節字元。

-- The database's collation is set to: SQL_Latin1_General_CP1_CI_AS
SELECT  N'𨝫' AS [SupplementaryCharacter],
       LEN(N'𨝫') AS [LEN],
       DATALENGTH(N'𨝫') AS [DATALENGTH],
       UNICODE(N'𨝫') AS [UNICODE],
       LEFT(N'𨝫', 1) AS [LEFT],
       HASHBYTES('MD5', N'𨝫') AS [HASHBYTES];

SELECT  N'𨝫' AS [SupplementaryCharacter],
       LEN(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [LEN],
       DATALENGTH(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [DATALENGTH],
       UNICODE(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [UNICODE],
       LEFT(N'𨝫' COLLATE Latin1_General_100_CI_AS_SC, 1) AS [LEFT],
       HASHBYTES('MD5', N'𨝫' COLLATE Latin1_General_100_CI_AS_SC) AS [HASHBYTES];

回報:

SupplementaryChar   LEN   DATALENGTH   UNICODE   LEFT   HASHBYTES
𨝫                  2     4             55393    �     0x7A04F43DA81E3150F539C6B99F4B8FA9
𨝫                  1     4            165739    𨝫     0x7A04F43DA81E3150F539C6B99F4B8FA9

如您所見,既不DATALENGTHHASHBYTES不受影響。有關詳細資訊,請參閱排序規則和 Unicode 支持的 MSDN 頁面(特別是“補充字元”部分)。

引用自:https://stackoverflow.com/questions/27908449