Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working algorithm for PasswordDigest in WS-Security

I'm having trouble with WS-Security, and creating a nonce and password digest that is correct.

I am successfully using SoapUI to send data to an Oracle system. So I'm able to intercept SoapUI's call (change proxy to 127.0.0.1 port 8888 to use Fiddler where it fails because it's over SSL) - intercepting is important because these values can only be used once. I can then grab the nonce, created timestamp and password digest put them into my code (I've only got 30 seconds to do this as the values don't last!) and I get a success.

So I know it's nothing else - just the Password Digest.

The values I use are the following:

Nonce: UIYifr1SPoNlrmmKGSVOug==
Created Timestamp: 2009-12-03T16:14:49Z
Password: test8
Required Password Digest: yf2yatQzoaNaC8BflCMatVch/B8=

I know the algorithm for creating the Digest is:

Password_Digest = Base64 ( SHA-1 ( nonce + created + password ) )

using the following code (from Rick Strahl's post)

protected string GetSHA1String(string phrase)
{
    SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
    byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
    return Convert.ToBase64String(hashedDataBytes);
}

I get:

GetSHA1String("UIYifr1SPoNlrmmKGSVOug==" + "2009-12-03T16:14:49Z" + "test8") = "YoQKI3ERlMDGEXHlztIelsgL50M="

I have tried various SHA1 methods, all return the same results (which is a good thing I guess!):

SHA1 sha1 = SHA1.Create();
SHA1 sha1 = SHA1Managed.Create();

// Bouncy Castle:
protected string GetSHA1usingBouncyCastle(string phrase)
{
    IDigest digest = new Sha1Digest();
    byte[] resBuf = new byte[digest.GetDigestSize()];
    byte[] bytes = Encoding.UTF8.GetBytes(phrase);
    digest.BlockUpdate(bytes, 0, bytes.Length);
    digest.DoFinal(resBuf, 0);
    return Convert.ToBase64String(resBuf);
}

Any ideas on how to get the correct hash?

like image 808
Matt Kemp Avatar asked Oct 17 '13 21:10

Matt Kemp


1 Answers

The problem was the nonce.

I was trying to use a nonce that had already been Base64 encoded. If you want to use a Nonce that is in the form "UIYifr1SPoNlrmmKGSVOug==" then you need to decode it.

Convert.FromBase64String("UIYifr1SPoNlrmmKGSVOug==") which is a byte array.

So we need a new method:

public string CreatePasswordDigest(byte[] nonce, string createdTime, string password)
{
    // combine three byte arrays into one
    byte[] time = Encoding.UTF8.GetBytes(createdTime);
    byte[] pwd = Encoding.UTF8.GetBytes(password);
    byte[] operand = new byte[nonce.Length + time.Length + pwd.Length];
    Array.Copy(nonce, operand, nonce.Length);
    Array.Copy(time, 0, operand, nonce.Length, time.Length);
    Array.Copy(pwd, 0, operand, nonce.Length + time.Length, pwd.Length);

    // create the hash
    var sha1Hasher = new SHA1CryptoServiceProvider();
    byte[] hashedDataBytes = sha1Hasher.ComputeHash(operand);
    return Convert.ToBase64String(hashedDataBytes);
}

CreatePasswordDigest(Convert.FromBase64String("UIYifr1SPoNlrmmKGSVOug=="), "2009-12-03T16:14:49Z", "test8")

which returns yf2yatQzoaNaC8BflCMatVch/B8= as we want.

Remember to use the same createdTime in the digest as you put in the XML, this might sound obvious, but some people include milliseconds on their timestamps and some don't - it doesn't matter, it just needs to be consistent.

Also the Id field in the UsernameToken XML doesn't matter - it doesn't need to change.

Here's a method to create a Nonce like the one above, if you don't want to use GUIDs like Rick uses:

private byte[] CreateNonce()
{
    var Rand = new RNGCryptoServiceProvider();
    //make random octets
    byte[] buf = new byte[0x10];
    Rand.GetBytes(buf);
    return buf;
}

I hope that helps someone - it took me lots of frustration, trial and error, searching web pages, and general head/wall banging.

like image 141
Matt Kemp Avatar answered Nov 13 '22 18:11

Matt Kemp