Storing PBKDF2 Iterations - c#

So I am hashing passwords to a database using the Rcfc2898DeriveBytes class in .NET
Everything is working fine for hashing and storing both the final hash and the salt.
However, I am having an issue in figuring out to store the number of iterations that the class runs through when hashing the password.
After Googling, I have seen only that it should be stored in front of the password hash but I am unsure how to go about this in a way that would make the number retrievable in case I were to change the number later (which without storing the iterations, the change would break the old passwords).
I see the bcrypt will do this all for you but I don't have the luxury of added a library to the project.
What is the best way to store this number and how would I go about doing that so it is retrieveable and not just lost in the hash when stored (how would I seperate it from the hash once I get it back)?
Thanks for any advice and information ahead of time!

In principle, you don't have to store the amount of iterations at all. Just like you don't have to store the hash type if you keep it consistent.
I would propose something slightly different: store a single byte (prefixed) that contains a version number, starting with zero, just before the salt & hash. This version number is linked to the number of iterations, the hash method, the character encoding method and of course PBKDF2. If you want to upgrade to a better protocol, simply store 01 as initial byte and link that with new parameters. This way you can distinguish between the old style passwords and new style.
Normally you can only upgrade if the user enters his password as a hash is not reversible, so it's not easily possible to upgrade the number of iterations.

I found what I am looking for at the URL below.
https://cmatskas.com/-net-password-hashing-using-pbkdf2/
Relevant code here:
public class PasswordHash
{
public const int SALT_BYTE_SIZE = 24;
public const int HASH_BYTE_SIZE = 20;
public const int PBKDF2_ITERATIONS = 1000;
public const int ITERATION_INDEX = 0;
public const int SALT_INDEX = 1;
public const int PBKDF2_INDEX = 2;
public static string HashPassword(string password)
{
var cryptoProvider = new RNGCryptoServiceProvider();
byte[] salt = new byte[SALT_BYTE_SIZE];
cryptoProvider.GetBytes(salt);
var hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
return PBKDF2_ITERATIONS + ":" +
Convert.ToBase64String(salt) + ":" +
Convert.ToBase64String(hash);
}
public static bool ValidatePassword(string password, string correctHash)
{
char[] delimiter = { ':' };
var split = correctHash.Split(delimiter);
var iterations = Int32.Parse(split[ITERATION_INDEX]);
var salt = Convert.FromBase64String(split[SALT_INDEX]);
var hash = Convert.FromBase64String(split[PBKDF2_INDEX]);
var testHash = PBKDF2(password, salt, iterations, hash.Length);
return SlowEquals(hash, testHash);
}
}
I took HashPassword method to save my values to the database for later use. I used the ValidatePassword to get them back out for me to use again. It worked like a charm and allowed me to make the size of the bytes for the salt and hash, along with the number of iterations variable based on the strength needed. The values are just stored in the Web/App config file.
Thank you for the answers everyone!

Take a look at how bcrypt stores the hashes here:
What column type/length should I use for storing a Bcrypt hashed password in a Database?
So if you only plan to change iterations, you could go for something simple like this:
$<number of iterations>$<hash>

Related

Securely Generate 6 MFA Pin

I'm trying to Generate a 6 digit code to be used for 2 factor authentication, at a first glance I might do something like this:
Random random = new Random();
var securitycode = random.Next(1000000, 10000000);
However this seems somewhat insecure to me because, there probably is a way to predict the next number if you can figure out the seeds by grabbing alot of security codes.
I'm thinking there is a better way to get a secure code using RNGCryptoServiceProvider but i'm a bit confused on how I can assure that the code generated is 6 digits
private string GenerateSecurityCode(int length)
{
var provider = new RNGCryptoServiceProvider();
var byteArray = new byte[8];
provider.GetBytes(byteArray);
var code = BitConverter.ToUInt32(byteArray, 0);
//how can I assure the code is 6 digits
}
Is this a secure way to generate MFA Codes, if not what would be a good method for Generating numeric codes?
I'm not sure if this is the most secure but I ended up doing this:
private string GenerateSecurityCode()
{
var buffer = new byte[sizeof(UInt64)];
var cryptoRng = new RNGCryptoServiceProvider();
cryptoRng.GetBytes(buffer);
var num = BitConverter.ToUInt64(buffer, 0);
var code = num % 1000000;
return code.ToString("D6");
}

Unique string for each record in the database table

In my Asp.Net MVC 5 project I use Entity Framework code first to work with MS SQL database. Suppose this is the table:
public class Ticket
{
[Key]
public int Id { get; set; }
[Required]
public string ReferenceCode { get; set; }
//Rest of the table
}
In this table, whenever I add a new code I want the ReferenceCode column to be a unique and random AlphaNumeric (containing only letters and digits) string with a specific length. This will allow users to refer to a specific ticket for instance.
These are some examples with 10 character lenght: TK254X26W1, W2S564Z111, 1STT135PA5...
Right now, I'm able to generate random strings with the given length. However, I'm not sure how to guarantee their uniqueness. I do it like this:
db.Tickets.Add(new Ticket()
{
ReferenceCode = GenerateRandomCode(10),
//...
});
To be exact, I want the GenerateRandomCode function or maybe another method to be able to make sure the generated string has not been used for another record.
I can use a for loop to check each generated code but I don't think it's a good idea. Especially after a while when the table will have thousands of records.
You can use Guid in order to generate unique (but not that random when it comes to security) keys.
Pulling from this SO question:
Guid g = Guid.NewGuid();
string GuidString = Convert.ToBase64String(g.ToByteArray());
GuidString = GuidString.Replace("=","");
GuidString = GuidString.Replace("+","");
GuidString = GuidString.ToUpper();
will generate a unique key to fit your ReferenceCode property needs but longer (22 characters). Collapsing it and using a X characters would no longer guarantee its uniqueness.
OZVV5TPP4U6XJTHACORZEQ
Mind an off-the-beaten-path solution? You've got two needs, that I can see:
Randomness. You can't have a "deterministic" function, because if someone can guess the algorithm, they could figure out everyone elses' ticket numbers.
Uniqueness. You can't have any duplicate ticket nums - which makes Random a bit difficult (you'll have to account for collisions and retry.)
But there's no reason you can't do both - you've got plenty of bit-space with 36^10. You could dedicate 4 bytes to Uniqueness, and 6 bytes to Randomness. Here's some sample code:
public partial class Form1 : Form
{
private static Random random = new Random();
private static int largeCoprimeNumber = 502277;
private static int largestPossibleValue = 1679616; // 36 ^ 4
private static char[] Base36Alphabet = new char[] { '0','1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z' };
public static string GetTicket(int id)
{
int adjustedID = id * largeCoprimeNumber % largestPossibleValue;
string ticket = IntToString(adjustedID);
while (ticket.Length < 4) ticket = "0" + ticket;
return ticket + new string(Enumerable.Repeat(Base36Alphabet, 6) .Select(s => s[random.Next(s.Length)]).ToArray());
}
private static string IntToString(int value)
{
string result = string.Empty;
int targetBase = Base36Alphabet.Length;
do
{
result = Base36Alphabet[value % targetBase] + result;
value = value / targetBase;
}
while (value > 0);
return result;
}
Quick rundown on what the code's doing. You're passing in your int id - which it then hashes in such a way that it looks random, but is guaranteed to never repeat a number for the first 1.68 million entries.
It then takes this hashed int value, and turns it into a 4-digit code; this is the "uniqueness part" - you're guaranteed a different 4 digit code at the beginning of the first 1.68 million IDs (the magic of coprime numbers.)
That leaves 6 more characters to play with. Just fill them in with random characters - that makes the whole 10-digit code awfully difficult to guess.
This solves both of your problems. It's guaranteed to be unique for the first million+ records. And it's not really "guessable" by the client, since even if they guessed the algorithm, they'd have 2 billion different possibilities for any given ID they wanted to crack.
Here's my approach that guarantees uniqueness and introduces some randomness.
Use a sequence generator that is guaranteed to give a unique number. Since you're working with SQL Server, this can be an IDENTITY column's value. You could alternatively increment an application-level value within your C# code to achieve this.
Generate a random integer to bring in some randomness to the result. This could be done with Random.Next() and any seed, even the number generated in the preceding step.
Use a method EncodeInt32AsString to convert the integers from the previous two steps into two strings (one is the unique string, one the random string). The method returns a string composed of only the allowed characters specified in the method. The logic of this method is similar to how number conversion between different bases takes place (for example, change the allowed string to only 0-9, or only 0-9A-F to get the decimal/hex representations). Therefore, the result is a "number" composed of the "digits" in allowedList.
Concatenate the strings returned. Keep the entire unique string as-is (to guarantee uniqueness) and add as many characters from the random string to pad the total length to the desired length. If required, this concatenation can be fancy, by injecting characters from the random string at random points into the unique string.
By retaining the entire unique string, this ensures uniqueness of the final result.
By using a random string, this introduces randomness. Randomness cannot be guaranteed in case the target string's length is very close to the length of the unique string.
In my testing, calling EncodeInt32AsString for Int32.MaxValue returns a unique string 6 characters long:
2147483647: ZIK0ZJ
On that basis, a target string length of 12 will be ideal, though 10 is also reasonable.
The EncodeInt32AsString Method
/// <summary>
/// Encodes the 'input' parameter into a string of characters defined by the allowed list (0-9, A-Z)
/// </summary>
/// <param name="input">Integer that is to be encoded as a string</param>
/// <param name="maxLength">If zero, the string is returned as-is. If non-zero, the string is truncated to this length</param>
/// <returns></returns>
static String EncodeInt32AsString(Int32 input, Int32 maxLength = 0)
{
// List of characters allowed in the target string
Char[] allowedList = new Char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z' };
Int32 allowedSize = allowedList.Length;
StringBuilder result = new StringBuilder(input.ToString().Length);
Int32 moduloResult;
while (input > 0)
{
moduloResult = input % allowedSize;
input /= allowedSize;
result.Insert(0, allowedList[moduloResult]);
}
if (maxLength > result.Length)
{
result.Insert(0, new String(allowedList[0], maxLength - result.Length));
}
if (maxLength > 0)
return result.ToString().Substring(0, maxLength);
else
return result.ToString();
}
The GetRandomizedString Method
Now, the preceding method just takes care of encoding a string. In order to achieve the uniqueness and randomness properties, the following logic (or similar) can be used.
In the comments, Kevin pointed out the following risk with the implementation of the EncodeInt32AsString method:
The code needs to be tweaked so that it returns a fixed-length string.
Otherwise, you can never be guaranteed of the final result is unique.
If it helps, picture one value generating ABCDE (Unique) +
F8CV1 (Random)... and then later on, another value generating
ABCDEF (Unique) + 8CV1 (Random). Both values are ABCDEF8CV1
This is a very valid point, and this has been addressed in the following GetRandomizedString method, by specifying lengths for the unique and random strings. The EncodeInt32AsString method has also been modified to pad out the return value to a specified length.
// Returns a string that is the encoded representation of the input number, and a random value
static String GetRandomizedString(Int32 input)
{
Int32 uniqueLength = 6; // Length of the unique string (based on the input)
Int32 randomLength = 4; // Length of the random string (based on the RNG)
String uniqueString;
String randomString;
StringBuilder resultString = new StringBuilder(uniqueLength + randomLength);
// This might not be the best way of seeding the RNG, so feel free to replace it with better alternatives.
// Here, the seed is based on the ratio of the current time and the input number. The ratio is flipped
// around (i.e. it is either M/N or N/M) to ensure an integer is returned.
// Casting an expression with Ticks (Long) to Int32 results in truncation, which is fine since this is
// only a seed for an RNG
Random randomizer = new Random(
(Int32)(
DateTime.Now.Ticks + (DateTime.Now.Ticks > input ? DateTime.Now.Ticks / (input + 1) : input / DateTime.Now.Ticks)
)
);
// Get a random number and encode it as a string, limit its length to 'randomLength'
randomString = EncodeInt32AsString(randomizer.Next(1, Int32.MaxValue), randomLength);
// Encode the input number and limit its length to 'uniqueLength'
uniqueString = EncodeInt32AsString(input, uniqueLength);
// For debugging/display purposes alone: show the 2 constituent parts
resultString.AppendFormat("{0}\t {1}\t ", uniqueString, randomString);
// Take successive characters from the unique and random strings and
// alternate them in the output
for (Int32 i = 0; i < Math.Min(uniqueLength, randomLength); i++)
{
resultString.AppendFormat("{0}{1}", uniqueString[i], randomString[i]);
}
resultString.Append((uniqueLength < randomLength ? randomString : uniqueString).Substring(Math.Min(uniqueLength, randomLength)));
return resultString.ToString();
}
Sample Output
Calling the above method for a variety of input values results in:
Input Int Unique String Random String Combined String
------------ ----------------- -------------- ---------------------
-10 000000 CRJM 0C0R0J0M00
0 000000 33VT 03030V0T00
1 000001 DEQK 0D0E0Q0K01
2147 0001NN 6IU8 060I0U18NN
21474 000GKI VNOA 0V0N0OGAKI
214748 004LP8 REVP 0R0E4VLPP8
2147483 01A10B RPUM 0R1PAU1M0B
21474836 0CSA38 RNL5 0RCNSLA538
214748364 3JUSWC EP3U 3EJPU3SUWC
2147483647 ZIK0ZJ BM2X ZBIMK20XZJ
1 000001 QTAF 0Q0T0A0F01
2 000002 GTDT 0G0T0D0T02
3 000003 YMEA 0Y0M0E0A03
4 000004 P2EK 0P020E0K04
5 000005 17CT 01070C0T05
6 000006 WH12 0W0H010206
7 000007 SHP0 0S0H0P0007
8 000008 DDNM 0D0D0N0M08
9 000009 192O 0109020O09
10 00000A KOLD 0K0O0L0D0A
11 00000B YUIN 0Y0U0I0N0B
12 00000C D8IO 0D080I0O0C
13 00000D KGB7 0K0G0B070D
14 00000E HROI 0H0R0O0I0E
15 00000F AGBT 0A0G0B0T0F
As can be seen above, the unique string is predictable for sequential numbers, given it is just the same number represented in a different base. However, the random string brings in some entropy to prevent users from guessing subsequent numbers. Moreover, by interleaving the "digits" of the unique string and random string it becomes slightly more difficult for users to observe any pattern.
In the above example, the length of the unique string is set to 6 (since that allows it to represent Int32.MaxValue), but the length of the random string is set to 4 because the OP wanted a total length of 10 characters.
You can achieve absolute uniqueness on the machine using UuidCreateSequential method(which deals with Uuid) in rpcrt4.dll as below. Check this link from microsoft to be sure about uniqueness. You will never get the same Id twice on your machine or the host where you upload your website.
The output format from following code is what Asp.Net MVC uses to create unique id for AspNetUsers table:
using System;
using System.Runtime.InteropServices;
public class SqlGuidUtil
{
[DllImport("rpcrt4.dll", SetLastError = true)]
static extern int UuidCreateSequential(out Guid guid);
public static Guid NewSequentialId()
{
Guid guid;
UuidCreateSequential(out guid);
var s = guid.ToByteArray();
var t = new byte[16];
t[3] = s[0];
t[2] = s[1];
t[1] = s[2];
t[0] = s[3];
t[5] = s[4];
t[4] = s[5];
t[7] = s[6];
t[6] = s[7];
t[8] = s[8];
t[9] = s[9];
t[10] = s[10];
t[11] = s[11];
t[12] = s[12];
t[13] = s[13];
t[14] = s[14];
t[15] = s[15];
return new Guid(t);
}
}
Usage:
Guid gid = SqlGuidUtil.NewSequentialId();
String sid = SqlGuidUtil.NewSequentialId().ToString();
Sample output:
637E3E78-23F5-E611-8278-506313F91120
This format is exactly the same as AspNet Identity user Id format.
You can also remove dashes(not a good idea) as below:
String sid = SqlGuidUtil.NewSequentialId().ToString().Replace("-","");
Put a unique index on the DB column, and keep generating until the DB accepts it without a unique constraint. Collisions will be very rare.
We needed to implement something like this for a different purpose in of our previous projects. We just pregenerated a number of unique identifiers into a new table (let's call it table A), and then when we wanted to insert a new record into table B, we just added the top 1 record from table A in a trigger.
user your code like
string referenceCode=Guid.NewGuid().ToString();
referenceCode=referenceCode.Replace('-', '');
db.Tickets.Add(new Ticket()
{
ReferenceCode = referenceCode;
//...
});
Try this one. It's work for me
var buffer = new byte[5];
new Random().NextBytes(buffer);
Console.WriteLine(string.Join("", buffer.Select(b => b.ToString("X2"))));
Using your ID for granting uniqueness and the System.Random class to get you a randomness you may expect something like :
private string GenerateRandomCode(int Key)
{
Random rnd = new Random(Key);
char[] values = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToArray();
string result = string.Empty;
for(int i = 0; i < 10; i++)
{
result += values[rnd.Next(0, values.Length)];
}
return result;
}
The key value will ensure the same generated code and the random class has not enough period to worry about unicity.
Try this:
Guid.NewGuid().ToString("N").Substring(0, 10)
I used this while generating random strings for ID in SQL tables from C# codebase. This relies on randomness of C# Guid and everytime you will get a new alphanumeric String.
Using Guid I have create a function to generate unique string. Of course GUIDs can collide, so I am altering the string in the middle with new Guid.
static string GenerateRandomCode(){
string guid = Guid.NewGuid().ToString("N");
List<char> lst = new List<char>();
int count = 1;
foreach(char c in guid){
if(count==11) break;
if(count % 2 ==0){
lst.Add(Guid.NewGuid().ToString().ToCharArray()[1]);
}
else{
lst.Add(c);
}
count++;
}
return string.Join("",lst.ToArray());
}
Maybe this will help you
DECLARE #userReportId BIGINT
SET #userReportId = FLOOR(RAND()*(10000000000000-1) + 1);

Generating a unique 5 character sequence of random upper/lower letters?

I'm just wondering what the usual method is of generating a unique string of random upper/lowercase letters of arbitrary length?
My immediate thought would be to use a guid generator provided by the programming framework I use and then apply a well known hashing algorithm to the guid.
If this is the case, what types of hashing functions do I need to look into?
Thanks
Creating a GUID is not a good way to get a random number, as not all data in a GUID is random.
Just use the Random class. Example:
string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int len = 5;
Random rnd = new Random();
StringBuilder b = new StringBuilder(len);
for (int i = 0; i < len; i++) {
b.Append(chars[rnd.Next(chars.Length)]);
}
string result = b.ToString();
Note however that this does not guarantee that the code is unique. To accomplish that you have to save all previous codes and check against those for any new code. Eventhough a GUID is considered unqiue enough, a short hash generated from a GUID does not have that property.

best practices to implement hashing?

I need to implement hashing (I am not referring encryption) to make some data fields (passwords or some details that do not require getting back in original format, rather only need to match in db) secure. Can you please suggest me the best practices to implement hashing. I will be using C# and SQL Server and it will be a web site.
OK now you've said you're protecting passwords you have some options.
The .NET framework has some built in algorithms - MD5, SHA1, SHA2. MD5 and SHA1 are considered obsolete and dangerous now, instead stick to SHA256.
For example (taken from my book)
static byte[] GenerateSaltedHash(string password, byte[] salt)
{
byte[] plainText = Encoding.UTF8.GetBytes(password);
HashAlgorithm algorithm = new SHA256Managed();
byte[] plainTextWithSaltBytes =
new byte[plainText.Length + salt.Length];
for (int i = 0; i < plainText.Length; i++)
{
plainTextWithSaltBytes[i] = plainText[i];
}
for (int i = 0; i < salt.Length; i++)
{
plainTextWithSaltBytes[plainText.Length + i] = salt[i];
}
byte[] hash = algorithm.ComputeHash(plainTextWithSaltBytes);
}
Now the salt is there to stop precomputed lookups of hashes (hashing itself is not enough any more, people have precomputed hashes of dictionary words and more). But how do you get a salt? Well it's any unique value really, usually a random set of bytes.
public byte[] GenerateSalt(int length)
{
salt = new byte[length];
// Strong runtime pseudo-random number generator, on Windows uses CryptAPI
// on Unix /dev/urandom
RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
random.GetNonZeroBytes(salt);
return salt;
}
So you'd call GenerateSalt(32) first to get the salt (32 is just an example, longer if you wish. You will need to store the salt alongside the password - you don't need to worry about protecting it at all.
Finally you'll need a compare function. When you want to check passwords you would take the user input, get the salt for that user, generate the hash for the supplied password and stored salt, and then compare. You would do this using something like
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static bool ConstantCompare(byte[] array1, byte[] array2)
{
const byte Zero = 0;
int maxLength = array1.Length > array2.Length ? array1.Length : array2.Length;
bool wereEqual = array1.Length == array2.Length;
byte[] paddedArray1 = new byte[maxLength];
byte[] paddedArray2 = new byte[maxLength];
for (int i = 0; i < maxLength; i++)
{
paddedArray1[i] = array1.Length > i ? array1[i] : Zero;
paddedArray2[i] = array2.Length > i ? array2[i] : Zero;
}
bool compareResult = true;
for (int i = 0; i < maxLength; i++)
{
compareResult = compareResult & paddedArray1[i] == paddedArray2[i];
}
return compareResult & wereEqual;
}
I should, of course, point out the ASP.NET membership functions do salt and hash, so they should probably be a first point of call. No point in rolling your own if someone else has done the work.
Here is an API using PBKDF2 for key stretching ONTOP of hashing, this is now best practice.
https://sourceforge.net/projects/pwdtknet
I think you misunderstand what hashing is. You don't use a hash to "make data fields secure", because a hash can only be calculated in one direction. Compute and store the hash for your data, store the hash only, and you will be unable to retrieve your data later.
You might need to encrypt the data. That is an entirely different can of worms with it's own set of problems: how do you store and protect the decryption key, what encryption algorithm to use, do you do the encryption at the database level (sql server supports this) or the application level? How do you enforce encrypted transfer, and protect against copy of the data by the end user?

C# : How to generate unique passwords with the length of 7 or 8 characters

I need to repeated generate unique password many times, Ensure that every time the generated passwords are unique, Please help me.
Thanks!
So here is another method which generates cryptedRandom password and a thread safe...
private string CryptedRandomString()
{
lock (this)
{
int rand = 0;
byte[] randomNumber = new byte[5];
RNGCryptoServiceProvider Gen = new RNGCryptoServiceProvider();
Gen.GetBytes(randomNumber);
rand = Math.Abs(BitConverter.ToInt32(randomNumber, 0));
return ConvertIntToStr(rand);
}
}
private string ConvertIntToStr(int input)
{
lock (this)
{
string output = "";
while (input > 0)
{
int current = input % 10;
input /= 10;
if (current == 0)
current = 10;
output = (char)((char)'A' + (current - 1)) + output;
}
return output;
}
}
Now you can call this method like this: -
string GeneratedPassword = "";
GeneratedPassword = CryptedRandomString() + CryptedRandomString();
Console.WriteLine(GeneratedPassword.Substring(0,8));
Now you all must be wondering why GeneratedPassword = CryptedRandomString() + CryptedRandomString(); , the reason I called CryptedRamdomString() method twice is just to make sure it returns more then 10 digits so as it will be easier to get eight character passwords otherwise if it is called once then sometimes it will generate less then eight character password.
Well you must consider one thing before using this method that generating random numbers using "RNGCryptoServiceProvider " is bit time consuming then Random.Next. But "RNGCryptoServiceProvider " is much more secure then "Random.Next" .
If you want to generate uniq password every time than
take CurrentTIME and CurrrentDATE in account because by this you can able to create new password.
have look to this resolve your problem : generating a batch of random passwords
Try this
http://www.yetanotherchris.me/home/2009/3/15/c-pronounceable-password-generator.html
I'd define an array of possible characters for the password alphabet, and then generate 7 or 8 random indexes into that array to create a password.
This would need to be refined if you want to guarantee a minimum number of each character type, etc.
Globally unique strings you say?
System.Guid.NewGuid().ToString()

Categories

Resources