I received an email asking for a hand with a C# .NET implementation of the WSSE atom authentication for Nokia’s Lifeblog.
I took a look at this as I’m brushing up on my C# at the moment for work, and I relish a challenge.
As there are two different methods of authentication depending on the version of Lifeblog being used, the code has to handle this.
The first method is to append the nonce, timestamp and password together, create a SHA1 hash with this and Base64 encode the result. The second way is almost the same, but the nonce has to be Base64 decoded first before being appended with the timestamp and password.
See my previous article on WSSE Authentication For Atom Using Perl for more details on how this works.
The first method can be calculate a digest value using code like this.
string mystring = nonce + created + password;
SHA1Managed SHhash = new SHA1Managed();
string mydigest = Convert.ToBase64String(SHhash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(mystring)));
My first attempt at the second method looked very similar.
string mystring = (System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(nonce))) + created + password;
SHA1Managed SHhash = new SHA1Managed();
string mydigest = Convert.ToBase64String(SHhash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(mystring)));
Looks OK on the surface, but it fails to validate when posting from my Nokia N93 phone.
What’s gone wrong?
Well looking in the debugger, the process of converting the nonce back from the array of bytes Convert.FromBase64String()
produces and appending them to form mystring corrupts the data compared with the Perl version in my previous article.
We know the approach is basically right, so we need to cut out this corrupting step.
The best way to do this is to keep the data in byte arrays as we need the data in this format anyway to computer the SHA1.
System.Text.Encoding enc = System.Text.Encoding.UTF8;
byte[] noncebytes = enc.GetBytes(nonce);
byte[] passwordbytes = enc.GetBytes(password);
byte[] createdbytes = enc.GetBytes(created);
byte[] code = new byte[nonce.Length + password.Length + created.Length];
Array.Copy(nonce, code, nonce.Length);
Array.Copy(created, 0, code, nonce.Length, created.Length);
Array.Copy(password, 0, code, nonce.Length + created.Length, password.Length);
System.Security.Cryptography.SHA1Managed SHhash = new System.Security.Cryptography.SHA1Managed();
string digest = Convert.ToBase64String(SHhash.ComputeHash(code));
I’m sure there is a nicer way to achieve the appending of the 3 arrays together in C#, but this works for now.
We can tie this up to form a nice static class to handle our WSSE authentication.
/// <summary>
/// Computes WSSE codes from input for validation.
/// </summary>
public static class WSSE {
/// <summary>
/// Calculate the digest.
/// </summary>
/// <param name="password">A string with your unencrypted password.</param>
/// <param name="nonce">The nonce the digest is to be created with as a string.</param>
/// <param name="created">The timestamp as a string.</param>
/// <returns>A string containing the digest.</returns>
public static string GetDigest(string password, string nonce, string created)
{
return GetDigest(password, nonce, created, System.Text.Encoding.ASCII);
}
/// <summary>
/// Calculate the digest.
/// </summary>
/// <param name="password">A string with your unencrypted password.</param>
/// <param name="nonce">The nonce the digest is to be created with as a string.</param>
/// <param name="created">The timestamp as a string.</param>
/// <param name="enc">The System.Text.Encoding to use.</param>
/// <returns>A string containing the digest.</returns>
public static string GetDigest(string password, string nonce, string created, System.Text.Encoding enc)
{
byte[] noncebytes = enc.GetBytes(nonce);
byte[] passwordbytes = enc.GetBytes(password);
byte[] createdbytes = enc.GetBytes(created);
return generatecode(noncebytes, passwordbytes, createdbytes);
}
/// <summary>
/// Calculate the alternative digest.
/// </summary>
/// <param name="password">A string with your unencrypted password.</param>
/// <param name="nonce">The nonce the digest is to be created with as a string.</param>
/// <param name="created">The timestamp as a string.</param>
/// <returns>A string containing the digest.</returns>
public static string GetDigestAlt(string password, string nonce, string created)
{
return GetDigestAlt(password, nonce, created, System.Text.Encoding.ASCII);
}
/// <summary>
/// Calculate the alternative digest.
/// </summary>
/// <param name="password">A string with your unencrypted password.</param>
/// <param name="nonce">The nonce the digest is to be created with as a string.</param>
/// <param name="created">The timestamp as a string.</param>
/// <param name="enc">The System.Text.Encoding to use.</param>
/// <returns>A string containing the digest.</returns>
public static string GetDigestAlt(string password, string nonce, string created, System.Text.Encoding enc)
{
byte[] noncebytes = Convert.FromBase64String(nonce);
byte[] passwordbytes = enc.GetBytes(password);
byte[] createdbytes = enc.GetBytes(created);
return generatecode(noncebytes, passwordbytes, createdbytes);
}
private static string generatecode(byte[] nonce, byte[] password, byte[] created)
{
byte[] code = new byte[nonce.Length + password.Length + created.Length];
Array.Copy(nonce, code, nonce.Length);
Array.Copy(created, 0, code, nonce.Length, created.Length);
Array.Copy(password, 0, code, nonce.Length + created.Length, password.Length);
System.Security.Cryptography.SHA1Managed SHhash = new System.Security.Cryptography.SHA1Managed();
return Convert.ToBase64String(SHhash.ComputeHash(code));
}
/// <summary>
/// Validates password, nonce and created create the same digest code as digest.
/// </summary>
/// <param name="password">A string with your unencrypted password.</param>
/// <param name="nonce">The nonce the digest is to be created with as a string.</param>
/// <param name="created">The timestamp as a string.</param>
/// <param name="digest">The digest to validate the password, nonce and created strings against as a string.</param>
/// <returns>true or false depending on wether the digest validates</returns>
public static bool IsValid(string password, string nonce, string created, string digest)
{
return digest == GetDigest(password, nonce, created) || digest == GetDigestAlt(password, nonce, created);
}
/// <summary>
/// Validates password, nonce and created create the same digest code as digest.
/// </summary>
/// <param name="password">A string with your unencrypted password.</param>
/// <param name="nonce">The nonce the digest is to be created with as a string.</param>
/// <param name="created">The timestamp as a string.</param>
/// <param name="digest">The digest to validate the password, nonce and created strings against as a string.</param>
/// <param name="enc">A System.Text.Encoding encoding.</param>
/// <returns>true or false depending on wether the digest validates.</returns>
public static bool IsValid(string password, string nonce, string created, string digest, System.Text.Encoding enc)
{
return digest == GetDigest(password, nonce, created, enc) || digest == GetDigestAlt(password, nonce, created, enc);
}
}
We can test the code using the following code snippet.
string digest = "nvvHvNuLb+7wGFrop+cC2tjgQqs=";
string nonce = "bgZ4BHcjmWcg7gVhCxyQOg==";
string timestamp = "2006-04-25T19:40:45Z";
string password = "a";
if (WSSE.IsValid(password, nonce, timestamp, digest, System.Text.Encoding.UTF8))
{
Console.WriteLine("valid");
}
else
{
Console.WriteLine("invalid");
}
This gives us the result “valid” as we’d hope.
To save you time, feel free to download the C# code
WSSE.cs.