Search code examples
sql-server-2005coldfusionasp.net-membershipcoldfusion-9

Membership SHA1 hash not the same for all users


I have a user table that was in plain text and migrated it to Membership provider.

Using ColdFusion (current system) I managed to HASH one user's password (test user) and it matched perfectly. But now the subsequent users do not match. What am I doing wrong.

<cfscript>
    theEncoding = "UTF-16LE";
    thePassword = "dtD3v310p3r!";
    base64Salt = "JZjdzUXREM0A7DPI3FV3iQ==";
    theSalt = charsetEncode( binaryDecode(base64Salt, "base64"), theEncoding );
    theHash = hash(theSalt & thePassword, "SHA1", theEncoding);

    // hash always returns hex. convert it to base64 so it matches DNN
    theBase64Hash = binaryEncode(binaryDecode(theHash, "hex"), "base64");
    WriteOutput("<br />theBase64Hash= "& theBase64Hash &"<br/>");
    WriteOutput("DBPassword= 5khDDMmoFtW+j99r/whE/TjyIUo= <br />");


    theEncoding = "UTF-16LE";
    thePassword = "DT!@12";
    base64Salt = "+muo6gAmjvvyy5doTdjyaA==";
    theSalt = charsetEncode( binaryDecode(base64Salt, "base64"), theEncoding );
    theHash = hash(theSalt & thePassword, "SHA1", theEncoding);

    // hash always returns hex. convert it to base64 so it matches DNN
    theBase64Hash = binaryEncode(binaryDecode(theHash, "hex"), "base64");
    WriteOutput("<br />theBase64Hash= "& theBase64Hash &"<br/>");
    WriteOutput("DBPassword= nfcqQBgeAm0Dp1oGZI0O70Y6DvA= <br />");
</cfscript>

The first one works 100%. But the second one doesn't. The second one produces a Hash value of 86SrPKXW5xywDYoC8MVy0q259sQ=


Solution

  • Hm.. I think something may be going wrong when the two values are concatenated. The hashing should really use a byte array, like with the encrypt version, but unfortunately CF9's hash() function does not support it - only strings. (Though poorly documented, it is supported in CF11). I am not sure if there is a pure CF work-around for CF9. However, in the mean time you could use java directly:

    <cfscript>
        thePassword = "DT!@12";
        base64Salt = "+muo6gAmjvvyy5doTdjyaA==";
    
        // extract bytes of the salt and password
        saltBytes = binaryDecode(base64Salt, "base64");
        passBytes = charsetDecode(thePassword, "UTF-16LE" );
    
        // next combine the bytes. note, the returned arrays are immutable, 
        // so we cannot use the standard CF tricks to merge them    
        ArrayUtils = createObject("java", "org.apache.commons.lang.ArrayUtils");
        dataBytes = ArrayUtils.addAll( saltBytes, passBytes );
    
        // hash binary using java
        MessageDigest = createObject("java", "java.security.MessageDigest").getInstance("SHA-1");
        MessageDigest.update(dataBytes);    
        theBase64Hash = binaryEncode(MessageDigest.digest(), "base64");
    
        WriteOutput("<br />theBase64Hash= "& theBase64Hash &"<br/>");
        WriteOutput("DBPassword= nfcqQBgeAm0Dp1oGZI0O70Y6DvA= <br />");
    </cfscript>
    

    Update:

    After looking around further, I do not think there is pure CF solution. The UTF-16LE encoding is only part of the problem. The other issue is that DNN decodes each string separately, which may produce different bytes than when both are decoded as a single string (see comparison below). It does in the case of your second password, which is why the final hash is different. Since hash will not accept byte arrays, I do not think it is the right tool for this job. MessageDigest is the way to go.

    Byte Array Comparison

               old|   new | 
       1 |     -6 |    -6 | 
       2 |    107 |   107 | 
       3 |    -88 |   -88 | 
       4 |    -22 |   -22 | 
       5 |      0 |     0 | 
       6 |     38 |    38 | 
       7 |   -114 |  -114 | 
       8 |     -5 |    -5 | 
       9 |    -14 |   -14 | 
      10 |    -53 |   -53 | 
      11 |   -105 |  -105 | 
      12 |    104 |   104 | 
      13 |     -3 |    77 | **
      14 |     -1 |   -40 | **
      15 |     68 |   -14 | **
      16 |      0 |   104 | **
      17 |     84 |    68 | **
      18 |      0 |     0 | 
      19 |     33 |    84 | **
      20 |      0 |     0 | 
      21 |     64 |    33 | **
      22 |      0 |     0 | 
      23 |     49 |    64 | **
      24 |      0 |     0 | 
      25 |     50 |    49 | **
      26 |      0 |     0 | 
      27 |        |    50 | **
      28 |        |     0 | **
    
    • old => charsetDecode( theSalt & thePassword, "UTF-16LE")
    • new => ArrayUtils.addAll( saltBytes, passBytes );