One of the things I’ve recently been working on is a solution to allow both a .NET application and a legacy VBScript / Classic ASP to be able to validate a specific username / password combination, comparing two hashed passwords. In the process, I discovered (with the help of Google and StackOverflow) that accessing .NET objects from Classic ASP isn’t really all that hard! But to the issue at hand: Password Hashing! Sorting this out from the .NET side was relatively easy. For the purpose of this post, here is roughly what I have at the moment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security.Cryptography; namespace Amadiere.Com.Utilities { public static class Cryptography { /// <summary> /// Encrypts a password based on the passed in Encryption method (SHA512Managed is a good starting /// point if you don't know which to use). /// </summary> /// <remarks> /// The passwordSalt parameter is required to ensure that rainbow tables cannot be used to /// lookup all usernames if the salt is discovered. This salt is OK to store in the database, /// along with the hashedPassword generated by this function. /// </remarks> /// <param name="password">The password to be encrypted.</param> /// <param name="passwordSalt">The individual grain of salt for that password.</param> /// <param name="method">The method by which to encrypt.</param> /// <returns>An string representing the hashed password (88 characters long).</returns> public static string Hash(string password, string passwordSalt, HashMethods method) { string siteWideSalt = "THIS IS A SITE WIDE SALT, BUT COULD BE A GUID"; string encryptedPassword; switch (method) { default: encryptedPassword = HashSHA512Managed(siteWideSalt + password + passwordSalt); break; } return encryptedPassword; } /// <summary> /// One-way encrypts the password into oblivion. If the same password and salt are provided, the /// same end string will be churned out the other end of this sausage machine. /// </summary> /// <see cref="http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha512managed.aspx"/> /// <example>HashSHA512Managed("bobsYourUncle_SALT-GOES-HERE");</example> /// <param name="password">Unencoded pre-salted password.</param> /// <returns>An 88 character string, representing the originally encoded password.</returns> private static string HashSHA512Managed(string saltedPassword) { UnicodeEncoding uniEncode = new UnicodeEncoding(); SHA512Managed sha = new SHA512Managed(); byte[] bytePassword = uniEncode.GetBytes(saltedPassword); byte[] hash = sha.ComputeHash(bytePassword); return Convert.ToBase64String(hash); } } public enum HashMethods { SHA512 } } |
The Classic ASP side of things wasn’t as easy. There are no built in libraries for SHA512 (or in fact, many other password hashing algorithms). So I had a few options on how I was to proceed:
- Abandon my choice of SHA512 and go with MD5 where there seemed to be a bit more usage in the community. I was reluctant to do this because it isn’t as good as SHA512.
- Copy some code sample that has been created by someone else, that I either have to accept is OK, or spend a good deal of time understanding it and breaking it down bit by bit.
- Create a custom COM object in .NET, register it in the GAC and reference that via Classic ASP.
- Access the .NET functions directly from Classic ASP.
The last option won – because it worked, and because it meant a lot lessĀ maintenanceĀ and praying for things to keep working. This is the code that pretty much does the same as the above .NET code, but in VBScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Function Hash(strPassword, strIndividualSalt) Const strSiteWideSalt = "THIS IS A SITE WIDE SALT, BUT COULD BE A GUID" Hash = HashSHA512Managed(strSiteWideSalt & strPassword & strIndividualSalt) End Function Function HashSHA512Managed(saltedPassword) Dim objMD5, objUTF8 Dim arrByte Dim strHash Set objUnicode = CreateObject("System.Text.UnicodeEncoding") Set objSHA512 = Server.CreateObject("System.Security.Cryptography.SHA512Managed") arrByte = objUnicode.GetBytes_4(saltedPassword) strHash = objSHA512.ComputeHash_2((arrByte)) HashSHA512Managed = ToBase64String(strHash) End Function Function ToBase64String(rabyt) 'Ref: http://stackoverflow.com/questions/1118947/converting-binary-file-to-base64-string Dim xml: Set xml = CreateObject("MSXML2.DOMDocument.3.0") xml.LoadXml "" xml.documentElement.dataType = "bin.base64" xml.documentElement.nodeTypedValue = rabyt ToBase64String = Replace(xml.documentElement.Text,VbLf, "") End Function |
As you can see in the VBScript example, I can create the .NET objects as I would any other type of object in VBScript, the difference comes in how I use them. Normally, in C#, you’d simply use ComputeHash() and there would be a number of overloads for you to choose from. As VBScript doesn’t have the concept of overloading, you have to use a crazy-mad way of accessing the specific overload you want – using an underscore. I’ve not really come up with a full-proof way of working out which is which (though, if I’m honest, I didn’t try much). I did however find out that they started ComputeHash(), ComputeHash_1() and ComputeHash_2() – I assume the numbers resemble the order they appear in Visual Studio when using intellisense for C# – but trial and error is normally good enough.
Hope this is of use to someone else! If anything, I’m sure I’ll find a need to do this again someday and I’m sure it’ll be useful for then! All this hashing has made me hungry!

Coding Horror



