I need to encrypt a 2-digit number in a simple way. The samples I found in google seems to be quite complex. Is there any easy way to achieve that?
UPDATE
I'm working on a custom numeric captcha for my ASP.NET MVC application. I've created a custom html helper that will render an image tag with base-64 encoded string of the captcha image. The captcha image will be something like 23 + 12 = ?. When the user submit the answer I want to validate it right? I'm not interested in storing the sum in session so I thought of encrypt the sum and attach as a hidden field and so when the user submit the form I can easily do the validation.
If your number is x then you can encrypt it as (x + key) mod 100. This will result in another 2 digit number, y.
It doesn't get much simpler than that.
The decryption is simply x = y - key, +100 if necessary.
If key is 2:
x = 15
y = 15 + 2 = 17
x = 17 - 2 = 15
x = 99
y = 99 + 2 mod 100 = 101 mod 100 = 1
x = 1 - 2 + 100 = 99;
Even simpler would be to encrypt x as x. They would definitely never expect that...
Edit 1:
On a more serious note, If this is not some sort of personal experiment/homework I'd stay clear of such "simple" algorithms and go with System.Security.Cryptography and those not-that-complex samples from Google or charles sun's comment. Unless you make a carrier out of it never implement you own encryption/decryption algorithms, that way lies madness.
Edit 2:
So you want to send both the captcha and its correct response to the client? I don't think that is how it's done (but then again this is not my field...). I always thought validation is done on the server side (the part you control and keep secure).
To be on the safe side, I would do this the hard way and encrypt everything properly.
This is maybe not entirely serious, but it works!
static IEnumerable<string> GetRandomStringsForever()
{
var rng = new Random(); // or maybe new Random(14142848)
while (true)
{
char[] arr = new char[8];
for (int idx = 0; idx < arr.Length; ++idx)
arr[idx] = (char)rng.Next('A', 'Z' + 1);
yield return new string(arr);
}
}
static void Main()
{
var secretKey = GetRandomStringsForever().Distinct().Take(100).ToList();
int message = 42;
// encrypt:
string cryptic = secretKey[message];
Console.WriteLine("Who can guess the number from this: " + cryptic);
// decrypt:
int reconstructed = secretKey.IndexOf(cryptic);
Console.WriteLine("The message was: " + reconstructed);
}
Well, if people know you're doing this using my idea, they will probably be able to construct the secretKey themselves (using the same version of .NET as you), so this is not REALLY safe.
Related
I'm somewhat new to working with BigIntegers and have tried some stuff to get this system working, but feel a little stuck at the moment and would really appreciate a nudge in the right direction or a solution.
I'm currently working on a system which reduces BigInteger values down to a more readable form, and this is working fine with my current implementation, but I would like to further expand on it to get decimals implemented.
To better give a picture of what I'm attempting, I'll break it down.
In this context, we have a method which is taking a BigInteger, and returning it as a string:
public static string ShortenBigInt (BigInteger moneyValue)
With this in mind, when a number such as 10,000 is passed to this method, 10k will be returned. Same for 1,000,000 which will return 1M.
This is done by doing:
for(int i = 0; i < prefixes.Length; i++)
{
if(!(moneyValue >= BigInteger.Pow(10, 3*i)))
{
moneyValue = moneyValue / BigInteger.Pow(10, 3*(i-1));
return moneyValue + prefixes[i-1];
}
}
This system is working by grabbing a string from an array of prefixes and reducing numbers down to their simplest forms and combining the two and returning it when inside that prefix range.
So with that context, the question I have is:
How might I go about returning this in the same way, where passing 100,000 would return 100k, but also doing something like 1,111,111 would return 1.11M?
Currently, passing 1,111,111M returns 1M, but I would like that additional .11 tagged on. No more than 2 decimals.
My original thought was to convert the big integer into a string, then chunk out the first few characters into a new string and parse a decimal in there, but since prefixes don't change until values reach their 1000th mark, it's harder to tell when to place the decimal place.
My next thought was using BigInteger.Log to reduce the value down into a decimal friendly number and do a simple division to get the value in its decimal form, but doing this didn't seem to work with my implementation.
This system should work for the following prefixes, dynamically:
k, M, B, T, qd, Qn, sx, Sp,
O, N, de, Ud, DD, tdD, qdD, QnD,
sxD, SpD, OcD, NvD, Vgn, UVg, DVg,
TVg, qtV, QnV, SeV, SPG, OVG, NVG,
TGN, UTG, DTG, tsTG, qtTG, QnTG, ssTG,
SpTG, OcTG, NoTG, QdDR, uQDR, dQDR, tQDR,
qdQDR, QnQDR, sxQDR, SpQDR, OQDDr, NQDDr,
qQGNT, uQGNT, dQGNT, tQGNT, qdQGNT, QnQGNT,
sxQGNT, SpQGNT, OQQGNT, NQQGNT, SXGNTL
Would anyone happen to know how to do something like this? Any language is fine, C# is preferable, but I'm all good with translating. Thank you in advance!
formatting it manually could work a bit like this:
(prefixes as a string which is an char[])
public static string ShortenBigInt(BigInteger moneyValue)
{
string prefixes = " kMGTP";
double m2 = (double)moneyValue;
for (int i = 1; i < prefixes.Length; i++)
{
var step = Math.Pow(10, 3 * i);
if (m2 / step < 1000)
{
return String.Format("{0:F2}", (m2/step)) + prefixes[i];
}
}
return "err";
}
Although Falco's answer does work, it doesn't work for what was requested. This was the solution I was looking for and received some help from a friend on it. This solution will go until there are no more prefixes left in your string array of prefixes. If you do run out of bounds, the exception will be thrown and handled by returning "Infinity".
This solution is better due to the fact there is no crunch down to doubles/decimals within this process. This solution does not have a number cap, only limit is the amount of prefixes you make/provide.
public static string ShortenBigInt(BigInteger moneyValue)
{
if (moneyValue < 1000)
return "" + moneyValue;
try
{
string moneyAsString = moneyValue.ToString();
string prefix = prefixes[(moneyAsString.Length - 1) / 3];
BigInteger chopAmmount = (moneyAsString.Length - 1) % 3 + 1;
int insertPoint = (int)chopAmmount;
chopAmmount += 2;
moneyAsString = moneyAsString.Remove(Math.Min(moneyAsString.Length - 1, (int)chopAmmount));
moneyAsString = moneyAsString.Insert(insertPoint, ".");
return moneyAsString + " " + prefix;
}
catch (Exception exceptionToBeThrown)
{
return "Infinity";
}
}
Very new to programming/selenium automation in C# and have hit a bit of a stumbling block that I am struggling at resolving, I have looked around on here / google but nothing has come up matching what I am looking for, I could be wording my question slightly wrong so forgive me if that is the case.
What I need to achieve,
When logging into a website after entering a username/password we are prompted to enter a pin code, specifically a randomly generated combination – example “Please enter numbers 1, 2 & 3 of your PIN” - where (in the example) 1, 2, 3 can be anything from 1 to 6 (always chronological order), the message itself, “,” and “&” positions do not change – only the numbers.
** Info from the 'line' containing the message (and numbers) **
Inner HTML
Please enter numbers 1, 3 & 5 of your PIN
Outer HTML
Please enter numbers 1, 3 & 5 of your PIN
CSS Selector
h2.login-desktop-only
xPath
/html/body/section[2]/div/div/div/form/div/div[2]/div[1]/div/h2
For this situation I am using a ‘UAT site’ so I have control on the PIN, let’s say it is always 123456 – so 1 = 1, 2 =2, 3-3 and so on. I have no way to determine which numbers will be asked each time the test is run.
How can I ‘scrape’ the text from ‘Please enter numbers XXXXX’ and parse (I think that is the correct word) the data to separate the ‘scraped’ numbers and then in turn use that data to match the pre-declared ‘1 = 1’ etc etc to then end up selecting the correct number on the keypad?
I imagine this is going to need a list of IF statements but again I still do not know how to scrape / store the requested numbers. Ideally would like to keep this using c# (however if any Java examples exist I can work with that as a colleague is using java selenium - both of us are very new to this)
Any advice would be greatly appreciated.
(EDIT TO Add code from comment)
Many thanks for getting back to me, I tried that code and it has located the index position of the integers contained within that ‘string’. Currently it ‘prints’ out the index position, but how can I get that to give the value rather than print it?
I suppose if I could assign it to a variable I could then split the three numbers down to a unique variable that has IF statements to cover the IF 1 – then 1 IF 2 – then 2 and so on. If that makes sense?
public class Some_Class {
public static void main(String[] args) {
WebDriver driver = new SafariDriver();
driver.get("SomeWebsite");
driver.findElement(By.id("username")).sendKeys("XXXXXXX");
driver.findElement(By.id("password")).sendKeys("XXXXXXX ");
driver.findElement(By.id("login-button")).click();
/* --- This was my original plan to set the xpath as a string and then replace all with numbers only. This did not work as I thought.
{
WebElement str = driver.findElement(By.xpath("/html/body/section[2]/div/div/div/form/div/div[2]/div[1]/div/h2"));
String numberOnly = str.replaceAll("[^0-9]", "");
}
*/
WebElement option = driver.findElement(By.xpath("/html/body/section[2]/div/div/div/form/div/div[2]/div[1]/div/h2"));
String word=option.getText();
String check[]=word.split("");
for(int i=0; i<check.length ; i++)
{
if( Pattern.matches("\\d", check[i]))
{
System.out.println("found integer at i = "+ i);
}
}
}
}
Input : you need to scrape a string containing ' integers and alphabets' and return only integers.
Here's an example I have done using Selenium,Java.
Please change your URL and WebElement to scrape.
driver.get("https://en.wikipedia.org/wiki/Main_Page");
WebElement option = driver.findElement(By.xpath("//*[#id=\"articlecount\"]"));
String word=option.getText();
//here you get - 5,589,206 articles in English
String check[]=word.split("");
for(int i=0; i<check.length ; i++)
{
if( Pattern.matches("\\d", check[i]))
{
System.out.println("found integer at i = "+ i);
}
}
Basically that would print you the index at which you have integers. Use them
In the case of a challenge string that says
string pinRequest = "Please enter pin digits 1,4 & 8 of your pin.";
var pinNums = pinRequest.Where(Char.IsDigit).ToArray().ToList();
pinNums will be an integer array that has 3 parts which are equal to
{1,4,8}
Your pin challenge is then solved via:
string part1 = fullWord.ToArray()[pinNums[0] - 1].ToString();
string part2 = fullWord.ToArray()[pinNums[1] - 1].ToString();
string part3 = fullWord.ToArray()[pinNums[2] - 1].ToString();
where fullWord could be something like 123123 or Password123
I am not too sure how this code will handle pin challenges >= 10 digits in length.
I have noticed a strange issue with the random number generation in c#, it looks like sets (patterns) are repeated a lot more often than you would expect.
I'm writing a mechanism that generates activation codes, a series of 7 numbers (range 0-29).
Doing the math, there should be 30^7 (22billion) possible combinations of activation codes. Based on this it should be extremely unlikely to get a duplicate activation code before the 1 billionth code is generated. However running my test, I start getting duplicate codes after about 60,000 iteration, which is very surprising. I have also tried using RNGCryptoServiceProvider with similar results, I get duplicates at about 100,000 iterations.
I would really like to know if this is a bug/limitation of the random number generation in .Net or if I'm doing something wrong.
The following code is a test to validate the uniqueness of the generated codes:
static void Main(string[] args)
{
Random rand = new Random();
RandomActivationCode(rand, true);
Console.Out.WriteLine("Press enter");
Console.ReadLine();
}
static void RandomActivationCode(Random randomGenerator)
{
var maxItems = 11000000;
var list = new List<string>(maxItems);
var activationCodes = new HashSet<string>(list);
activationCodes.Clear();
DateTime start = DateTime.Now;
for (int i = 0; i < maxItems; ++i)
{
string activationCode = "";
for (int j = 0; j < 7; ++j)
{
activationCode += randomGenerator.Next(0,30) + "-";
}
if (activationCodes.Contains(activationCode))
{
Console.Out.WriteLine("Code: " + activationCode);
Console.Out.WriteLine("Duplicate at iteration: " + i.ToString("##,#"));
Console.Out.WriteLine("Press enter");
Console.ReadLine();
Console.Out.WriteLine();
Console.Out.WriteLine();
}
else
{
activationCodes.Add(activationCode);
}
if (i % 100000 == 0)
{
Console.Out.WriteLine("Iteration: " + i.ToString("##,#"));
Console.Out.WriteLine("Time elapsed: " + (DateTime.Now - start));
}
}
}
My workaround is to use 10 number activation codes, which means that the test runs without any duplicate values being generated. The test runs up to 11 million iterations (after which point it runs out of memory).
This is not at all surprising; this is exactly what you should expect. Your belief that it should take a long time to generate duplicates when the space of possibilities is large is simply false, so stop believing that. Start believing the truth: that if there are n possible codes then you should start getting duplicates at about the square root of n codes generated, which is about 150 thousand if n is 22 billion.
Think about it this way: by the time you have generated root-n codes, most of them have had roughly a root-n-in-n chance to have a collision. Multiply root-n by roughly root-n-in-n, and you get... roughly 100% chance of collision.
That is of course not a rigorous argument, but it should give you the right intution, to replace your faulty belief. If that argument is unconvincing then you might want to read my article on the subject:
http://blogs.msdn.com/b/ericlippert/archive/2010/03/22/socks-birthdays-and-hash-collisions.aspx
If you want to generate a unique code then generate a GUID; that's what they're for. Note that a GUID is not guaranteed to be random, it is only guaranteed to be unique.
Another choice for generating random seeming codes that are not actually random at all, but are unique, is to generate the numbers 1, 2, 3, 4, ... as many as you want, and then use the multiplicative inverse technique to make a random-looking unique encoding of those numbers. See http://ericlippert.com/2013/11/14/a-practical-use-of-multiplicative-inverses/ for details.
I've used the following query to fetch hard disk serial number.
ManagementObjectSearcher("SELECT * FROM Win32_PhysicalMedia");
It returns different serial number for admin user and non-admin user as follows:
admin - WD-WCAYUS426947
non-admin - 2020202057202d44435759415355323439363734
When tried to put the non-admin serial into hex to char converter it gave W -DCWYASU249674, which is actually a character swap on every 2 characters.
Any idea to fetch the correct serial without manupulating the un-hexed format please?
As posted in the comments:
This seems to be an unsolved bug in Windows, although Microsoft knows about it.
The way to solve it is to convert the hex string and swap the numbers, I wrote a method that does this for you, feel free to edit it to your needs:
public static string ConvertAndSwapHex(string hex)
{
hex = hex.Replace("-", "");
byte[] raw = new byte[hex.Length / 2];
for (int i = 0; i < raw.Length; i++)
{
int j = i;
if (j != 0)
{
j = (j % 2 == 1 ? j-1 : j+1);
}
raw[j] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
}
return System.Text.Encoding.UTF8.GetString(raw).Trim(' ', '\t', '\0');
}
Thank you very much #thedutchman, for writing the code to convert and swap the jixed characters. I combined your code and code in this link and created a new function like below:
public static string ConvertAndSwapHex(string hexString)
{
var charString = new StringBuilder();
for (var i = 0; i < hexString.Length; i += 4)
{
charString.Append(Convert.ToChar(Convert.ToUInt32(hexString.Substring(i + 2, 2), 16)));
charString.Append(Convert.ToChar(Convert.ToUInt32(hexString.Substring(i, 2), 16)));
}
return charString.ToString();
}
One more important thing is, as mentioned in this microsoft forum, using Win32_DiskDrive instead of Win32_PhysicalMedia returns jinxed serial number consistently in Win 7 for both admin and non-admin users. Even though it returns completely different things in WinXP (which we don't support for our software anymore), consistently returning the jinxed serial number is good enough for me. You know I don't have to check the length of the serial number to determine if I need to use the above ConvertAndSwap method or not.
on my site I allow people to buy subscriptions to my site in bulk(I call them vouchers). Once they have these vouchers, they give them to whoever and they enter that code into their account to upgrade them.
Right now I am thinking of doing 4 alphanumeric code(upper case, lower case and digits) and will have something like this
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[4];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new String(stringChars);
For now I think that will give me more than enough combinations and if I ever do run out I can always up the length of the code. I want to keep it short because I don't want the user to have to type in huge as numbers.
I also don't have the time to make a more elegant solution maybe were they click a link or something in their email and it activates their account and of course this would cut down on someone trying to randomly guess a voucher number.
These are things I would deal with if the site every becomes more popular.
I am wondering though how can I handle the possible duplicate generation of the same voucher. My first thought was to check the database each time a voucher is created and if it exists then make a new one.
However that seems like it could be slow. So I thought also maybe getting all the keys first and store them in memory and they check there but if the list keeps growing I might run into out of memory exceptions and all that great stuff.
So does anyone have any ideas? Or am I stuck doing one of the 2 method I listed above?
I am using nhibernate, asp.net mvc and C#.
Edit
static void Main(string[] args)
{
List<string> hold = new List<string>();
for (int i = 0; i < 10000; i++)
{
HashAlgorithm sha = new SHA1CryptoServiceProvider();
byte[] result = sha.ComputeHash(BitConverter.GetBytes(i));
string hex = null;
foreach (byte x in result)
{
hex += String.Format("{0:x2}", x);
}
hold.Add(hex.Substring(0,3));
Console.WriteLine(hex.Substring(0, 4));
}
Console.WriteLine("Number of Distinct values {0}", hold.Distinct().Count());
}
above is my attempt to try to use hashing. However I think I am missing something as it seems to have quite a bit more duplicates then expected.
Edit 2
I think I added what I was missing but not sure if this is exactly what he meant. I am also not sure what to do in a situation when I moved it as far as I can move it(my has seems to give me a length of 40 places I can move it).
static void Main(string[] args)
{
int subStringLength = 4;
List<string> hold = new List<string>();
for (int i = 0; i < 10000; i++)
{
SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider();
byte[] result = sha.ComputeHash(BitConverter.GetBytes(i));
string hex = null;
foreach (byte x in result)
{
hex += String.Format("{0:x2}", x);
}
int startingPositon = 0;
string possibleVoucherCode = hex.Substring(startingPositon,subStringLength);
string voucherCode = Move(subStringLength, hold, hex, startingPositon, possibleVoucherCode);
hold.Add(voucherCode);
}
Console.WriteLine("Number of Distinct values {0}", hold.Distinct().Count());
}
private static string Move(int subStringLength, List<string> hold, string hex, int startingPositon, string possibleVoucherCode)
{
if (hold.Contains(possibleVoucherCode))
{
int newPosition = startingPositon + 1;
if (newPosition <= hex.Length)
{
if ((newPosition + subStringLength) > hex.Length)
{
possibleVoucherCode = hex.Substring(newPosition, subStringLength);
return Move(subStringLength, hold, hex, newPosition, possibleVoucherCode);
}
// return something
return "0";
}
else
{
// return something
return "0";
}
}
else
{
return possibleVoucherCode;
}
}
}
It is going to be slow because you want to generate the vouchers randomly and then check the database for every generated code.
I would create a table vouchers with an id, the code and an is_used column. I would fill that table once with enough random codes. Since this can be done in a separate process, the performance won't be such a big problem. Let it run in the evening and the next day you get a fully filled vouchers-table.
If you want to prevent generating duplicate vouchers, that won't be a problem. You can generate them anyway and put them either in a System.Collections.Generic.HashSet (which prevents adding duplicates without throwing an exception) or call the Linq-method Distinct(), before adding them to that vouchers table.
If you insist on short codes:
Use a GUID as a primary key, generate one random number. How you might want to translate this in to alpha-num is up to you.
Use the last byte or two of the guid and the random number. 1234-684687 This should make it slightly less easy to bruteforce coupons. And handle any (rare) collisions with an exception.
Easy way to shorten an int, change it's base (from 10 to 62). (in VB, and this is old code)
This yields "2lkCB1" when given Int32.MaxValue
''//given intValue as your random integer
Dim result As String = String.Empty
Dim digits as String = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
Dim x As Integer
While (intValue > 0)
x = intValue Mod digits.Length
result = digits(x) & result
intValue = intValue - x
intValue = intValue \ digits.Length
End While
Return result
But now we're already answering more than one question.
For a bulk data operation like this, I would recommend not using NHibernate and just doing straight ADO.NET.
Batch Check
Since you anticipate generating big batches of codes at once, you should batch multiple code checks into a single round-trip to the database. If you're using SQL Server 2008 or higher, you could do this using table-valued parameters, checking a whole list of codes at once.
SELECT DISTINCT b.Code
FROM #batch b
WHERE NOT EXISTS (
SELECT v.Code
FROM dbo.Voucher v
WHERE v.Code = b.Code
);
Concurrency
Now, what about concurrency issues? What if two users generate the same code at roughly the same time? Or simply in-between the time when we check the code for uniqueness and when we insert it into the Voucher table?
We can take care of that by modifying the query as follows:
DECLARE #batchid uniqueidentifier;
SET #batchid = NEWID();
INSERT INTO dbo.Voucher (Code, BatchId)
SELECT DISTINCT b.Code, #batchid
FROM #batch b
WHERE NOT EXISTS (
SELECT Code
FROM dbo.Voucher v
WHERE b.Code = v.Code
);
SELECT Code
FROM dbo.Voucher
WHERE BatchId = #batchid;
Executing via .NET
Assuming that you have defined the following table-valued user type...
CREATE TYPE dbo.VoucherCodeList AS TABLE (
Code nvarchar(8) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL
/* !!! Remember to specify the collation on your Voucher.Code column too, since you want upper and lower-case codes. */
);
... you could execute this query via .NET code like this:
public ICollection<string> GenerateCodes(int numberOfCodes)
{
var result = new List<string>(numberOfCodes);
while (result.Count < numberOfCodes)
{
var batchSize = Math.Min(_batchSize, numberOfCodes - result.Count);
var batch = Enumerable.Range(0, batchSize)
.Select(x => GenerateRandomCode());
var oldResultCount = result.Count;
result.AddRange(FilterAndSecureBatch(batch));
var filteredBatchSize = result.Count - oldResultCount;
var collisionRatio = ((double)batchSize - filteredBatchSize) / batchSize;
// Automatically increment length of random codes if collisions begin happening too frequently
if (collisionRatio > _collisionThreshold)
CodeLength++;
}
return result;
}
private IEnumerable<string> FilterAndSecureBatch(IEnumerable<string> batch)
{
using (var command = _connection.CreateCommand())
{
command.CommandText = _sqlQuery; // the concurrency-safe query listed above
var metaData = new[] { new SqlMetaData("Code", SqlDbType.NVarChar, 8) };
var param = command.Parameters.Add("#batch", SqlDbType.Structured);
param.TypeName = "dbo.VoucherCodeList";
param.Value = batch.Select(x =>
{
var record = new SqlDataRecord(metaData);
record.SetString(0, x);
return record;
});
using (var reader = command.ExecuteReader())
while (reader.Read())
yield return reader.GetString(0);
}
}
Performance
After implementing all of this (and moving the command and parameter creation out of the loop so it would be re-used between batches), I was able to insert 10,000 codes using a batch size of 500 consistently in approx. 0.5 to 2 seconds, or 5 to 20 codes per millisecond.
Code Density / Collisions / Guessability
The _collisionThreshold field limits the density of your codes. It's a value between 0 and 1. Actually, it must be less than 1 or else you would wind up in an infinite loop when the 4 digit codes were exhausted (probably should add an assertion for this in code). I would recommend never turning it above 0.5 for performance reasons. More than 50% collisions would mean it's spending more time testing already-used codes than actually generating new ones.
Keeping the collision threshold low is how you would control how hard-to-guess your codes are. Setting _collisionThreshold to 0.01 would generate codes such that there's approximately a 1% chance of someone guessing a code.
If collisions occur too frequently, CodeLength (which is used by the GenerateRandomCode() method) will be incremented. This value needs to be persisted somewhere. After executing GenerateCodes(), check CodeLength to see if it has changed and then save the new value.
Source Code
The full code is available here: https://gist.github.com/3217856. I am the author of this code, and am releasing it under the MIT license. I had fun with this little challenge, and also got to learn how to pass a table-valued parameter to an inline parametrized query. I hadn't ever done that before. I've only ever passed them to full-fledged stored procedures.
A possible solution for you is like this:
Find the maximum ID of a voucher (an integer). Then, run any hash function on it, take the first 32 bits and convert to the string you want to show the user (or use a 32bit hash function such as Jenkins hash function). This will probably work, hash collisions are pretty rare. But this solution is very similar to yours, in the point of randomness.
You could run a test which finds the first 10 or 100 collisions (this should be enough for you) and forces the algorithm to "skip" them and use a different starting value. Then, you don't need to check the database at all (well, at least until you reach about 4294967296 vouchers...)
how about utilizing nHibernate's HiLo algorithm?
Here is an example on how you can get the next value (without DB access).