Playing with the DDI Character Builder API
24 January 2011 in Articles by Iain M NormanFor anyone who’s been playing with the two new web-services that the online version of WotC’s Character Builder uses, you may have tried remotely logging in, but wondered how the password needs to be encrypted?
Well I’ve cracked the way passwords are encrypted and have been able to login remotely and do simple actions such as, get a list of all my characters and export the XML for an old style DnD4e file.
Here’s a simple bit of C# showing how to encode a password.
public static byte[] SimpleEncrypt(string value, string key)
{
byte[] buffer2;
ICryptoTransform transform = GetSimpleAlgorithm(key).CreateEncryptor();
using (MemoryStream stream = new MemoryStream())
{
using (CryptoStream stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write))
{
byte[] bytes = Encoding.UTF8.GetBytes(value);
stream2.Write(bytes, 0, bytes.Length);
stream2.Flush();
stream2.FlushFinalBlock();
stream.Position = 0L;
buffer2 = stream.ToArray();
}
}
return buffer2;
}
private static SymmetricAlgorithm GetSimpleAlgorithm(string key)
{
AesManaged aes = new AesManaged();
byte[] source = new SHA256Managed().ComputeHash(Encoding.UTF8.GetBytes(key));
return new AesManaged { Key = source, IV = source.Take<byte>((aes.BlockSize / 8)).ToArray<byte>() };
}
As you can see this takes the username and password as inputs, if you’ve built a nice client wrapper around the SOAP webservice, something that Visual Studio does for me, then logging in is as simple as calling the login method on either of the web services.
contentClient.Login(username, SimpleEncrypt(password, username));
If we then use the GetAvailableContent() method of the ContentVault service, we can return and enumerate a list of characters.
ContentInfo[] content = contentClient.GetAvailableContent(0);
for (int i = 0; i < content.Length; i++)
{
XDocument doc = XDocument.Parse(content[i].CommittedContent.Details.ToString());
var name = doc.Element("CharacterDetails").Element("Name").Value;
Console.WriteLine("{0} : {1}", i, name);
}
Then using the GetData() method of the same service we can grab the DnD4e XML file.
DataWithVersion data = contentClient.GetData(
new ContentVault.ContentIdentifier { ContentID = content[0].CommittedContent.Identifier.ContentID },
null
);
RawContentBlob blob = data.Data as RawContentBlob;
String charFile = new UTF8Encoding().GetString(blob.RawData, 0, blob.RawData.Length);
File.WriteAllBytes("d:\\temp.dnd4e", blob.RawData);
There you go just a brief window into some of the things the ContentVault service can do.
No word from WotC on whether or not we’re allowed to do this though. I did ask, but never got a response.
One last note, if you’re doing this in Visual Studio then make sure the bindings for the web services have allowCookies set to true, then the login will be remembered for the subsequent calls to the service.
If you’re consuming the SOAP in some other not Microsoft way, then obviously you’ll need to do the same thing, save the cookie from the login response and send it on with any following requests.


[...] This post was mentioned on Twitter by RPG Bloggers Network, Iain M Norman. Iain M Norman said: More info on using the Character Builder API / Web services. http://is.gd/7qtTEm #DnD #DDI [...]
Ha! There seems to be someone roughly familiar with all those compendium stuff third party implementation thing! As I see some mobile apps and things going on I wondered how the legal situation is whith those APIs. I mean, clearly some of the apps use the compendium API. You mentioned that WotC does not clarify how the lega situation is for the new APIs. But do you know how the situation is for the “old” compendium API and other API that are available? I never found any information regarding that. If one could use them that could open up whole new possibilities.
The Compendium API once had instructions on how to use it on the wizards.com site, so I think that’s fairly safe to use. It is not zehr gut though, it’s just a search API, you can’t actually get API access to the compendium entries, just search results. The search results include the URI of the compendium entry, which you could programatically access with a DDI login, or ask for the users login. Both have issues. Masterplan did some scraping of the entries like this, and got into trouble for storing them. It’s okay it seems to get them when you need them, just not rebuild the database at your end.
Masterplan still exist and haven’t been sued into oblivion, so I’d say in summing up, try anything you like until you’re told to stop. Seems to be the only way to get them talking to you maybe?
Yeah looks like. Would be nice to have some openness and they frankly tell you what they want you to do and what not. Thanks for the info. Now I have at least some point to start from.
This is fantastic! Do you happen to have this code in VB?
I haven’t got it in VB no. Are you okay enough with C# syntax to convert it?
I’ve tried converting it from C# – but, it can’t negotiate the encryption correctly. If anyone has a VB version, it would be greatly appreciated
Public Shared Function SimpleEncrypt(value As String, key As String) As Byte()
Dim buffer2 As Byte()
Dim transform As ICryptoTransform = GetSimpleAlgorithm(key).CreateEncryptor()
Using stream As New MemoryStream()
Using stream2 As New CryptoStream(stream, transform, CryptoStreamMode.Write)
Dim bytes As Byte() = Encoding.UTF8.GetBytes(value)
stream2.Write(bytes, 0, bytes.Length)
stream2.Flush()
stream2.FlushFinalBlock()
stream.Position = 0L
buffer2 = stream.ToArray()
End Using
End Using
Return buffer2
End Function
Private Shared Function GetSimpleAlgorithm(key As String) As SymmetricAlgorithm
Dim aes As New AesManaged()
Dim source As Byte() = New SHA256Managed().ComputeHash(Encoding.UTF8.GetBytes(key))
Return New AesManaged() With { _
Key .Key = source, _
Key .IV = source.Take(Of Byte)((aes.BlockSize / 8)).ToArray(Of Byte)() _
}
End Function
Hopefully that helps. I haven’t tested though.
Thanks! I’ll give it a whirl tonight!
Ok… I made some changes (since it wasn’t quite VB friendly)… but still can’t get it to work. Here’s the code (it compiles) – but won’t return a valid login:
Public Shared Function SimpleEncrypt(ByVal value As String, ByVal key As String) As Byte()
Dim buffer2 As Byte()
Dim Transform As ICryptoTransform
Transform = GetSimpleAlgorithm(key).CreateEncryptor
Dim stream As New MemoryStream()
Dim stream2 As New CryptoStream(stream, Transform, CryptoStreamMode.Write)
Dim bytes As Byte()
Using stream
bytes = Encoding.UTF8.GetBytes(value)
stream2.Write(bytes, 0, bytes.Length)
stream2.Flush()
stream2.FlushFinalBlock()
stream.Position = 0L
buffer2 = stream.ToArray()
End Using
Return buffer2
End Function
Private Shared Function GetSimpleAlgorithm(ByVal key As String) As SymmetricAlgorithm
Dim aes As New AesManaged()
Dim sha As SHA256Managed = New SHA256Managed()
Dim source As Byte() = sha.ComputeHash(Encoding.UTF8.GetBytes(key))
Dim myMgr As New AesManaged
myMgr.IV = source.Take(aes.BlockSize / 8).ToArray()
myMgr.Key = source
Return myMgr
End Function
Any ideas?
Mail me a zip of your solution and I’ll take a look.
Thanks Iain! It works like a charm! I’m making a Windows 8 character viewer. I’ll try to send you the end result
.
A metro style app?
Yes, though I converted the code to WinRT and have a COMException on the Encrypt line. It seems to me like a bug (I sent it to Microsoft):
private static byte[] SimpleEncrypt(string value, string key)
{
var simpleAlgorithm = GetSimpleAlgorithm(key);
var encryptedBuffer = CryptographicEngine.Encrypt(simpleAlgorithm.Item1,
CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8),
simpleAlgorithm.Item2);
//bug here with CBC
var result = new byte[encryptedBuffer.Length];
encryptedBuffer.CopyTo(result);
return result;
}
private static Tuple GetSimpleAlgorithm(string key)
{
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(“AES_CBC”);
var keyAsBinary = CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8);
var source = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256).HashData(keyAsBinary);
var shortKey = CryptographicBuffer.CreateFromByteArray(UTF8Encoding.UTF8.GetBytes(key).Take((int)provider.BlockLength).ToArray());
return new Tuple(provider.CreateSymmetricKey(source), shortKey);
}
What are the web service urls
I don’t remember off hand. A quick check with fiddler or something will tell you.
By the way, in the aforementioned Windows 8 code I think the correct algorithm is AES_CBC_PKCS7.
Thanks for the clarification.
I’ve also been working on porting this to a Metro app, and I’ve managed to fix the problems with tec-goblin’s code. The main problem was the fact that he was using the “key” string to create the IV, whereas he should have used the hash created from the string, instead.
private static byte[] SimpleEncrypt(string value, string key)
{
var simpleAlgorithm = GetSimpleAlgorithm(key);
CryptographicKey encryptKey = simpleAlgorithm.Item1;
IBuffer IV = simpleAlgorithm.Item2;
var encryptedBuffer = CryptographicEngine.Encrypt(encryptKey, CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8), IV);
var result = new byte[encryptedBuffer.Length];
CryptographicBuffer.CopyToByteArray(encryptedBuffer, out result);
return result;
}
private static Tuple GetSimpleAlgorithm(string key)
{
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7);
var keyAsBinary = CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8);
var source = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256).HashData(keyAsBinary);
byte[] sourceArray = new byte[16];
CryptographicBuffer.CopyToByteArray(source, out sourceArray);
var shortKey = CryptographicBuffer.CreateFromByteArray(sourceArray.Take((int)provider.BlockLength).ToArray());
return new Tuple(provider.CreateSymmetricKey(source), shortKey);
}
Many thanks for sharing that with everyone James.
Thank YOU for sharing this in the first place.
There were some problems with the GetSimpleAlgorithm code that got posted on the comment; problems with the less-than and greater-than signs. Let’s try that again, shall we?
private static Tuple<CryptographicKey, IBuffer> GetSimpleAlgorithm(string key)
{
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7);
var keyAsBinary = CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8);
var source = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256).HashData(keyAsBinary);
byte[] sourceArray = new byte[16];
CryptographicBuffer.CopyToByteArray(source, out sourceArray);
var shortKey = CryptographicBuffer.CreateFromByteArray(sourceArray.Take((int)provider.BlockLength).ToArray());
return new Tuple<CryptographicKey,IBuffer>(provider.CreateSymmetricKey(source), shortKey);
}
Now, if only you had that available in Visual Basic format…
Any good?
Private Shared Function GetSimpleAlgorithm(key As String) As Tuple(Of CryptographicKey, IBuffer)
Dim provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7)
Dim keyAsBinary = CryptographicBuffer.ConvertStringToBinary(key, BinaryStringEncoding.Utf8)
Dim source = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256).HashData(keyAsBinary)
Dim sourceArray As Byte() = New Byte(15) {}
CryptographicBuffer.CopyToByteArray(source, sourceArray)
Dim shortKey = CryptographicBuffer.CreateFromByteArray(sourceArray.Take(CInt(provider.BlockLength)).ToArray())
Return New Tuple(Of CryptographicKey, IBuffer)(provider.CreateSymmetricKey(source), shortKey)
End Function
Thanks James W!! I am not very strong in cryptography (because I use it directly so rarely and I forget the basics). I will soon start working again on my application and your fix will be invaluable!
Hmmm – code snippet not good (trying to use it in a web app build in .NET). Some quick research seems to indicate some of the method calls are only available in Windows 8?
Well, yeah, the code tec-goblin originally posted and I got working was specifically written for Windows 8 Metro apps, because the classes the original .NET code used weren’t available for Metro apps. Since you replied right after I had posted the Metro version (after months of no other activity), people kinda assumed you were referring to the new stuff.
If you’re looking to build a .NET app, you’ll want to use the code in the original post as your starting point; not sure how the “web app” part will affect whether or not it’ll work, but it’s your best bet.
Mr. Nice Guy was apparently ALSO working on a VB version, but he said the last version he posted here wasn’t returning a valid login.
Yeah,… me = Mr. Nice Guy
I’ve been trying to get this stuff working for years now, but continue to fail to return a valid login.
The code was ripped out of WotC’s silverlight app so I can’t off hand think of anything that wouldn’t work in an aspx page.
Eric is it asp.net you’re attempting to get it working in?
Here’s the barest interaction in ASP.net, showing logging in and retrieval of a list of characters.
http://buccaneersguild.com/wp-content/uploads/2012/11/Web.zip
It’s a straight asp.net application (but vb-flavored)… using VS2008.
I’ve tried the code you sent (after converting to vb) and now I’m getting this error (upon trying to execute the login statement):
SOAP header Action was not understood.
Here’s the code:
Imports System.Security.Cryptography
Imports System.Security.Cryptography.HashAlgorithm
Imports System.IO
Imports System.Text
Imports com.wizards.ContentService
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Xml.Linq
Partial Class _Default
Inherits System.Web.UI.Page
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Try
Dim username As String = “username”
Dim password As String = “password”
Dim contentClient As New com.wizards.ContentService.ContentVaultService
Dim workspaceClient As New com.wizards.vecna.D20WorkspaceService
Dim loggedIn As Boolean = False
Dim loggedInResult As Boolean = False
Call contentClient.Login(username, SimpleEncrypt(password, username), loggedIn, loggedInResult)
If loggedIn Then
Dim content As ContentInfo() = contentClient.GetAvailableContent(0, True)
Dim doc As XDocument
Dim name As String
For intX As Integer = 0 To content.Length
doc = XDocument.Parse(content(intX).CommittedContent.Details.ToString)
name = doc.Element(“CharacterDetails”).Element(“Name”).Value
Response.Write(String.Format(“{0} : {1}”, intX, name))
Next
End If
Catch ex As Exception
End Try
End Sub
Public Shared Function SimpleEncrypt(ByVal value As String, ByVal key As String) As Byte()
Dim buffer2 As Byte()
Dim Transform As ICryptoTransform
Transform = GetSimpleAlgorithm(key).CreateEncryptor
Dim stream As New MemoryStream()
Dim stream2 As New CryptoStream(stream, Transform, CryptoStreamMode.Write)
Dim bytes As Byte()
Using stream
bytes = Encoding.UTF8.GetBytes(value)
stream2.Write(bytes, 0, bytes.Length)
stream2.Flush()
stream2.FlushFinalBlock()
stream.Position = 0L
buffer2 = stream.ToArray()
End Using
Return buffer2
End Function
Private Shared Function GetSimpleAlgorithm(ByVal key As String) As SymmetricAlgorithm
Dim aes As New AesManaged()
Dim sha As SHA256Managed = New SHA256Managed()
Dim source As Byte() = sha.ComputeHash(Encoding.UTF8.GetBytes(key))
Dim myMgr As New AesManaged
myMgr.IV = source.Take(aes.BlockSize / 8).ToArray()
myMgr.Key = source
Return myMgr
End Function
End Class
Ok – I imported the WSDL files from the project you uploaded and that made a huge difference! I’m now able to achieve a valid login, however, as soon as it tries to pull in data (at this line)
ContentInfo[] content = contentClient.GetAvailableContent(0);
I then get an error “Your session has expired”
This one has me quite stumped… but – at least I’m getting closer!
Eric H, try setting AllowCookies = true! (I hope you have figured it by now, it’s just that I read the forum again, trying to set up the proper binding – in Metro we have to do it by hand).
Good spot, yes that does look like a cookies related error. God knows what DNDNext is going to bring. I don’t hold out for a decent API.
Any C# D&D people fancy working on a project together?
I haven’t gotten it working yet – but, I’ll give the cookies a try… I wish this were a little easier
Got it working yet Eric?