Your question asks about a 4-byte random hexadecimal generator, but in the comments you clarify that you only want 4 hex digits, which means there are only 2**16 combinations. That makes the problem rather easy: we just create a list of all 65,536 combinations and shuffle it, and then we can simply iterate over that shuffled list. To save a little time & RAM I create a list of integers & just convert the integers to hex strings as needed.
The fact that you've got a list of 124 codes that are already in use adds a little bit of complexity, but we can handle that by putting those codes into a set; we can easily test the generated codes to see if they're in the used set.
Obviously, we want to be able to run the program multiple times, so we save the index number of the shuffled list into a text file. This is simpler and more efficient than storing each number that we generate into the used set.
We also need the randomization to be consistent, so we need to supply a seed to the random number generator.
Here's some Python 3 code. It can be adapted to run on Python 2 (by changing FileNotFoundError to IOError), but you cannot switch versions between runs because the sequence of random numbers generated by Python 2 will not be the same as that generated by Python 3
from random import seed, shuffle
passphrase = 'My secret passphrase'
seed(passphrase)
# Codes that were already in use.
# This list could be read from a file instead of being embedded in the script
used = '''\
2b2d
40a7
c257
d929
c252
5805
2936
8b20
'''
# Convert to a set of strings
used = set(used.splitlines())
# The index of the next number to generate is stored in this file
fname = 'nextindex.txt'
try:
with open(fname) as f:
idx = int(f.read())
except FileNotFoundError:
idx = 0
print('Using index', idx)
allnums = list(range(0x10000))
shuffle(allnums)
#Search for the next code that's not in the `used` set
while True:
hexcode = format(allnums[idx], '04x')
idx += 1
if hexcode not in used:
print('Code:', hexcode)
break
# Save the next index
with open(fname, 'w') as f:
print('Saving index', idx)
f.write(str(idx))
output from 3 runs
Using index 0
Code: d0fc
Saving index 1
Using index 1
Code: d7e9
Saving index 3
Using index 3
Code: fc42
Saving index 4
As you can see, index 2 gets skipped, that's because it corresponds to a code in the used set.
2**16 == 65536different combinations. That doesn't sound very secure...