Pergunta

Windows Phone 7 had a anonymous user ID property called ANID. Windows Phone 8 has replaced that with ANID2. The difference is that ANID2 is dependent on the app's publisher ID.

It's possible to convert ANID to ANID2 as the following code sample on MSDN shows. Only things you need are the original WP7 ANID and the publisher ID (guid). The problem is that the example is in C++. I've been trying to port it to C# but with no success.

The algorithm itself is quite easy:

var data = HMAC(ANID, publisherId) // Uses SHA-256
var result = ToBase64(data)

The problem is that I'm not able to get the results match. I've made sure that the C++ works correctly by creating two apps (WP7 and WP8), running them on same devices and then converting the ANID from WP7 app using the publisher GUID. On C++ the converted ANID2 and the ANID2 from the device match. On C# the converted ANID2 is something else.

The C# code is simple:

        var anidBytes = System.Text.Encoding.UTF8.GetBytes(this.anidBox.Text);
        var publisherGuid = Guid.Parse(this.publisherBox.Text.ToUpper());

        var macObject = new HMACSHA256(anidBytes);
        var hashed = macObject.ComputeHash(publisherGuid.ToByteArray());

        var result = Convert.ToBase64String(hashed);

The C++ version uses something called CNG (Cryptography API: Next Generation). Here's some code from it:

BCryptOpenAlgorithmProvider(&Algorithm, BCRYPT_SHA256_ALGORITHM, NULL, BCRYPT_ALG_HANDLE_HMAC_FLAG);
BCryptGetProperty(Algorithm,BCRYPT_OBJECT_LENGTH,reinterpret_cast<BYTE*>(&HashObjectLength),PropertyLength,&PropertyLength,0);
BCryptCreateHash(Algorithm, &Hash, HashObject, HashObjectLength, pAnidId, dwAnidLength, 0);
BCryptHashData(Hash, const_cast<BYTE*>(pPublisherId), dwPublisherIdLength, 0);
BCryptFinishHash(Hash, pUniqueId, GETDEVICEUNIQUEID_V1_OUTPUT, 0);

After which the "pUniqueId" is converter to Base64 using some custom built function.

Any help is appreciated.

Update: Visual Studio reports that the anidBytes (C#) and pAnidId (C++) (this is the ANID string converted to bytes) both have length of 44. This is how C# debugger reports the byte array:

C# Byte array

And here's the C++ debugger: C++ version

I don't know C++ so I'm not sure if these two are identical. They are, but the C++ has these '\0' chars after each and I'm not sure if that's OK. I think it is, as the length in both cases is reported as 44.

Another byte array comparison is the publisherGuid (C#) vs pPublisherId (C++) (publisher id as GUID). I think they again match. C#:

C# byte array

C++:

C++ byte array

If I then look at the output after the value has been crypted, I can see a difference:

On C# I get the output from this code:

        var macObject = new HMACSHA256(anidBytes);
        var hashed = macObject.ComputeHash(publisherBytes);

And the byte array looks like this:

C# crypt result

On C++ code, if I check the pUniqueId (result of BCryptFinishHash), I see this:

C++ hash result

The length seems to be the same in both of these cases but the outcome isn't.

On C#, if I change the encoding type from UTF8 to Unicode, the anidBytes byte array changes to this:

Unicode

So it's identical to what C++ debugger shows. Also the result changes but it still differs from C++. Here's the new C# outcome:

C# unicode results

This is the correct result from C++:

Correct result

Foi útil?

Solução

Make sure you cut the ANID byte array into half before you use it as the secret key for the HMACSHA256. The conversion algorithm only needs the first 44 bytes and not the entire 88. Don't ask me why, but AFAIK, it's a bug in the C++ code they use when converting ANID to ANID2 if you look at the sample code at http://code.msdn.microsoft.com/wpapps/ANID-to-ANID2-Converter-cc428038 - they must have mistaken wchar for char!

Outras dicas

In C#, for your HMACSHA256 object, try setting the Key property rather than initializing it in the constructor.

var anidBytes = System.Text.Encoding.Unicode.GetBytes(this.anidBox.Text);
var macObject = new HMACSHA256();
macObject.Key = anidBytes;

http://msdn.microsoft.com/en-us/library/9c9tf8wc.aspx says that the constructor pads the key to 64 bytes.

(I'm not 100% sure what the property setter does but it's worth a shot).

public string Convert(string winPhone7Anid)
{
    var anidAsBytes = System.Text.Encoding.Unicode.GetBytes("A=" + winPhone7Anid + "&E=f48&W=3");
    var macObject = new HMACSHA256(anidAsBytes.Take(anidAsBytes.Length / 2).ToArray());
    var hashedBytes = macObject.ComputeHash(new Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX").ToByteArray());
    return System.Convert.ToBase64String(hashedBytes);
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top