Zufällige Daten in Python generieren (Anleitung)

Zufällige Daten in Python generieren (Anleitung)

Wie zufällig ist zufällig? Dies ist eine seltsame Frage, die jedoch in Fällen, in denen es um Informationssicherheit geht, von größter Bedeutung ist. Wann immer Sie zufällige Daten, Zeichenfolgen oder Zahlen in Python generieren, ist es eine gute Idee, zumindest eine grobe Vorstellung davon zu haben, wie diese Daten generiert wurden.

Hier werden Sie eine Handvoll verschiedener Optionen zum Generieren zufälliger Daten in Python behandeln und anschließend einen Vergleich der einzelnen Daten hinsichtlich Sicherheit, Vielseitigkeit, Zweck und Geschwindigkeit erstellen.

Ich verspreche, dass dieses Tutorial keine Lektion in Mathematik oder Kryptographie sein wird, über die ich überhaupt nicht gut sprechen könnte. Sie werden genauso viel Mathe lernen wie nötig und nicht mehr.

Wie zufällig ist zufällig?

Erstens ist ein prominenter Haftungsausschluss erforderlich. Die meisten mit Python generierten Zufallsdaten sind im wissenschaftlichen Sinne des Wortes nicht vollständig zufällig. Vielmehr handelt es sich um Pseudozufallszahlen , die mit einem Pseudozufallszahlengenerator (PRNG) generiert werden. Dies ist im Wesentlichen ein beliebiger Algorithmus zum Generieren scheinbar zufälliger, aber immer noch reproduzierbarer Daten.

"Wahre" Zufallszahlen können von einem echten Zufallszahlengenerator (TRNG) generiert werden. Ein Beispiel ist, wiederholt einen Würfel vom Boden aufzunehmen, ihn in die Luft zu werfen und ihn landen zu lassen, wie es mag.

Angenommen, Ihr Wurf ist unvoreingenommen, dann haben Sie wirklich keine Ahnung, auf welcher Zahl der Würfel landen wird. Das Würfeln ist eine grobe Form der Verwendung von Hardware, um eine Zahl zu generieren, die überhaupt nicht deterministisch ist. (Oder Sie können dies von dice-o-matic für Sie erledigen lassen.) TRNGs fallen jedoch nicht in den Geltungsbereich dieses Artikels dennoch zu Vergleichszwecken erwähnenswert.

PRNGs, die normalerweise eher mit Software als mit Hardware erstellt werden, funktionieren etwas anders. Hier ist eine kurze Beschreibung:

_ Sie beginnen mit einer Zufallszahl, die als Startwert bezeichnet wird, und verwenden dann einen Algorithmus, um eine darauf basierende pseudozufällige Folge von Bits zu generieren. (Source) _

Sie wurden wahrscheinlich aufgefordert, "die Dokumente zu lesen!" irgendwann. Nun, diese Leute liegen nicht falsch. Hier ist ein besonders bemerkenswerter Ausschnitt aus der Dokumentation des Moduls "+ random +", den Sie nicht missen möchten:

_ Warnung : Die Pseudozufallsgeneratoren dieses Moduls sollten nicht aus Sicherheitsgründen verwendet werden. (Source) _

Sie haben wahrscheinlich + random.seed (999) +, + random.seed (1234) + oder ähnliches in Python gesehen. Dieser Funktionsaufruf setzt den zugrunde liegenden Zufallszahlengenerator, der vom Python-Modul "+ random +" verwendet wird. Es ist das, was nachfolgende Aufrufe zur Erzeugung von Zufallszahlen deterministisch macht: Eingabe A erzeugt immer Ausgabe B. Dieser Segen kann auch ein Fluch sein, wenn er böswillig eingesetzt wird.

Vielleicht scheinen die Begriffe „zufällig“ und „deterministisch“ nicht nebeneinander zu existieren. Um dies klarer zu machen, finden Sie hier eine extrem reduzierte Version von "+ random () ", die iterativ eine "Zufallszahl" mit " x = (x *3)% 19 " erstellt. ` x +` ist ursprünglich als Startwert definiert und verwandelt sich dann in eine deterministische Folge von Zahlen, die auf diesem Startwert basieren:

class NotSoRandom(object):
    def seed(self, a=3):
        """Seed the world's most mysterious random number generator."""
        self.seedval = a
    def random(self):
        """Look, random numbers!"""
        self.seedval = (self.seedval* 3) % 19
        return self.seedval

_inst = NotSoRandom()
seed = _inst.seed
random = _inst.random

Nehmen Sie dieses Beispiel nicht zu wörtlich, da es hauptsächlich zur Veranschaulichung des Konzepts gedacht ist. Wenn Sie den Startwert 1234 verwenden, sollte die nachfolgende Folge von Aufrufen von + random () + immer identisch sein:

>>>

>>> seed(1234)
>>> [random() for _ in range(10)]
[16, 10, 11, 14, 4, 12, 17, 13, 1, 3]

>>> seed(1234)
>>> [random() for _ in range(10)]
[16, 10, 11, 14, 4, 12, 17, 13, 1, 3]

Sie werden in Kürze eine ernsthaftere Darstellung davon sehen.

Was ist "kryptografisch sicher"?

Wenn Sie mit den Akronymen "RNG" nicht genug haben, lassen Sie uns noch eines in die Mischung aufnehmen: ein CSPRNG oder ein kryptografisch sicheres PRNG. CSPRNGs eignen sich zum Generieren vertraulicher Daten wie Kennwörter, Authentifikatoren und Token. Bei einer zufälligen Zeichenfolge kann Malicious Joe realistisch nicht bestimmen, welche Zeichenfolge vor oder nach dieser Zeichenfolge in einer Folge von zufälligen Zeichenfolgen kam.

Ein anderer Begriff, den Sie möglicherweise sehen, ist Entropie . Kurz gesagt bezieht sich dies auf das Ausmaß der eingeführten oder gewünschten Zufälligkeit. Beispielsweise definiert ein Python module, das Sie hier behandeln, + DEFAULT_ENTROPY = 32 +, die Anzahl der Bytes standardmäßig zurückkehren. Die Entwickler halten dies für "genug" Bytes, um eine ausreichende Menge an Rauschen zu erzeugen.

*Hinweis* : In diesem Tutorial gehe ich davon aus, dass sich ein Byte auf 8 Bit bezieht, wie es seit den 1960er Jahren der Fall ist, und nicht auf eine andere Einheit der Datenspeicherung. Sie können dies als https://en.wikipedia.org/wiki/Octet_(computing)[_octet_] bezeichnen, wenn Sie dies bevorzugen.

Ein wichtiger Punkt bei CSPRNGs ist, dass sie immer noch pseudozufällig sind. Sie sind auf eine Weise konstruiert, die intern deterministisch ist, aber sie fügen eine andere Variable hinzu oder haben eine Eigenschaft, die sie „zufällig genug“ macht, um das Zurücksetzen in eine Funktion zu verhindern, die Determinismus erzwingt.

Was Sie hier behandeln werden

In der Praxis bedeutet dies, dass Sie einfache PRNGs zur statistischen Modellierung, Simulation und Reproduzierbarkeit von Zufallsdaten verwenden sollten. Sie sind auch erheblich schneller als CSPRNGs, wie Sie später sehen werden. Verwenden Sie CSPRNGs für Sicherheits- und kryptografische Anwendungen, bei denen Datensensitivität unerlässlich ist.

In diesem Lernprogramm werden nicht nur die oben genannten Anwendungsfälle erläutert, sondern auch Python-Tools für die Verwendung von PRNGs und CSPRNGs vorgestellt:

  • Zu den PRNG-Optionen gehören das Modul "+ random " aus der Standardbibliothek von Python und das Array-basierte NumPy-Gegenstück " numpy.random +".

  • Die Python-Module "+ os ", " secret " und " uuid +" enthalten Funktionen zum Generieren kryptografisch sicherer Objekte.

Sie werden alle oben genannten Punkte ansprechen und mit einem Vergleich auf hoher Ebene abschließen.

PRNGs in Python

Das + random + Modul

Das wahrscheinlich bekannteste Tool zum Generieren von Zufallsdaten in Python ist das Modul + random +, das den PRNG-Algorithmus Mersenne Twister verwendet als sein Kerngenerator.

Früher haben Sie kurz + random.seed () + angesprochen, und jetzt ist ein guter Zeitpunkt, um zu sehen, wie es funktioniert. Lassen Sie uns zunächst einige zufällige Daten ohne Seeding erstellen. Die Funktion + random.random () + gibt einen zufälligen Float im Intervall [0.0, 1.0) zurück. Das Ergebnis ist immer kleiner als der rechte Endpunkt (1.0). Dies wird auch als halboffener Bereich bezeichnet:

>>>

>>> # Don't call `random.seed()` yet
>>> import random
>>> random.random()
0.35553263284394376
>>> random.random()
0.6101992345575074

Wenn Sie diesen Code selbst ausführen, wette ich, dass die auf Ihrem Computer zurückgegebenen Zahlen unterschiedlich sind. Die default, wenn Sie den Generator nicht setzen, dient dazu, Ihre aktuelle Systemzeit oder eine „Zufallsquelle“ von Ihrem zu verwenden Betriebssystem, falls eines verfügbar ist.

Mit + random.seed () + können Sie Ergebnisse reproduzierbar machen, und die Aufrufkette nach + random.seed () + erzeugt den gleichen Datenspur:

>>>

>>> random.seed(444)
>>> random.random()
0.3088946587429545
>>> random.random()
0.01323751590501987

>>> random.seed(444)  # Re-seed
>>> random.random()
0.3088946587429545
>>> random.random()
0.01323751590501987

Beachten Sie die Wiederholung von „Zufallszahlen“. Die Folge von Zufallszahlen wird deterministisch oder wird vollständig durch den Startwert 444 bestimmt.

Werfen wir einen Blick auf einige grundlegendere Funktionen von + random +. Oben haben Sie einen zufälligen Float generiert. Mit der Funktion + random.randint () + können Sie in Python eine zufällige Ganzzahl zwischen zwei Endpunkten generieren. Dies erstreckt sich über das gesamte [x, y] -Intervall und kann beide Endpunkte umfassen:

>>>

>>> random.randint(0, 10)
7
>>> random.randint(500, 50000)
18601

Mit + random.randrange () + können Sie die rechte Seite des Intervalls ausschließen, dh die generierte Zahl liegt immer innerhalb von [x, y) und ist immer kleiner als der rechte Endpunkt:

>>>

>>> random.randrange(1, 10)
5

Wenn Sie zufällige Floats generieren müssen, die innerhalb eines bestimmten [x, y] -Intervalls liegen, können Sie + random.uniform () + verwenden, das aus https://en.wikipedia.org/wiki/continuous_uniform_distribution [ kontinuierliche gleichmäßige Verteilung]:

>>>

>>> random.uniform(20, 30)
27.42639687016509
>>> random.uniform(30, 40)
36.33865802745107

Um ein zufälliges Element aus einer nicht leeren Sequenz (wie einer Liste oder einem Tupel) auszuwählen, können Sie + random.choice () + verwenden. Es gibt auch + random.choices () + zum Auswählen mehrerer Elemente aus einer Sequenz mit Ersetzung (Duplikate sind möglich):

>>> items = ['one', 'two', 'three', 'four', 'five']
>>> random.choice(items)
'four'

>>> random.choices(items, k=2)
['three', 'three']
>>> random.choices(items, k=3)
['three', 'five', 'four']

Verwenden Sie + random.sample () +, um die ersatzlose Stichprobe nachzuahmen:

>>>

>>> random.sample(items, 4)
['one', 'five', 'four', 'three']

Sie können eine Sequenz direkt an Ort und Stelle mit + random.shuffle () + randomisieren. Dadurch wird das Sequenzobjekt geändert und die Reihenfolge der Elemente zufällig festgelegt:

>>>

>>> random.shuffle(items)
>>> items
['four', 'three', 'two', 'one', 'five']

Wenn Sie die ursprüngliche Liste lieber nicht mutieren möchten, müssen Sie zuerst erst eine Kopie erstellen und dann die Kopie mischen. Sie können Kopien von Python-Listen mit dem Modul https://docs.python.org/library/copy.html [+ copy +] oder einfach mit + x [:] + oder + x.copy () erstellen + `, wobei + x + `die Liste ist.

Bevor wir mit der Generierung von Zufallsdaten mit NumPy fortfahren, schauen wir uns noch eine etwas komplexere Anwendung an: die Generierung einer Folge eindeutiger Zufallszeichenfolgen mit einheitlicher Länge.

Es kann hilfreich sein, zuerst über das Design der Funktion nachzudenken. Sie müssen aus einem „Pool“ von Zeichen wie Buchstaben, Zahlen und/oder Satzzeichen auswählen, diese zu einer einzigen Zeichenfolge kombinieren und dann überprüfen, ob diese Zeichenfolge noch nicht generiert wurde. Ein Python + set + funktioniert gut für diese Art von Mitgliedschaftstests:

import string

def unique_strings(k: int, ntokens: int,
               pool: str=string.ascii_letters) -> set:
    """Generate a set of unique string tokens.

    k: Length of each token
    ntokens: Number of tokens
    pool: Iterable of characters to choose from

    For a highly optimized version:
    https://stackoverflow.com/a/48421303/7954504
    """

    seen = set()

    # An optimization for tightly-bound loops:
    # Bind these methods outside of a loop
    join = ''.join
    add = seen.add

    while len(seen) < ntokens:
        token = join(random.choices(pool, k=k))
        add(token)
    return seen

+ ''. join () + verbindet die Buchstaben von + random.choices () + zu einem einzelnen Python + str + der Länge + k +. Dieses Token wird dem Satz hinzugefügt, der keine Duplikate enthalten darf, und die Schleife "+ while +" wird ausgeführt, bis der Satz die von Ihnen angegebene Anzahl von Elementen enthält.

*Ressource* : Pythons https://docs.python.org/3/library/string.html [`+ string +`] Modul enthält eine Reihe nützlicher Konstanten: `+ ascii_lowercase +`, `+ ascii_uppercase +`, `+ string. Interpunktion + `,` + ascii_whitespace + `und eine Handvoll anderer.

Probieren wir diese Funktion aus:

>>>

>>> unique_strings(k=4, ntokens=5)
{'AsMk', 'Cvmi', 'GIxv', 'HGsZ', 'eurU'}

>>> unique_strings(5, 4, string.printable)
{"'O*1!", '9Ien%', 'W=m7<', 'mUD|z'}

Für eine genau abgestimmte Version dieser Funktion verwendet https://stackoverflow.com/a/48421303/7954504 [diese Antwort zum Stapelüberlauf] Generatorfunktionen, Namensbindung und einige andere erweiterte Tricks, um eine schnellere, kryptografisch sichere Version von zu erstellen + unique_strings () + oben.

PRNGs für Arrays: + numpy.random +

Eine Sache, die Sie vielleicht bemerkt haben, ist, dass ein Großteil der Funktionen von + random + einen skalaren Wert zurückgibt (ein einzelnes + int +, + float + oder ein anderes Objekt). Wenn Sie eine Folge von Zufallszahlen generieren möchten, können Sie dies mit einem Python-Listenverständnis erreichen:

>>>

>>> [random.random() for _ in range(5)]
[0.021655420657909374,
 0.4031628347066195,
 0.6609991871223335,
 0.5854998250783767,
 0.42886606317322706]

Es gibt jedoch noch eine andere Option, die speziell dafür entwickelt wurde. Sie können sich NumPys eigenes https://docs.scipy.org/doc/numpy/reference/routines.random.html [+ numpy.random +] Paket als das + random + der Standardbibliothek vorstellen, jedoch für https ://realpython.com/numpy-array-programming/[NumPy-Arrays]. (Es ist auch mit der Fähigkeit ausgestattet, aus viel mehr statistischen Verteilungen zu ziehen.)

Beachten Sie, dass + numpy.random + ein eigenes PRNG verwendet, das vom einfachen alten + random + getrennt ist. Sie werden keine deterministisch zufälligen NumPy-Arrays mit einem Aufruf von Pythons eigenem + random.seed () + erzeugen:

>>>

>>> import numpy as np
>>> np.random.seed(444)
>>> np.set_printoptions(precision=2)  # Output decimal fmt.

Hier einige Beispiele, die Ihren Appetit anregen sollen:

>>>

>>> # Return samples from the standard normal distribution
>>> np.random.randn(5)
array([ 0.36,  0.38,  1.38,  1.18, -0.94])

>>> np.random.randn(3, 4)
array([[p` is the probability of choosing each element
>>> np.random.choice([0, 1], p=[0.6, 0.4], size=(5, 4))
array([[In the syntax for `+randn(d0, d1, ..., dn)+`, the parameters `+d0, d1, ..., dn+` are optional and indicate the shape of the final object. Here, `+np.random.randn(3, 4)+` creates a 2d array with 3 rows and 4 columns. The data will be https://en.wikipedia.org/wiki/Independent_and_identically_distributed_random_variables[i.i.d.], meaning that each data point is drawn independent of the others.

Another common operation is to create a sequence of random Boolean values, `+True+` or `+False+`. One way to do this would be with `+np.random.choice([True, False])+`. However, it’s actually about 4x faster to choose from `+(0, 1)+` and then https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.view.html[view-cast] these integers to their corresponding Boolean values:

[.repl-toggle]#>>>#

[source,python,repl]

>>> # NumPys randint ist [inklusive, exklusiv), im Gegensatz zu` random.randint ()` >>> np.random.randint (0, 2, size = 25, dtype = np.uint8) .view (bool ) array ([Richtig, Falsch, Richtig, Richtig, Falsch, Richtig, Falsch, Falsch, Falsch, Falsch, Falsch, Richtig, Richtig, Falsch, Falsch, Falsch, Richtig, Falsch, Richtig, Falsch, Richtig, Richtig, Richtig, Richtig Falsch Richtig])

What about generating correlated data? Let’s say you want to simulate two correlated time series. One way of going about this is with NumPy’s https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.multivariate_normal.html#numpy.random.multivariate_normal[`+multivariate_normal()+`] function, which takes a covariance matrix into account. In other words, to draw from a single normally distributed random variable, you need to specify its mean and variance (or standard deviation).

To sample from the https://en.wikipedia.org/wiki/Multivariate_normal[multivariate normal] distribution, you specify the means and covariance matrix, and you end up with multiple, correlated series of data that are each approximately normally distributed.

However, rather than covariance, https://en.wikipedia.org/wiki/correlation[correlation] is a measure that is more familiar and intuitive to most. It’s the covariance normalized by the product of standard deviations, and so you can also define covariance in terms of correlation and standard deviation:

https://files.realpython.com/media/scalar_equation.2ef9746c8834.jpg[image:https://files.realpython.com/media/scalar_equation.2ef9746c8834.jpg[Covariance in Scalar Form,width=620,height=88]]

So, could you draw random samples from a multivariate normal distribution by specifying a correlation matrix and standard deviations? Yes, but you’ll need to get the above https://blogs.sas.com/content/iml/2010/12/10/converting-between-correlation-and-covariance-matrices.html[into matrix form] first. Here, *_S_ *is a vector of the standard deviations,* _P_ *is their correlation matrix, and* _C_* is the resulting (square) covariance matrix:

https://files.realpython.com/media/matrix_equation.d70f9fd73960.jpg[image:https://files.realpython.com/media/matrix_equation.d70f9fd73960.jpg[Covariance in Matrix Form,width=610,height=78]]

This can be expressed in NumPy as follows:

[source,python]

def corr2cov (p: np.ndarray, s: np.ndarray) → np.ndarray: "" Kovarianzmatrix aus Korrelation und Standardabweichungen "" d = np.diag (s) return d @ p @ d

Now, you can generate two time series that are correlated but still random:

[.repl-toggle]#>>>#

[source,python,repl]

>>> # Beginnen Sie mit einer Korrelationsmatrix und Standardabweichungen. >>> # -0.40 ist die Korrelation zwischen A und B, und die Korrelation >>> # einer Variablen mit sich selbst ist 1.0. >>> corr = np.array ([[Standardabweichungen/Mittelwerte von A bzw. B >>> stdev = np.array ([6., 1.]) >>> mean = np.array ([2. , 0,5]) >>> cov = corr2cov (corr, stdev)

>>> # size ist die Länge der Zeitreihen für 2D-Daten >>> # (500 Monate, Tage usw.). >>> data = np.random.multivariate_normal (Mittelwert = Mittelwert, cov = cov, Größe = 500) >>> data [: 10] array ([[data.shape (500, 2))

You can think of `+data+` as 500 pairs of inversely correlated data points. Here’s a sanity check that you can back into the original inputs, which approximate `+corr+`, `+stdev+`, and `+mean+` from above:

[.repl-toggle]#>>>#

[source,python,repl]

>>> Array np.corrcoef (data, rowvar = False) (Array [[data.std (axis = 0) ([5.96, 1.01]))

>>> data.mean (Achse = 0) Array ([2.13, 0.49])

Before we move on to CSPRNGs, it might be helpful to summarize some `+random+` functions and their `+numpy.random+` counterparts:

[cols=",,",options="header",]
|===
|Python `+random+` Module |NumPy Counterpart |Use
|`+random()+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.rand.html#numpy.random.rand[`+rand()+`] |Random float in [0.0, 1.0)
|`+randint(a, b)+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.random_integers.html#numpy.random.random_integers[`+random_integers()+`] |Random integer in [a, b]
|`+randrange(a, b[, step])+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.randint.html#numpy.random.randint[`+randint()+`] |Random integer in [a, b)
|`+uniform(a, b)+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.uniform.html#numpy.random.uniform[`+uniform()+`] |Random float in [a, b]
|`+choice(seq)+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.choice.html#numpy.random.choice[`+choice()+`] |Random element from `+seq+`
|`+choices(seq, k=1)+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.choice.html#numpy.random.choice[`+choice()+`] |Random `+k+` elements from `+seq+` with replacement
|`+sample(population, k)+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.choice.html#numpy.random.choice[`+choice()+`] with `+replace=False+` |Random `+k+` elements from `+seq+` without replacement
|`+shuffle(x[, random])+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.shuffle.html#numpy.random.shuffle[`+shuffle()+`] |Shuffle the sequence `+x+` in place
|`+normalvariate(mu, sigma)+` or `+gauss(mu, sigma)+` |https://docs.scipy.org/doc/numpy/reference/generated/numpy.random.normal.html#numpy.random.normal[`+normal()+`] |Sample from a normal distribution with mean `+mu+` and standard deviation `+sigma+`
|===

*Note*: NumPy is specialized for building and manipulating large, multidimensional arrays. If you just need a single value, `+random+` will suffice and will probably be faster as well. For small sequences, `+random+` may even be faster too, because NumPy does come with some overhead.

Now that you’ve covered two fundamental options for PRNGs, let’s move onto a few more secure adaptations.

=== CSPRNGs in Python

[[osurandom-about-as-random-as-it-gets]]
==== `+os.urandom()+`: About as Random as It Gets

Python’s https://docs.python.org/library/os.html#os.urandom[`+os.urandom()+`] function is used by both https://github.com/python/cpython/blob/b225cb770fb17596298f5a05c41a7c90c470c4f8/Lib/secrets.py#L47[`+secrets+`] and https://github.com/python/cpython/blob/b225cb770fb17596298f5a05c41a7c90c470c4f8/Lib/uuid.py#L621[`+uuid+`] (both of which you’ll see here in a moment). Without getting into too much detail, `+os.urandom()+` generates operating-system-dependent random bytes that can safely be called cryptographically secure:

* On Unix operating systems, it reads random bytes from the special file `+/dev/urandom+`, which in turn “allow access to environmental noise collected from device drivers and other sources.” (Thank you, https://en.wikipedia.org/wiki//dev/random[Wikipedia].) This is garbled information that is particular to your hardware and system state at an instance in time but at the same time sufficiently random.
 *On Windows, the C++ function https://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(v=vs.85).aspx[`+CryptGenRandom()+`] is used. This function is still technically pseudorandom, but it works by generating a seed value from variables such as the process ID, memory status, and so on.

With `+os.urandom()+`, there is no concept of manually seeding. While still technically pseudorandom, this function better aligns with how we think of randomness. The only argument is the number of https://docs.python.org/library/stdtypes.html#bytes[bytes] to return:

[.repl-toggle]#>>>#

[source,python,repl]

>>> os.urandom (3) b '\ xa2 \ xe8 \ x02'

>>> x = os.urandom (6) >>> x b '\ xce \ x11 \ xe7 "! \ x84'

>>> Typ (x), Länge (x) (Bytes, 6)

Before we go any further, this might be a good time to delve into a mini-lesson on https://docs.python.org/howto/unicode.html[character encoding]. Many people, including myself, have some type of allergic reaction when they see `+bytes+` objects and a long line of `+\x+` characters. However, it’s useful to know how sequences such as `+x+` above eventually get turned into strings or numbers.

`+os.urandom()+` returns a sequence of single bytes:

[.repl-toggle]#>>>#

[source,python,repl]

>>> x b '\ xce \ x11 \ xe7 "! \ x84'

But how does this eventually get turned into a Python `+str+` or sequence of numbers?

First, recall one of the fundamental concepts of computing, which is that a byte is made up of 8 bits. You can think of a bit as a single digit that is either 0 or 1. A byte effectively chooses between 0 and 1 eight times, so both `+01101100+` and `+11110000+` could represent bytes. Try this, which makes use of Python https://realpython.com/python-f-strings/[f-strings] introduced in Python 3.6, in your interpreter:

[.repl-toggle]#>>>#

[source,python,repl]

>>> binär = [f '{i: 0> 8b}' für i im Bereich (256)] >>> binär [: 16] ['00000000', '00000001', '00000010', '00000011', ' 00000100 ',' 00000101 ',' 00000110 ',' 00000111 ',' 00001000 ',' 00001001 ',' 00001010 ',' 00001011 ',' 00001100 ',' 00001101 ',' 00001110 ',' 00001111 ']

This is equivalent to `+[bin(i) for i in range(256)]+`, with some special formatting. https://docs.python.org/3/library/functions.html#bin[`+bin()+`] converts an integer to its binary representation as a string.

Where does that leave us? Using `+range(256)+` above is not a random choice. (No pun intended.) Given that we are allowed 8 bits, each with 2 choices, there are `+2*  *8 == 256+` possible bytes “combinations.”

This means that each byte maps to an integer between 0 and 255. In other words, we would need more than 8 bits to express the integer 256. You can verify this by checking that `+len(f'{256:0>8b}')+` is now 9, not 8.

Okay, now let’s get back to the `+bytes+` data type that you saw above, by constructing a sequence of the bytes that correspond to integers 0 through 255:

[.repl-toggle]#>>>#

[source,python,repl]

>>> Bisse = Bytes (Bereich (256))

If you call `+list(bites)+`, you’ll get back to a Python list that runs from 0 to 255. But if you just print `+bites+`, you get an ugly looking sequence littered with backslashes:

[.repl-toggle]#>>>#

[source,python,repl]

>>> beißt b '\ x00 \ x01 \ x02 \ x03 \ x04 \ x05 \ x06 \ x07 \ x08 \ t \ n \ x0b \ x0c \ r \ x0e \ x0f \ x10 \ x11 \ x12 \ x13 \ x14 \ x15 '' \ x16 \ x17 \ x18 \ x19 \ x1a \ x1b \ x1c \ x1d \ x1e \ x1f! "# $% & \ '()* +, -./0123456789 :; <⇒? @ ABCDEFGHIJK' 'LMNOPQRSTUVWXYZ [\\] ^ _ `abcdefghijklmnopqrstuvwxyz {|} ~ \ x7f \ x80 \ x81 \ x82 \ x83 \ x84 \ x85 \ x86 '' \ x87 \ x88 \ x89 \ x8a \ x8b \ x8c \ x8d \ x8e \ x8f \ x90 \ x91 \ x92 \ x93 \ x94 \ x95 \ x96 \ x97 \ x98 \ x99 \ x9a \ x9b '

…​

These backslashes are escape sequences, and `+\xhh+` https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals[represents] the character with hex value `+hh+`. Some of the elements of `+bites+` are displayed literally (printable characters such as letters, numbers, and punctuation). Most are expressed with escapes. `+\x08+` represents a keyboard’s backspace, while `+\x13+` is a https://en.wikipedia.org/wiki/Carriage_return[carriage return] (part of a new line, on Windows systems).

If you need a refresher on hexadecimal, Charles Petzold’s https://realpython.com/asins/0735611319/[_Code: The Hidden Language_] is a great place for that. Hex is a base-16 numbering system that, instead of using 0 through 9, uses 0 through 9 and _a_ through _f_ as its basic digits.

Finally, let’s get back to where you started, with the sequence of random bytes `+x+`. Hopefully this makes a little more sense now. Calling `+.hex()+` on a `+bytes+` object gives a `+str+` of hexadecimal numbers, with each corresponding to a decimal number from 0 through 255:

[.repl-toggle]#>>>#

[source,python,repl]

>>> x b '\ xce \ x11 \ xe7 "! \ x84'

>>> Liste (x)

>>> x.hex () 'ce11e7222184'

>>> len (x.hex ()) 12

One last question: how is `+b.hex()+` 12 characters long above, even though `+x+` is only 6 bytes? This is because two hexadecimal digits correspond precisely to a single byte. The `+str+` version of `+bytes+` will always be twice as long as far as our eyes are concerned.

Even if the byte (such as `+\x01+`) does not need a full 8 bits to be represented, `+b.hex()+` will always use two hex digits per byte, so the number 1 will be represented as `+01+` rather than just `+1+`. Mathematically, though, both of these are the same size.

*Technical Detail*: What you’ve mainly dissected here is how a `+bytes+` object becomes a Python `+str+`. One other technicality is how `+bytes+` produced by `+os.urandom()+` get converted to a `+float+` in the interval [0.0, 1.0), as in the https://github.com/python/cpython/blob/c6040638aa1537709add895d24cdbbb9ee310fde/Lib/random.py#L676[cryptographically secure version] of `+random.random()+`. If you’re interested in exploring this further, https://github.com/realpython/materials/blob/master/random-data/bytes_to_int.py[this code snippet] demonstrates how `+int.from_bytes()+` makes the initial conversion to an integer, using a base-256 numbering system.

With that under your belt, let’s touch on a recently introduced module, `+secrets+`, which makes generating secure tokens much more user-friendly.

==== Python’s Best Kept `+secrets+`

Introduced in Python 3.6 by https://www.python.org/dev/peps/pep-0506/[one of the more colorful PEPs] out there, the `+secrets+` module is intended to be the de facto Python module for generating cryptographically secure random bytes and strings.

You can check out the https://github.com/python/cpython/blob/3.6/Lib/secrets.py[source code] for the module, which is short and sweet at about 25 lines of code. `+secrets+` is basically a wrapper around `+os.urandom()+`. It exports just a handful of functions for generating random numbers, bytes, and strings. Most of these examples should be fairly self-explanatory:

[.repl-toggle]#>>>#

[source,python,repl]

>>> n = 16

>>> # Generiere sichere Token >>> Secrets.token_bytes (n) b’A \ x8cz \ xe1o \ xf9 !; \ X8b \ xf2 \ x80pJ \ x8b \ xd4 \ xd3 '>>> Secrets.token_hex (n)' 9cb190491e01230ec4239cae643f286f '>>> Secrets.token_urlsafe (n)' MJoi7CknFu3YN41m88SEgQ '

>>> # Sichere Version von random.choice () >>> Secrets.choice ('rain') 'a'

Now, how about a concrete example? You’ve probably used URL shortener services like https://tinyurl.com[tinyurl.com] or https://bit.ly[bit.ly] that turn an unwieldy URL into something like https://bit.ly/2IcCp9u. Most shorteners don’t do any complicated hashing from input to output; they just generate a random string, make sure that string has not already been generated previously, and then tie that back to the input URL.

Let’s say that after taking a look at the https://www.iana.org/domains/root/db[Root Zone Database], you’ve registered the site *short.ly*. Here’s a function to get you started with your service:

[source,python]

kurz.py

aus Geheimnissen importiere token_urlsafe

DATABASE = {}

def shorten (url: str, nbytes: int = 5) → str: ext = token_urlsafe (nbytes = nbytes) wenn ext in DATABASE: return shorten (url, nbytes = nbytes) else: DATABASE.update ({ext: url} ) return f’short.ly/{ext}

Is this a full-fledged real illustration? No. I would wager that bit.ly does things in a slightly more advanced way than storing its gold mine in a global Python dictionary that is not persistent between sessions. However, it’s roughly accurate conceptually:

[.repl-toggle]#>>>#

[source,python,repl]

>>> urls = ( . 'https://realpython.com/', . 'https://docs.python.org/3/howto/regex.html' . )

>>> für dich in URLs: . drucken (kürzen (u)) short.ly/p_Z4fLI short.ly/fuxSyNY

>>> DATABASE {'p_Z4fLI': 'https://realpython.com/', 'fuxSyNY': 'https://docs.python.org/3/howto/regex.html'}

*Hold On: *One thing you may notice is that both of these results are of length 7 when you requested 5 bytes. _Wait, I thought that you said the result would be twice as long?_ Well, not exactly, in this case. There is one more thing going on here: `+token_urlsafe()+` uses base64 encoding, where each character is 6 bits of data. (It’s 0 through 63, and corresponding characters. The characters are A-Z, a-z, 0-9, and +/.)

If you originally specify a certain number of bytes `+nbytes+`, the resulting length from `+secrets.token_urlsafe(nbytes)+` will be `+math.ceil(nbytes* 8/6)+`, which you can https://github.com/realpython/materials/blob/master/random-data/urlsafe.py[prove] and investigate further if you’re curious.

The bottom line here is that, while `+secrets+` is really just a wrapper around existing Python functions, it can be your go-to when security is your foremost concern.

=== One Last Candidate: `+uuid+`

One last option for generating a random token is the `+uuid4()+` function from Python’s https://docs.python.org/library/uuid.html[`+uuid+`] module. A https://tools.ietf.org/html/rfc4122.html[UUID] is a Universally Unique IDentifier, a 128-bit sequence (`+str+` of length 32) designed to “guarantee uniqueness across space and time.” `+uuid4()+` is one of the module’s most useful functions, and this function https://github.com/python/cpython/blob/78392885c9b08021c89649728053d31503d8a509/Lib/uuid.py#L623[also uses `+os.urandom()+`]:

[.repl-toggle]#>>>#

[source,python,repl]

>>> UUID importieren

>>> uuid.uuid4 () UUID ('3e3ef28d-3ff0-4933-9bba-e5ee91ce0e7b') >>> uuid.uuid4 () UUID ('2e115fcb-5761-4fa1-8287-19f4ee2877ac')

The nice thing is that all of `+uuid+`’s functions produce an instance of the `+UUID+` class, which encapsulates the ID and has properties like `+.int+`, `+.bytes+`, and `+.hex+`:

[.repl-toggle]#>>>#

[source,python,repl]

>>> tok = uuid.uuid4 () >>> tok.bytes b '. \ xb7 \ x80 \ xfd \ xbfIG \ xb3 \ xae \ x1d \ xe3 \ x97 \ xee \ xc5 \ xd5 \ x81'

>>> len (tok.bytes) 16 >>> len (tok.bytes) * 8 # In Bits 128

>>> tok.hex '2eb780fdbf4947b3ae1de397eec5d581' >>> tok.int 62097294383572614195530565389543396737

You may also have seen some other variations: `+uuid1()+`, `+uuid3()+`, and `+uuid5()+`. The key difference between these and `+uuid4()+` is that those three functions all take some form of input and therefore don’t meet the definition of “random” to the extent that a Version 4 UUID does:

* `+uuid1()+` uses your machine’s host ID and current time by default. Because of the reliance on current time down to nanosecond resolution, this version is where UUID derives the claim “guaranteed uniqueness across time.”
 *`+uuid3()+` and `+uuid5()+` both take a namespace identifier and a name. The former uses an https://en.wikipedia.org/wiki/MD5[MD5] hash and the latter uses SHA-1.

`+uuid4()+`, conversely, is entirely pseudorandom (or random). It consists of getting 16 bytes via `+os.urandom()+`, converting this to a https://en.wikipedia.org/wiki/Endianness[big-endian] integer, and doing a number of bitwise operations to comply with the https://tools.ietf.org/html/rfc4122.html#section-4.1.1[formal specification].

Hopefully, by now you have a good idea of the distinction between different “types” of random data and how to create them. However, one other issue that might come to mind is that of collisions.

In this case, a collision would simply refer to generating two matching UUIDs. What is the chance of that? Well, it is technically not zero, but perhaps it is close enough: there are `+2* * 128+` or 340 https://en.wikipedia.org/wiki/Names_of_large_numbers[undecillion] possible `+uuid4+` values. So, I’ll leave it up to you to judge whether this is enough of a guarantee to sleep well.

One common use of `+uuid+` is in Django, which has a https://docs.djangoproject.com/en/2.0/ref/models/fields/#uuidfield[`+UUIDField+`] that is often used as a primary key in a model’s underlying relational database.

=== Why Not Just “Default to” `+SystemRandom+`?

In addition to the secure modules discussed here such as `+secrets+`, Python’s `+random+` module actually has a little-used class called https://github.com/python/cpython/blob/b225cb770fb17596298f5a05c41a7c90c470c4f8/Lib/random.py#L666[`+SystemRandom+`] that uses `+os.urandom()+`. (`+SystemRandom+`, in turn, is also used by `+secrets+`. It’s all a bit of a web that traces back to `+urandom()+`.)

At this point, you might be asking yourself why you wouldn’t just “default to” this version? Why not “always be safe” rather than defaulting to the deterministic `+random+` functions that aren’t cryptographically secure ?

I’ve already mentioned one reason: sometimes you want your data to be deterministic and reproducible for others to follow along with.

But the second reason is that CSPRNGs, at least in Python, tend to be meaningfully slower than PRNGs. Let’s test that with a script, https://github.com/realpython/materials/blob/master/random-data/timed.py[`+timed.py+`], that compares the PRNG and CSPRNG versions of `+randint()+` using Python’s `+timeit.repeat()+`:

[source,python]

timed.py

Import zufällige Importzeit

Der "Standard" -Zufall ist eigentlich eine Instanz von random.Random (). # Die CSPRNG-Version verwendet nacheinander "SystemRandom ()" und "os.urandom ()". _sysrand = random.SystemRandom ()

def prng () → Keine: random.randint (0, 95)

def csprng () → Keine: _sysrand.randint (0, 95)

setup = 'zufällig importieren; von main import prng, csprng '

if name == 'main': print ('Best of 3 Versuche mit 1.000.000 Schleifen pro Versuch:')

für f in ('prng ()', 'csprng ()'): best = min (timeit.repeat (f, setup = setup)) print ('\ t {: 8s} {: 0.2f} Sekunden Gesamtzeit. '.format (f, am besten))

Now to execute this from the shell:

[source,sh]

$ python3 ./timed.py Best of 3 Versuche mit 1.000.000 Schleifen pro Versuch: prng () 1,07 Sekunden Gesamtzeit. csprng () 6,20 Sekunden Gesamtzeit.

A 5x timing difference is certainly a valid consideration in addition to cryptographic security when choosing between the two.

=== Odds and Ends: Hashing

One concept that hasn’t received much attention in this tutorial is that of https://en.wikipedia.org/wiki/Cryptographic_hash_function[hashing], which can be done with Python’s https://docs.python.org/3/library/hashlib.html[`+hashlib+`] module.

A hash is designed to be a one-way mapping from an input value to a fixed-size string that is virtually impossible to reverse engineer. As such, while the result of a hash function may “look like” random data, it doesn’t really qualify under the definition here.

=== Recap

You’ve covered a lot of ground in this tutorial. To recap, here is a high-level comparison of the options available to you for engineering randomness in Python:

[cols=",,",options="header",]
|===
|Package/Module |Description |Cryptographically Secure
|https://docs.python.org/library/random.html[`+random+`] |Fasty & easy random data using Mersenne Twister |No
|https://docs.scipy.org/doc/numpy/reference/routines.random.html[`+numpy.random+`] |Like `+random+` but for (possibly multidimensional) arrays |No
|https://docs.python.org/library/os.html[`+os+`] |Contains `+urandom()+`, the base of other functions covered here |Yes
|https://docs.python.org/library/secrets.html[`+secrets+`] |Designed to be Python’s de facto module for generating secure random numbers, bytes, and strings |Yes
|https://docs.python.org/library/uuid.html[`+uuid+`] |Home to a handful of functions for building 128-bit identifiers |Yes, `+uuid4()+`
|===

Feel free to leave some totally random comments below, and thanks for reading.

=== Additional Links

* https://www.random.org/[Random.org] offers “true random numbers to anyone on the Internet” derived from atmospheric noise.
* The https://docs.python.org/3.6/library/random.html#examples-and-recipes[Recipes] section from the `+random+` module has some additional tricks.
* The seminal paper on the http://www.math.sci.hiroshima-u.ac.jp/%7Em-mat/MT/ARTICLES/mt.pdf[Mersienne Twister] appeared in 1997, if you’re into that kind of thing.
* The https://docs.python.org/3.6/library/itertools.html#itertools-recipes[Itertools Recipes] define functions for choosing randomly from a combinatoric set, such as from combinations or permutations.
* http://scikit-learn.org/stable/datasets/index.html#sample-generators[Scikit-Learn] includes various random sample generators that can be used to build artificial datasets of controlled size and complexity.
* Eli Bendersky digs into `+random.randint()+` in his article https://eli.thegreenplace.net/2018/slow-and-fast-methods-for-generating-random-integers-in-python/#id2[Slow and Fast Methods for Generating Random Integers in Python].
* Peter Norvig’s a http://nbviewer.jupyter.org/url/norvig.com/ipython/Probability.ipynb[Concrete Introduction to Probability using Python] is a comprehensive resource as well.
* The Pandas library includes a https://github.com/pandas-dev/pandas/blob/f9cc39fb1391cb05f55232367f6547ff9ea615b8/pandas/util/testing.py#L2513[context manager] that can be used to set a temporary random state.
* From Stack Overflow:
** https://stackoverflow.com/q/50559078/7954504[Generating Random Dates In a Given Range]
** https://stackoverflow.com/q/48421142/7954504[Fastest Way to Generate a Random-like Unique String with Random Length]
** https://stackoverflow.com/q/21187131/7954504[How to Use `+random.shuffle()+` on a Generator]
** https://stackoverflow.com/q/31389481/7954504[Replace Random Elements in a NumPy Array]
** https://stackoverflow.com/q/14720799/7954504[Getting Numbers from/dev/random in Python]