Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
95702725
Commit
95702725
authored
Apr 15, 2016
by
Steven D'Aprano
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add secrets module and tests.
parent
15d2d49c
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
269 additions
and
0 deletions
+269
-0
Lib/secrets.py
Lib/secrets.py
+149
-0
Lib/test/test_secrets.py
Lib/test/test_secrets.py
+120
-0
No files found.
Lib/secrets.py
0 → 100644
View file @
95702725
"""Generate cryptographically strong pseudo-random numbers suitable for
managing secrets such as account authentication, tokens, and similar.
See PEP 506 for more information.
https://www.python.org/dev/peps/pep-0506/
Random numbers
==============
The ``secrets`` module provides the following pseudo-random functions, based
on SystemRandom, which in turn uses the most secure source of randomness your
operating system provides.
choice(sequence)
Choose a random element from a non-empty sequence.
randbelow(n)
Return a random int in the range [0, n).
randbits(k)
Generates an int with k random bits.
SystemRandom
Class for generating random numbers using sources provided by
the operating system. See the ``random`` module for documentation.
Token functions
===============
The ``secrets`` module provides a number of functions for generating secure
tokens, suitable for applications such as password resets, hard-to-guess
URLs, and similar. All the ``token_*`` functions take an optional single
argument specifying the number of bytes of randomness to use. If that is
not given, or is ``None``, a reasonable default is used. That default is
subject to change at any time, including during maintenance releases.
token_bytes(nbytes=None)
Return a random byte-string containing ``nbytes`` number of bytes.
>>> secrets.token_bytes(16) #doctest:+SKIP
b'
\
\
xebr
\
\
x17D*t
\
\
xae
\
\
xd4
\
\
xe3S
\
\
xb6
\
\
xe2
\
\
xebP1
\
\
x8b'
token_hex(nbytes=None)
Return a random text-string, in hexadecimal. The string has ``nbytes``
random bytes, each byte converted to two hex digits.
>>> secrets.token_hex(16) #doctest:+SKIP
'f9bf78b9a18ce6d46a0cd2b0b86df9da'
token_urlsafe(nbytes=None)
Return a random URL-safe text-string, containing ``nbytes`` random
bytes. On average, each byte results in approximately 1.3 characters
in the final result.
>>> secrets.token_urlsafe(16) #doctest:+SKIP
'Drmhze6EPcv0fN_81Bj-nA'
(The examples above assume Python 3. In Python 2, byte-strings will display
using regular quotes ``''`` with no prefix, and text-strings will have a
``u`` prefix.)
Other functions
===============
compare_digest(a, b)
Return True if strings a and b are equal, otherwise False.
Performs the equality comparison in such a way as to reduce the
risk of timing attacks.
See http://codahale.com/a-lesson-in-timing-attacks/ for a
discussion on how timing attacks against ``==`` can reveal
secrets from your application.
"""
__all__
=
[
'choice'
,
'randbelow'
,
'randbits'
,
'SystemRandom'
,
'token_bytes'
,
'token_hex'
,
'token_urlsafe'
,
'compare_digest'
,
]
import
base64
import
binascii
import
os
try
:
from
hmac
import
compare_digest
except
ImportError
:
# Python version is too old. Fall back to a pure-Python version.
import
operator
from
functools
import
reduce
def
compare_digest
(
a
,
b
):
"""Return ``a == b`` using an approach resistant to timing analysis.
a and b must both be of the same type: either both text strings,
or both byte strings.
Note: If a and b are of different lengths, or if an error occurs,
a timing attack could theoretically reveal information about the
types and lengths of a and b, but not their values.
"""
# For a similar approach, see
# http://codahale.com/a-lesson-in-timing-attacks/
for
T
in
(
bytes
,
str
):
if
isinstance
(
a
,
T
)
and
isinstance
(
b
,
T
):
break
else
:
# for...else
raise
TypeError
(
"arguments must be both strings or both bytes"
)
if
len
(
a
)
!=
len
(
b
):
return
False
# Thanks to Raymond Hettinger for this one-liner.
return
reduce
(
operator
.
and_
,
map
(
operator
.
eq
,
a
,
b
),
True
)
from
random
import
SystemRandom
_sysrand
=
SystemRandom
()
randbits
=
_sysrand
.
getrandbits
choice
=
_sysrand
.
choice
def
randbelow
(
exclusive_upper_bound
):
return
_sysrand
.
_randbelow
(
exclusive_upper_bound
)
DEFAULT_ENTROPY
=
32
# number of bytes to return by default
def
token_bytes
(
nbytes
=
None
):
if
nbytes
is
None
:
nbytes
=
DEFAULT_ENTROPY
return
os
.
urandom
(
nbytes
)
def
token_hex
(
nbytes
=
None
):
return
binascii
.
hexlify
(
token_bytes
(
nbytes
)).
decode
(
'ascii'
)
def
token_urlsafe
(
nbytes
=
None
):
tok
=
token_bytes
(
nbytes
)
return
base64
.
urlsafe_b64encode
(
tok
).
rstrip
(
b'='
).
decode
(
'ascii'
)
Lib/test/test_secrets.py
0 → 100644
View file @
95702725
"""Test the secrets module.
As most of the functions in secrets are thin wrappers around functions
defined elsewhere, we don't need to test them exhaustively.
"""
import
secrets
import
unittest
import
string
# === Unit tests ===
class
Compare_Digest_Tests
(
unittest
.
TestCase
):
"""Test secrets.compare_digest function."""
def
test_equal
(
self
):
# Test compare_digest functionality with equal (byte/text) strings.
for
s
in
(
"a"
,
"bcd"
,
"xyz123"
):
a
=
s
*
100
b
=
s
*
100
self
.
assertTrue
(
secrets
.
compare_digest
(
a
,
b
))
self
.
assertTrue
(
secrets
.
compare_digest
(
a
.
encode
(
'utf-8'
),
b
.
encode
(
'utf-8'
)))
def
test_unequal
(
self
):
# Test compare_digest functionality with unequal (byte/text) strings.
self
.
assertFalse
(
secrets
.
compare_digest
(
"abc"
,
"abcd"
))
self
.
assertFalse
(
secrets
.
compare_digest
(
b"abc"
,
b"abcd"
))
for
s
in
(
"x"
,
"mn"
,
"a1b2c3"
):
a
=
s
*
100
+
"q"
b
=
s
*
100
+
"k"
self
.
assertFalse
(
secrets
.
compare_digest
(
a
,
b
))
self
.
assertFalse
(
secrets
.
compare_digest
(
a
.
encode
(
'utf-8'
),
b
.
encode
(
'utf-8'
)))
def
test_bad_types
(
self
):
# Test that compare_digest raises with mixed types.
a
=
'abcde'
b
=
a
.
encode
(
'utf-8'
)
assert
isinstance
(
a
,
str
)
assert
isinstance
(
b
,
bytes
)
self
.
assertRaises
(
TypeError
,
secrets
.
compare_digest
,
a
,
b
)
self
.
assertRaises
(
TypeError
,
secrets
.
compare_digest
,
b
,
a
)
def
test_bool
(
self
):
# Test that compare_digest returns a bool.
self
.
assertTrue
(
isinstance
(
secrets
.
compare_digest
(
"abc"
,
"abc"
),
bool
))
self
.
assertTrue
(
isinstance
(
secrets
.
compare_digest
(
"abc"
,
"xyz"
),
bool
))
class
Random_Tests
(
unittest
.
TestCase
):
"""Test wrappers around SystemRandom methods."""
def
test_randbits
(
self
):
# Test randbits.
errmsg
=
"randbits(%d) returned %d"
for
numbits
in
(
3
,
12
,
30
):
for
i
in
range
(
6
):
n
=
secrets
.
randbits
(
numbits
)
self
.
assertTrue
(
0
<=
n
<
2
**
numbits
,
errmsg
%
(
numbits
,
n
))
def
test_choice
(
self
):
# Test choice.
items
=
[
1
,
2
,
4
,
8
,
16
,
32
,
64
]
for
i
in
range
(
10
):
self
.
assertTrue
(
secrets
.
choice
(
items
)
in
items
)
def
test_randbelow
(
self
):
# Test randbelow.
errmsg
=
"randbelow(%d) returned %d"
for
i
in
range
(
2
,
10
):
n
=
secrets
.
randbelow
(
i
)
self
.
assertTrue
(
n
in
range
(
i
),
errmsg
%
(
i
,
n
))
self
.
assertRaises
(
ValueError
,
secrets
.
randbelow
,
0
)
class
Token_Tests
(
unittest
.
TestCase
):
"""Test token functions."""
def
test_token_defaults
(
self
):
# Test that token_* functions handle default size correctly.
for
func
in
(
secrets
.
token_bytes
,
secrets
.
token_hex
,
secrets
.
token_urlsafe
):
name
=
func
.
__name__
try
:
func
()
except
TypeError
:
self
.
fail
(
"%s cannot be called with no argument"
%
name
)
try
:
func
(
None
)
except
TypeError
:
self
.
fail
(
"%s cannot be called with None"
%
name
)
size
=
secrets
.
DEFAULT_ENTROPY
self
.
assertEqual
(
len
(
secrets
.
token_bytes
(
None
)),
size
)
self
.
assertEqual
(
len
(
secrets
.
token_hex
(
None
)),
2
*
size
)
def
test_token_bytes
(
self
):
# Test token_bytes.
self
.
assertTrue
(
isinstance
(
secrets
.
token_bytes
(
11
),
bytes
))
for
n
in
(
1
,
8
,
17
,
100
):
self
.
assertEqual
(
len
(
secrets
.
token_bytes
(
n
)),
n
)
def
test_token_hex
(
self
):
# Test token_hex.
self
.
assertTrue
(
isinstance
(
secrets
.
token_hex
(
7
),
str
))
for
n
in
(
1
,
12
,
25
,
90
):
s
=
secrets
.
token_hex
(
n
)
self
.
assertEqual
(
len
(
s
),
2
*
n
)
self
.
assertTrue
(
all
(
c
in
string
.
hexdigits
for
c
in
s
))
def
test_token_urlsafe
(
self
):
# Test token_urlsafe.
self
.
assertTrue
(
isinstance
(
secrets
.
token_urlsafe
(
9
),
str
))
legal
=
string
.
ascii_letters
+
string
.
digits
+
'-_'
for
n
in
(
1
,
11
,
28
,
76
):
self
.
assertTrue
(
all
(
c
in
legal
for
c
in
secrets
.
token_urlsafe
(
n
)))
if
__name__
==
'__main__'
:
unittest
.
main
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment