Generate ASP.Net Membership password hash in pure T-SQL

后端 未结 5 1761
天命终不由人
天命终不由人 2020-12-31 10:32

I\'m attempting to create a pure t-sql representation of the default SHA-1 password hashing in the ASP.Net Membership system. Ideally, what I would get would be this:

<
相关标签:
5条回答
  • 2020-12-31 10:36

    OP requested "pure" sql - I think using CLR is cheating ;) I was stubborn and had to figure it out for myself so here's what I did.

    NOTE: Make a backup first!!

    Select * into dbo.aspnet_Membership_BACKUP from [dbo].[aspnet_Membership]
    

    Function to calculate the hashes:

    /*
        Create compatible hashes for the older style ASP.Net Membership
    
        Credit for Base64 encode/decode: http://stackoverflow.com/questions/5082345/base64-encoding-in-sql-server-2005-t-sql
    */
    Create Function dbo.AspNetHashCreate (@clearPass nvarchar(64), @encodedSalt nvarchar(64))
    Returns nvarchar(128)
    as
    begin
        declare @binSalt varbinary(128)
        declare @binPass varbinary(128)
    
        declare @result nvarchar(64)
    
        Select @binPass = CONVERT(VARBINARY(128), @clearPass)
    
        --  Passed salt is Base64 so decode to bin, then we'll combine/append it with password
        Select @binSalt = CAST(N'' as XML).value('xs:base64Binary(sql:column("bin"))','VARBINARY(128)') 
            from (Select @encodedSalt as bin) as temp;
    
        --  Hash the salt + pass, then convert to Base64 for the output
        Select @result = CAST(N'' as XML).value('xs:base64Binary(xs:hexBinary(sql:column("bin")))', 'NVARCHAR(64)')
            from (Select HASHBYTES('SHA1', @binSalt + @binPass) as bin) as temp2;
    
        --  Debug, check sizes
        --Select DATALENGTH(@binSalt), DATALENGTH(@binPass), DATALENGTH(@binSalt + @binPass)
    
        return @result
    end
    

    I was changing a Membership database from "clear" passwords to the more secure hashed format - call it like this:

    Update [dbo].[aspnet_Membership] set PasswordFormat = 1, Password = dbo.AspNetHashCreate(password, PasswordSalt) where PasswordFormat = 0
    

    Even with my database originally set to "clear" passwords, the salt values were created with each record, however, if for some reason you don't have salt values you can create them with this:

    /*
        Create compatible salts for the older style ASP.Net Membership (just a 16 byte random number in Base64)
    
        Note: Can't use newId() inside function so just call it like so: dbo.AspNetSaltCreate(newId())
    
        Credit for Base64 encode: http://stackoverflow.com/questions/5082345/base64-encoding-in-sql-server-2005-t-sql
    */
    Create Function dbo.AspNetSaltCreate (@RndId uniqueidentifier)
        Returns nvarchar(24)
    as
    begin
        return
            (Select CAST(N'' as XML).value('xs:base64Binary(xs:hexBinary(sql:column("bin")))', 'NVARCHAR(64)')
                from (select cast(@RndId as varbinary(16)) as bin) as temp)
    end
    

    Then use it like this:

    Update [dbo].[aspnet_Membership] set PasswordSalt = dbo.AspNetSaltCreate(newId()) where PasswordSalt = ''
    

    Enjoy!

    0 讨论(0)
  • 2020-12-31 10:46

    According to this SO post, this is the process they use to encode/hash your password/salt.

    public string EncodePassword(string pass, string salt)
    {
        byte[] bytes = Encoding.Unicode.GetBytes(pass); //HERE
        byte[] src = Encoding.Unicode.GetBytes(salt); //and HERE
        byte[] dst = new byte[src.Length + bytes.Length];
        Buffer.BlockCopy(src, 0, dst, 0, src.Length);
        Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
        HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
        byte[] inArray = algorithm.ComputeHash(dst); //then they has the bytes not the string...
        return Convert.ToBase64String(inArray);
    }
    

    I could be wrong but it looks like you are missing the step where you get the bytes for the password and salt. Can you try adding that and see if it works?

    0 讨论(0)
  • 2020-12-31 10:48

    Instead of using CLR you can create this function in SQL. On this page you will find very nice example:

    http://svakodnevnica.com.ba/index.php?option=com_kunena&func=view&catid=4&id=4&Itemid=5&lang=en#7

    P.S. byte[] src = Convert.FromBase64String(salt); is correct way...

    Fox

    0 讨论(0)
  • 2020-12-31 10:51

    I wrote a hashing stored proc by reverse enginering the C# code from here ASP.NET Identity default Password Hasher, how does it work and is it secure? and some fantastic PBKDF2 SQL functions from here Is there a SQL implementation of PBKDF2?

    First create these two functions taken from Is there a SQL implementation of PBKDF2?

    create FUNCTION [dbo].[fn_HMAC]
    (
            @hash_algorithm varchar(25),
            @key VARCHAR(MAX),
            @message VARCHAR(MAX)
    )
    RETURNS VARCHAR(MAX)
    
    
    AS
    BEGIN
        --HASH key if longer than 16 characters
        IF(LEN(@key) >64)
            SET @key = HASHBYTES(@hash_algorithm,@key)
    
    
        DECLARE @i_key_pad VARCHAR(MAX), @o_key_pad VARCHAR(MAX), @position INT
            SET @position = 1
            SET @i_key_pad = ''
            SET @o_key_pad = ''
    
        --splice ipad & opod with key
        WHILE @position <= LEN(@key)
           BEGIN
            SET @i_key_pad = @i_key_pad + CHAR(ASCII(SUBSTRING(@key, @position, 1)) ^ 54) 
            SET @o_key_pad = @o_key_pad + CHAR(ASCII(SUBSTRING(@key, @position, 1)) ^ 92) 
            SET @position = @position + 1
           END 
    
        --pad i_key_pad & o_key_pad
            SET @i_key_pad = LEFT(@i_key_pad + REPLICATE('6',64),64)
            SET @o_key_pad = LEFT(@o_key_pad + REPLICATE('\',64),64)
    
    
    RETURN HASHBYTES(@hash_algorithm,CONVERT(VARBINARY(MAX),@o_key_pad) + HASHBYTES(@hash_algorithm,@i_key_pad + @message))
    
    END
    
    GO
    

    and

    CREATE function [dbo].[fn_PBKDF2] 
    (
    @hash_algorithm varchar(25),
    @password varchar(max),
    @salt varchar(max),
    @rounds int,
    @outputbytes int
    )
    returns varchar(max)
    as
    begin
    
    declare @hlen int
    select @hlen = len(HASHBYTES(@hash_algorithm, 'test'))
    declare @l int 
    SET @l = (@outputbytes +@hLen -1)/@hLen
    declare @r int 
    SET @r = @outputbytes - (@l - 1) * @hLen
    declare @t varchar(max), @u varchar(max), @block1 varchar(max)
    
    declare @output varchar(max) 
    SET @output = ''
    
    declare @i int 
    SET @i = 1
    while @i <= @l
        begin
        set @block1 = @salt +cast(cast(@i as varbinary(4)) as varchar(4))
        set @u = dbo.fn_HMAC(@hash_algorithm,@password,@block1)
        set @t = @u
    
        declare @j int 
        SET @j = 1
        while @j < @rounds
            begin
            set @u = dbo.fn_HMAC(@hash_algorithm,@password,@u)
    
    
            declare @k int 
            SET @k = 0 
            DECLARE @workstring varchar(max) 
            SET @workstring = ''
            while @k < @hLen
                begin
                set @workstring = @workstring + char(ascii(substring(@u,@k+1,1))^ascii(substring(@t,@k+1,1)))
                set @k = @k + 1
                end
            set @t = @workstring
            set @j = @j + 1
            end
    
            select @output = @output + case when @i = @l then left(@t,@r) else @t end
        set @i = @i + 1
        end
    
      return master.dbo.fn_varbintohexstr(convert(varbinary(max), @output ))
    
    
    end
    GO
    

    then create the stored proc to generate the hash password

    CREATE PROCEDURE [dbo].[EncryptPassword2]
        @passwordIn AS VARCHAR(MAX),
        @passwordOut VARCHAR(max) OUTPUT
    AS
    
            -- Generate 16 byte salt
        DECLARE @saltVarBin VARBINARY(max)
        SET @saltVarBin = (SELECT CAST(newid() AS binary(16)))
    
        -- Base64 encode the salt
        DECLARE @saltOut VARCHAR(max)
        SET @saltOut = cast('' as xml).value('xs:base64Binary(sql:variable("@saltVarBin"))', 'varchar(max)')
    
        -- Decode salt to pass to function fn_PBKDF2
        DECLARE @decodedsalt varchar(max)
        SET @decodedsalt = convert(varchar(max),(SELECT CAST('' as xml).value('xs:base64Binary(sql:variable("@saltOut"))', 'varbinary(max)')))
    
        -- Build the password binary string from 00 + salt binary string + password binary string created by 32 byte 1000 iteration ORC_PBKDF2 hashing
        DECLARE @passwordVarBinStr VARCHAR(max)
        -- Identity V1.0 and V2.0 Format: { 0x00, salt, subkey } 
        SET @passwordVarBinStr = '0x00' + REPLACE(master.dbo.fn_varbintohexstr(@saltVarBin) + (SELECT dbo.fn_PBKDF2('sha1', @passwordIn, @decodedsalt, 1000, 32)),'0x','')
        -- Identity V3.0 Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } (comment out above line and uncomment below line)
        --SET @passwordVarBinStr = '0x01000000010000271000000010' + REPLACE(master.dbo.fn_varbintohexstr(@saltVarBin) + (SELECT dbo.fn_PBKDF2('SHA2_256', @passwordIn, @decodedsalt,10000, 32)),'0x','')
    
    
        -- Convert the password binary string to base 64
        DECLARE @passwordVarBin VARBINARY(max)
        SET @passwordVarBin =  (select cast('' as xml).value('xs:hexBinary( substring(sql:variable("@passwordVarBinStr"), sql:column("t.pos")) )', 'varbinary(max)') from (select case substring(@passwordVarBinStr, 1, 2) when '0x' then 3 else 0 end) as t(pos))
        SET @passwordOut = cast(''as xml).value('xs:base64Binary(sql:variable("@passwordVarBin"))', 'varchar(max)')
    
    RETURN
    

    Finally execute the stored proc using

    DECLARE @NewPassword varchar(100)
    DECLARE @EncryptPassword VARCHAR(max)
    
    select @NewPassword = 'password12344'                                           
    
    EXECUTE EncryptPassword2 @NewPassword, @PasswordOut = @EncryptPassword OUTPUT;
    
    PRINT @EncryptPassword
    

    Please note that the stored proc may need to be changed for later versions of SQL server as this was written specifically for 2005 and I belive conversion to base64 is different in later versions.

    0 讨论(0)
  • 2020-12-31 10:54

    if you are running 2005 or higher, you can create a CLR (.NET) UDF:

    [SqlFunction(
      IsDeterministic = true, IsPrecise = true, 
      DataAccess = DataAccessKind.None,
      SystemDataAccess = SystemDataAccessKind.None
    )]
    public static string EncodePassword(string pass, string salt) {
      byte[] bytes = Encoding.Unicode.GetBytes(pass);
      byte[] src = Convert.FromBase64String(salt);
      byte[] dst = new byte[src.Length + bytes.Length];
      Buffer.BlockCopy(src, 0, dst, 0, src.Length);
      Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
      using (SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider()) {
        return Convert.ToBase64String(sha1.ComputeHash(dst));
      }
    }
    

    you need to include the following namespaces in your class:

    using Microsoft.SqlServer.Server;
    using System.Data.SqlTypes;
    

    the class must be public.

    build the .dll then run the following (per database you want to call the UDF) SQL statement:

    sp_configure 'clr enabled', 1
    GO
    RECONFIGURE
    GO
    
    IF OBJECT_ID (N'dbo.EncodePassword', N'FS') IS NOT NULL
      DROP FUNCTION dbo.EncodePassword;    
    IF EXISTS (SELECT name FROM sys.assemblies WHERE name='UDF')
    DROP ASSEMBLY UDF
    
    CREATE ASSEMBLY UDF FROM 'FULL_PATH_TO.dll' WITH PERMISSION_SET=SAFE    
    GO
    
    CREATE FUNCTION EncodePassword(
      @pass NVARCHAR(4000),
      @salt NVARCHAR(4000)
    )
    RETURNS NVARCHAR(4000)
    -- return NULL if any input parameter(s) are NULL
    WITH RETURNS NULL ON NULL INPUT
    AS
    EXTERNAL NAME UDF.[NAMESPACE.CLASSNAME].EncodePassword
    GO
    

    obviously, replace 'NAMESPACE.CLASSNAME' with the namespace (if any) and name of your class. and you might want to mess with the input parameter and return value sizes.

    then call the UDF with T-SQL:

    SELECT UserName,Password
    ,dbo.EncodePassword('PASSWORD', PasswordSalt) As TestPassword 
    FROM aspnet_Users U 
    JOIN aspnet_membership M ON U.UserID = M.UserID
    

    works for me :)

    0 讨论(0)
提交回复
热议问题