Any Pseudo Random Number Generator using a Linear Congruential Generator, and no cryptography, is going to be unsafe, or at the very least unsatisfactory, per the criteria in our FAQ. Likely, a skilled adversary would be able to predict future output from some amount of past output with moderate work; at best, that won't happen, but there will be no sound argument that it is not possible. There are many more or less difficult to read papers making that point. A recent question, another there, further illustrate this. If the more secure requirement is to be taken any seriously, it is best to forget the LCG entirely, and use cryptography.
There should be three steps in a secure "Random Number Generator depending on a key" producing an output sequence uniformly distributed on the integers in $[1…100]$:
Stretching of the initial key. The purpose is to make brute force search of the key infeasible, or at least costly. That is superfluous if the key is already both wide enough (say 120 bit or more; 256 is aplenty) and chosen uniformly at random; in almost any other case, including when a human is expected to remember (or worse choose) the key, a deliberately slow transformation of that input is highly recommended. In many cases (notably: the combination of multiple users and use of password), the initial key should be salted while stretched. The standard building block for these functions is a Password-Based Key Derivation Function. A common one is PBKDF2; Bcrypt is much safer; the current state of the art is Scrypt.
A Cryptographically Secure Pseudo Random Number Generator, or equivalently the Keystream Generator of a secure Stream Cipher. We'll feed it the (possibly stretched) key (in the input named seed or key), and then it will output on demand practically any number of random-looking bits that we may want. The design of that cryptographic primitive strives to make that output computationally indistinguishable from random by an adversary not knowing the seed. There are a plethora of CSPRNG and Stream Ciphers to choose among, and the best choice depends on context. If it must be fast on any modern CPU, Salsa20 comes to mind.
Post-conditioning to obtain outputs uniformly distributed on the desired output interval $[u…v]$, here $[1…100]$. One simple suitable technique (slightly wasteful, but that does not matter since the CSPRNG is likely fast) is to:
- Find the lowest integer $w$ such that $2^w\ge v-u+1$; here that is $w=7$, since $128\ge100$.
- Obtain $w$ bits using the CSPRNG, forming an integer $r$ in range $[0…2^w-1]$, until $r<v-u+1$ (otherwise, discard $r$ and repeat this step).
- Output $u+r$, and proceed to the previous step as needed to obtain more output.
If being simple and based on a LCG is important, and being secure is pointless as long as the output is good enough to pass some simple statistical test, the following procedure builds a LCG and post-condition its output (I assume the range $[u…v]$ has width no more than about $2^{16}$).
- Choose $m=2^{64}$ (or a convenient higher power of $2$), $a=3141592621$, $c=1$ (this conforms to more detailed advice in TAOCP Volume 2, section 3.6; except that I kept $a$ unchanged, rather than taking the risk to scale it with $m$); the C or C++ type
unsigned long long
, if available, is suitable for implicit reduction modulo some appropriate implementation-dependent $m$.
- Find $w$ from the interval $[u…v]$ as above (here $w=7$ for $[1…100]$).
- Set $X$ to the key truncated to the bit width of $m$ (Repeat: security is out of scope! But still, it does not harm to feed other key bits in $c$, as long as it is forced to be odd).
- Set $X$ to $a⋅X+c\bmod m$; in C that's
X = a*X+c;
.
- Form $r$ in range $[0…2^w-1]$ from the $w$ high bits of $X$; in C that's
r = X>>(W-w);
where W
is the bit width of the unsigned integer type chosen for X
, and $m=2^{\mathtt W}$.
- If $r\ge v-u+1$, proceed to step 4.
- Output $u+r$, and proceed to step 4 as needed to obtain more output.