1

I am using ASP.NET Core 1.1 and I need to create an unique token with data.

So each token will be composed by: UniqueID + Data1 + Data2 + ... + DataN.

The UniqueId is a Guid and the Data objects can be types like Int32, String, etc:

DateTime expires = DateTime.Now.AddHours(24);
Int32 userId = user.Id;
Boolean enable = true;

And the method might be something like this:

public String GenerateToken(Guid id, params[] Object data) {

  Byte[] idBin = id.ToByteArray();

  // 1. Convert each object to Byte array
  // 2. Concat all byte arrays into tokenData string

  String token = Convert.ToBase64String(tokenData.ToArray());

  // 3. Encrypt token

  return encryptedToken;

}

So the main problems I have are:

  1. Convert each object to Byte array I know how to convert a specific type but not an Object.
  2. Concat all byte arrays into tokenData string
  3. Encrypt token

Is this the best way to create a token? The token will be sent in a URL.

And how can I solve problems 1 to 3?

Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • 3
    You are trying to do encryption when you really want message integrity. This is exactly like an old vulnerability in PHP where an ordinary user was able to change he user id and/or privilege in order to become an admin. I suggest that you really want something like HMAC rather than encryption to prevent this vulnerability. – TheGreatContini Apr 06 '17 at 22:00

4 Answers4

2

Keeping your original method I made this:

public String GenerateToken(Guid id, params object[] allData)
{
    byte[] idBin = id.ToByteArray();
    byte[] total = new byte[] { };

    total.Concat(idBin);

    foreach (var data in allData)
    {
        BinaryFormatter bf = new BinaryFormatter();
        using (MemoryStream ms = new MemoryStream())
        {
            bf.Serialize(ms, data);
            total.Concat(ms.ToArray());
        }
    }

    String token = Convert.ToBase64String(total);

    return token;
}

This turns all your extra parameters into 1 concatenated byte array.

The only thing I have excluded is the encryption as there are already a million examples out there: How to Encrypt and Decrypt (hint read past the 1st answer)

Community
  • 1
  • 1
EpicKip
  • 4,015
  • 1
  • 20
  • 37
1

I guess that you are using encryption so the user is not able to see or modify data inside this token.

  1. You want to serialize an object to a byte array. The best way to do it is to keep your object typed instead of using an object array. Then use a serialization library like BinaryFormatter or protobuf (better). C# and .NET: How to serialize a structure into a byte[] array, using BinaryWriter?

  2. It will be done automatically using a typed token content and serialization library.

  3. To protect your token, you must use MachineKey.Protect.

Sample :

public String GenerateToken(TokenContent data)
{
    byte[] data;
    using(var ms = new MemoryStream())
    {
        Serializer.Serialize(ms, cust);
        data = ms.ToArray();
    }
    var encryptedData = MachineKey.Protect(data, "TokenDataUrl");
    var token = Convert.ToBase64String(encryptedData);
    return token;
}

public TokenContent ReadToken(string token)
{
    byte[] encryptedData = Convert.FromBase64String(token);
    var data = MachineKey.Unprotect(encryptedData , "TokenDataUrl");
    TokenContent content;
    using(var ms = new MemoryStream(data))
    {
        content = Serializer.Deserialize<TokenContent>(ms);
    }
    return content;
}
Community
  • 1
  • 1
Guillaume
  • 12,824
  • 3
  • 40
  • 48
1

I'd definitely consider using something like Json.Net. This will keep all your serialised data nice and cross-platform. Also, since you mentioned you're using Asp.Net Core, BinaryFormatter isn't available to you if you want to use the cross-platform .Net Standard libraries.

To take your example, you might do something similar to this:

    public static string GenerateToken(Guid id, params object[] data)
    {
        var claims = new List<object>(data);

        claims.Add(new
        {
            id = id
        });

        string serialised = Newtonsoft.Json.JsonConvert.SerializeObject(claims);

        return serialised;
    }

So you can call the method with something like:

GenerateToken(Guid.NewGuid(), "hello world!", 25, new { Test = "value" });

Giving you the following:

["hello world!",25,{"Test":"value"},{"id":"bf9e5d38-5ac4-4c6b-b68f-88136fc233cf"}]

You could just encrypt this string and pass it to your API, decrypt it and then deserialise it to an object:

    public static object DeserialiseToken(string token)
    {
        object deserialised = Newtonsoft.Json.JsonConvert.DeserializeObject(token);

        return deserialised;
    }

That will return you an object with all your original data in.

Notice that because we're using params object[] for our arguments, we can't create key-value pairs easily. We lose a variable's original name when we pass it into the method, and we haven't really got a good way of knowing what each entry in the data array should be called. For example, accessing the 'hello world!' string could be tedious.

We might run into difficulties interpreting the data properly when we want to read it later on.

Improving It!

Having said all that, I think we can improve the approach a little bit.

The first thing I'd do is introduce a proper model for your claims. If you can guarantee your tokens will all have the same 'model' for the data they contain, you can have a class such as:

public class Token
{
    public Guid Id { get; set; }

    public int UserId { get; set; }

    public bool Enable { get; set; }
}

And pass that directly into Json.Net (or use some other serialiser):

    string output = GenerateToken(new Token
    {
        Id = Guid.NewGuid(),
        Enable = false,
        UserId = 2062
    });

and

    public static string GenerateToken(Token claims)
    {
        string serialised = Newtonsoft.Json.JsonConvert.SerializeObject(claims);

        return serialised;
    }

When you get back around to deserialising the json, you can map it straight to an object:

    public static Token DeserialiseToken(string token)
    {
        Token deserialised = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(token);

        return deserialised;
    }

You'll have a strongly-typed object with all your claims mapped against them.

You should also think about whether actually need to encrypt your token. One popular approach is the JSON Web Token (JWT) standard, where the set of claims are plaintext, but sent along with a verification hash, where the claims are hashed together with a secret.

In a situation where a user modifies the claims, when the token reaches your API it will rehash the claims with the secret, and the signatures won't match, so you'll know it's been tampered with!

If you're not storing anything particularly sensitive in your token then this is a perfectly good approach.

Callum Evans
  • 331
  • 3
  • 18
0

Just a thought, but if you want to serialise (and deserialise) objects (any C# .net object) without having to write a serialisation routing for them, and assuming that Json is OK as an output format, you can use NewtonSoft Json.Net:

http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_JsonConvert_SerializeObject.htm

var output = JsonConvert.SerializeObject(object);

if you wanted then to make a unique hash for this object, you could use SHA256 like here: Hashing a string with Sha256

Community
  • 1
  • 1