sql server - حساب تجزئة MD5 من سلسلة UTF8




sql-server tsql (2)

لدي جدول SQL حيث أقوم بتخزين قيم السلسلة الكبيرة التي يجب أن تكون فريدة من نوعها. لضمان التفرد ، لدي فهرس فريد في عمود أقوم فيه بتخزين تمثيل سلسلة لعنصر MD5 الخاص بالسلسلة الكبيرة.

يستخدم تطبيق C # الذي يحفظ هذه السجلات الطريقة التالية لإجراء التجزئة:

public static string CreateMd5HashString(byte[] input)
{
    var hashBytes = MD5.Create().ComputeHash(input);
    return string.Join("", hashBytes.Select(b => b.ToString("X")));
}

من أجل استدعاء هذا ، أقوم أولاً بتحويل string إلى byte[] باستخدام ترميز UTF-8:

// this is what I use in my app
CreateMd5HashString(Encoding.UTF8.GetBytes("abc"))
// result: 90150983CD24FB0D6963F7D28E17F72

الآن أود أن أكون قادرًا على تنفيذ وظيفة التجزئة هذه في SQL ، باستخدام دالة HASHBYTES ، لكنني حصلت على قيمة مختلفة:

print hashbytes('md5', N'abc')
-- result: 0xCE1473CF80C6B3FDA8E3DFC006ADC315

هذا لأن SQL يحسب MD5 تمثيل UTF-16 السلسلة. أحصل على نفس النتيجة في C # إذا قمت بإجراء CreateMd5HashString(Encoding.Unicode.GetBytes("abc")) .

لا يمكنني تغيير الطريقة التي يتم بها التجزئة في التطبيق.

هل هناك طريقة للحصول على SQL Server لحساب تجزئة MD5 بايت UTF-8 من السلسلة؟

لقد بحثت عن أسئلة مشابهة ، حاولت استخدام ترتيب ، لكن لم يحالفني الحظ حتى الآن.


تحتاج إلى إنشاء UDF لتحويل بيانات NVARCHAR إلى بايت في تمثيل UTF-8. لنقل أنه يسمى dbo.NCharToUTF8Binary ثم يمكنك القيام بما يلي:

hashbytes('md5', dbo.NCharToUTF8Binary(N'abc', 1))

فيما يلي UDF التي ستقوم بذلك:

create function dbo.NCharToUTF8Binary(@txt NVARCHAR(max), @modified bit)
returns varbinary(max)
as
begin
-- Note: This is not the fastest possible routine. 
-- If you want a fast routine, use SQLCLR
    set @modified = isnull(@modified, 0)
    -- First shred into a table.
    declare @chars table (
    ix int identity primary key,
    codepoint int,
    utf8 varbinary(6)
    )
    declare @ix int
    set @ix = 0
    while @ix < datalength(@txt)/2  -- trailing spaces
    begin
        set @ix = @ix + 1
        insert @chars(codepoint)
        select unicode(substring(@txt, @ix, 1))
    end

    -- Now look for surrogate pairs.
    -- If we find a pair (lead followed by trail) we will pair them
    -- High surrogate is \uD800 to \uDBFF
    -- Low surrogate  is \uDC00 to \uDFFF
    -- Look for high surrogate followed by low surrogate and update the codepoint   
    update c1 set codepoint = ((c1.codepoint & 0x07ff) * 0x0800) + (c2.codepoint & 0x07ff) + 0x10000
    from @chars c1 inner join @chars c2 on c1.ix = c2.ix -1
    where c1.codepoint >= 0xD800 and c1.codepoint <=0xDBFF
    and c2.codepoint >= 0xDC00 and c2.codepoint <=0xDFFF
    -- Get rid of the trailing half of the pair where found
    delete c2 
    from @chars c1 inner join @chars c2 on c1.ix = c2.ix -1
    where c1.codepoint >= 0x10000

    -- Now we utf-8 encode each codepoint.
    -- Lone surrogate halves will still be here
    -- so they will be encoded as if they were not surrogate pairs.
    update c 
    set utf8 = 
    case 
    -- One-byte encodings (modified UTF8 outputs zero as a two-byte encoding)
    when codepoint <= 0x7f and (@modified = 0 OR codepoint <> 0)
    then cast(substring(cast(codepoint as binary(4)), 4, 1) as varbinary(6))
    -- Two-byte encodings
    when codepoint <= 0x07ff
    then substring(cast((0x00C0 + ((codepoint/0x40) & 0x1f)) as binary(4)),4,1)
    + substring(cast((0x0080 + (codepoint & 0x3f)) as binary(4)),4,1)
    -- Three-byte encodings
    when codepoint <= 0x0ffff
    then substring(cast((0x00E0 + ((codepoint/0x1000) & 0x0f)) as binary(4)),4,1)
    + substring(cast((0x0080 + ((codepoint/0x40) & 0x3f)) as binary(4)),4,1)
    + substring(cast((0x0080 + (codepoint & 0x3f)) as binary(4)),4,1)
    -- Four-byte encodings 
    when codepoint <= 0x1FFFFF
    then substring(cast((0x00F0 + ((codepoint/0x00040000) & 0x07)) as binary(4)),4,1)
    + substring(cast((0x0080 + ((codepoint/0x1000) & 0x3f)) as binary(4)),4,1)
    + substring(cast((0x0080 + ((codepoint/0x40) & 0x3f)) as binary(4)),4,1)
    + substring(cast((0x0080 + (codepoint & 0x3f)) as binary(4)),4,1)

    end
    from @chars c

    -- Finally concatenate them all and return.
    declare @ret varbinary(max)
    set @ret = cast('' as varbinary(max))
    select @ret = @ret + utf8 from @chars c order by ix
    return  @ret

end

لا يدعم SQL Server أصلاً استخدام سلاسل UTF-8 ، وهو لا يعمل لفترة طويلة . كما لاحظت ، يستخدم NCHAR و NVARCHAR UCS-2 بدلاً من UTF-8 .

إذا كنت مصراً على استخدام دالة HASHBYTES ، فيجب أن تكون قادرًا على تمرير byte[] UTF-8 byte[] كـ VARBINARY من رمز C # الخاص بك للحفاظ على الترميز. HASHBYTES يقبل VARBINARY بدلاً من NVARCHAR . يمكن تحقيق ذلك باستخدام دالة CLR تقبل NVARCHAR وتُرجع نتائج Encoding.UTF8.GetBytes كـ VARBINARY .

مع ما يقال ، أقترح بشدة الحفاظ على هذه الأنواع من قواعد العمل معزولة داخل التطبيق الخاص بك بدلاً من قاعدة البيانات. خاصة وأن التطبيق يقوم بالفعل بتنفيذ هذا المنطق.





sql-server-2008-r2