views:

498

answers:

5

I'm writing a C# frontend to a legacy database that uses VB6 Rnd() and Randomize() methods for user password encryption. The encryption function is very simplistic and really not all that secure, but it's what all current passwords are stored with.

What I'd like to be able to do is authenticate legacy users from a C# application. I can write new encryption (or preferably hashing) code for VB6 so that all future users have a more secure password, and that can be duplicated in C#. But I don't want to require current users to have had their password reset before they can use the new frontend.

Is there any way I can reimplement that algorithm in C# so that it produces identical results to the legacy VB6 code?

A: 

Sample VB code :

Randomize()
Dim x as Single = Rnd()

(roughly) equivalent C# code :

Random r = new Random();
double x = r.NextDouble();

The Random class constructor initializes the random number generator with the current time, which is what Randomize does. You can also pass a seed to the constructor, which is equivalent to calling Randomize with a seed parameter

Thomas Levesque
I need the function to return deterministic results equal to the results out of VB6: Reimplementing the algorithm is doable (similar results, but not the same). Duplicating the algorithm is what I need help with.
Thomas G. Mayfield
Basically, given a known seed, I need C# random functions (or marshalled COM objects, or whatever) to return the same random numbers.
Thomas G. Mayfield
I believe you need to make the Random object static, otherwise you won't get much randomness....
code4life
@code4life, it depends on what you do with it... if you just call it once (or not very often), it's ok to create a new Random object every time. But if you use it in a loop, of course you should reuse the same Random object
Thomas Levesque
+1  A: 

It should be possible. The tricky part will be emulating the calls to Visual Basic's Randomize statement and Rnd function.

I just found a knowledge base article that looks like it might have the information that you'll need:

How Visual Basic Generates Pseudo-Random Numbers for the RND Function

EDIT...

After some investigation, it appears that the Randomize and Rnd implementations in recent versions of Visual Basic use exactly the same algorithms as VB6.

So, the good news is that you don't need to figure out and re-implement the VB6 algorithms yourself. Just import the Microsoft.VisualBasic namespace and you can call the built-in methods from C#:

using Microsoft.VisualBasic;

// ...

float x = VBMath.Rnd(-1);
VBMath.Randomize(password.Length);
float y = VBMath.Rnd();
// etc

(And if you're still curious about the actual algorithms used, you can always take a look in Reflector!)

LukeH
Duplicating the behavior of Rnd() is done and working--without changing the seed, I get the same sequence as VBA code. But I can't seem to find an exact reference for how to duplicate Randomize().
Thomas G. Mayfield
I got the impression from the KB article (although it's not stated explicitly) that `Randomize` simply reseeds the RNG. So, for example, "`Randomize Len(password)`" would become "`x0=password.Length`". Have you tried that?
LukeH
Yep. http://www.noesis.net.au/main/Resources/Resources/prng.html gives a rough reference, but it definitely mangles the data.
Thomas G. Mayfield
And you can tell from VB reference that requires Rnd(Negative Number) that Randomize() uses the current seed to generate the new one. Rnd(Negative) uses that negative value as the seed of the calculation, which by itself would reseed to a specific value. The code I'm duplicating uses Rnd(-1) : Randomize(Len(password)).
Thomas G. Mayfield
A: 

Buddies,

I have the same issue as Thomas. I tryed to use Microsoft.VisualBasic (Randomize and Rnd) functions in C# but the generated random number is different between the number generated in VB6 to the same seed.

Anybody have another suggestion?

Thanks in advance for all!

Saulo

Saulo
Isn't that expected...? Unless you want "constant" randomization (LOL)...
code4life
A: 

Buddies, I'm sorry!

What I wrote before is not true. I fix my code and the numbers generated by the VBMath.Rnd function in C# app are the same generated by the VB6 application.

Thanks a lot to LukeH for the correct answer.

See ya,

Saulo

Saulo
A: 

You can generate the same sequence from VB6 and C#. Just watch our for rounding errors (the results from C# are more precise). Make sure to call VBMath.Rnd(-1) before passing in a new seed to VBMath.Randomize().

[TestFixture]
public class VbaRandomTests
{
    // Random numbers generated from a known seed from VB6
    [TestCase(1, new[] { 0.333575300f, 0.068163870f, 0.593829300f, 0.766039500f, 0.189289400f, 0.537398600f, 0.326994400f, 0.393937000f, 0.073419150f, 0.831542500f, 0.854963000f, 0.828829900f, 0.962344000f, 0.833957400f, 0.090149820f, 0.645974500f, 0.192794900f, 0.346950500f, 0.188133400f, 0.691135000f })]
    [TestCase(32, new[] { 0.579913200f, 0.579150200f, 0.310870300f, 0.864916400f, 0.142658500f, 0.927291200f, 0.407316600f, 0.402970200f, 0.296319500f, 0.412841300f, 0.361066500f, 0.560519300f, 0.017275630f, 0.919162500f, 0.084534590f, 0.912820200f, 0.642257800f, 0.248561900f, 0.733299400f, 0.305637000f })]
    [TestCase(327680, new[] { 0.882708600f, 0.733264000f, 0.661029000f, 0.376940400f, 0.919086800f, 0.660506500f, 0.020170630f, 0.126908200f, 0.437005600f, 0.053283210f, 0.252240800f, 0.449496400f, 0.662844500f, 0.044955970f, 0.519654200f, 0.169961300f, 0.183334400f, 0.687831900f, 0.227989400f, 0.384067200f })]
    public void generates_same_results_as_VB6(int seed, float[] values)
    {
        VBMath.Rnd(-1);
        VBMath.Randomize(seed);

        float[] results = new float[values.Length];
        for (int index = 0; index < results.Length; index++)
        {
            results[index] = VBMath.Rnd();
        }

        CollectionAssert.AreEqual(values, results, new FloatEpsilonComparer(0.0000001f));
    }

    private class FloatEpsilonComparer
        : IComparer<float>, IComparer
    {
        private readonly float _epsilon;

        public FloatEpsilonComparer(float epsilon)
        {
            _epsilon = epsilon;
        }

        public int Compare(float x, float y)
        {
            float difference = x - y;

            if (Math.Abs(difference) < _epsilon)
            {
                return 0;
            }
            if (x < y)
            {
                return -1;
            }
            return 1;
        }

        public int Compare(object x, object y)
        {
            float xF = Convert.ToSingle(x);
            float yF = Convert.ToSingle(y);
            return Compare(xF, yF);
        }
    }
}
Thomas G. Mayfield