Skip to content

Commit be4fac0

Browse files
authored
Merge pull request #257 from tlsfuzzer/shake-256
add SHAKE-256 implementation
2 parents 34e9cec + b52ef33 commit be4fac0

File tree

4 files changed

+370
-8
lines changed

4 files changed

+370
-8
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,17 +259,17 @@ jobs:
259259
MERGE_BASE=$(git merge-base origin/$BASE_REF HEAD)
260260
echo "MERGE_BASE:" $MERGE_BASE
261261
git checkout $MERGE_BASE
262-
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa/test*.py
262+
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
263263
instrumental -f .instrumental.cov -s
264264
instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental
265265
git checkout $GITHUB_SHA
266-
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa/test*.py
266+
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
267267
instrumental -f .instrumental.cov -sr
268268
instrumental -f .instrumental.cov -s | python diff-instrumental.py --read .diff-instrumental --fail-under 70 --max-difference -0.1
269269
- name: instrumental test coverage on push
270270
if: ${{ contains(matrix.opt-deps, 'instrumental') && !github.event.pull_request }}
271271
run: |
272-
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat' `which pytest` src/ecdsa
272+
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa
273273
instrumental -f .instrumental.cov -s
274274
# just log the values when merging
275275
instrumental -f .instrumental.cov -s | python diff-instrumental.py

src/ecdsa/_compat.py

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ def str_idx_as_int(string, index):
1515

1616

1717
if sys.version_info < (3, 0): # pragma: no branch
18+
import binascii
19+
import platform
1820

1921
def normalise_bytes(buffer_object):
2022
"""Cast the input into array of bytes."""
@@ -24,22 +26,64 @@ def normalise_bytes(buffer_object):
2426
def hmac_compat(ret):
2527
return ret
2628

27-
if sys.version_info < (2, 7) or sys.version_info < ( # pragma: no branch
28-
2,
29-
7,
30-
4,
31-
):
29+
if (
30+
sys.version_info < (2, 7)
31+
or sys.version_info < (2, 7, 4)
32+
or platform.system() == "Java"
33+
): # pragma: no branch
3234

3335
def remove_whitespace(text):
3436
"""Removes all whitespace from passed in string"""
3537
return re.sub(r"\s+", "", text)
3638

39+
def compat26_str(val):
40+
return str(val)
41+
42+
def bit_length(val):
43+
if val == 0:
44+
return 0
45+
return len(bin(val)) - 2
46+
3747
else:
3848

3949
def remove_whitespace(text):
4050
"""Removes all whitespace from passed in string"""
4151
return re.sub(r"\s+", "", text, flags=re.UNICODE)
4252

53+
def compat26_str(val):
54+
return val
55+
56+
def bit_length(val):
57+
"""Return number of bits necessary to represent an integer."""
58+
return val.bit_length()
59+
60+
def b2a_hex(val):
61+
return binascii.b2a_hex(compat26_str(val))
62+
63+
def bytes_to_int(val, byteorder):
64+
"""Convert bytes to an int."""
65+
if not val:
66+
return 0
67+
if byteorder == "big":
68+
return int(b2a_hex(val), 16)
69+
if byteorder == "little":
70+
return int(b2a_hex(val[::-1]), 16)
71+
raise ValueError("Only 'big' and 'little' endian supported")
72+
73+
def int_to_bytes(val, length=None, byteorder="big"):
74+
"""Return number converted to bytes"""
75+
if length is None:
76+
length = byte_length(val)
77+
if byteorder == "big":
78+
return bytearray(
79+
(val >> i) & 0xFF for i in reversed(range(0, length * 8, 8))
80+
)
81+
if byteorder == "little":
82+
return bytearray(
83+
(val >> i) & 0xFF for i in range(0, length * 8, 8)
84+
)
85+
raise ValueError("Only 'big' or 'little' endian supported")
86+
4387

4488
else:
4589
if sys.version_info < (3, 4): # pragma: no branch
@@ -62,3 +106,28 @@ def normalise_bytes(buffer_object):
62106
def remove_whitespace(text):
63107
"""Removes all whitespace from passed in string"""
64108
return re.sub(r"\s+", "", text, flags=re.UNICODE)
109+
110+
# pylint: disable=invalid-name
111+
# pylint is stupid here and deson't notice it's a function, not
112+
# constant
113+
bytes_to_int = int.from_bytes
114+
# pylint: enable=invalid-name
115+
116+
def bit_length(val):
117+
"""Return number of bits necessary to represent an integer."""
118+
return val.bit_length()
119+
120+
def int_to_bytes(val, length=None, byteorder="big"):
121+
"""Convert integer to bytes."""
122+
if length is None:
123+
length = byte_length(val)
124+
# for gmpy we need to convert back to native int
125+
if type(val) != int:
126+
val = int(val)
127+
return bytearray(val.to_bytes(length=length, byteorder=byteorder))
128+
129+
130+
def byte_length(val):
131+
"""Return number of bytes necessary to represent an integer."""
132+
length = bit_length(val)
133+
return (length + 7) // 8

src/ecdsa/_sha3.py

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
"""
2+
Implementation of the SHAKE-256 algorithm for Ed448
3+
"""
4+
5+
try:
6+
import hashlib
7+
8+
hashlib.new("shake256").digest(64)
9+
10+
def shake_256(msg, outlen):
11+
return hashlib.new("shake256", msg).digest(outlen)
12+
13+
14+
except (TypeError, ValueError):
15+
16+
from ._compat import bytes_to_int, int_to_bytes
17+
18+
# From little endian.
19+
def _from_le(s):
20+
return bytes_to_int(s, byteorder="little")
21+
22+
# Rotate a word x by b places to the left.
23+
def _rol(x, b):
24+
return ((x << b) | (x >> (64 - b))) & (2 ** 64 - 1)
25+
26+
# Do the SHA-3 state transform on state s.
27+
def _sha3_transform(s):
28+
ROTATIONS = [
29+
0,
30+
1,
31+
62,
32+
28,
33+
27,
34+
36,
35+
44,
36+
6,
37+
55,
38+
20,
39+
3,
40+
10,
41+
43,
42+
25,
43+
39,
44+
41,
45+
45,
46+
15,
47+
21,
48+
8,
49+
18,
50+
2,
51+
61,
52+
56,
53+
14,
54+
]
55+
PERMUTATION = [
56+
1,
57+
6,
58+
9,
59+
22,
60+
14,
61+
20,
62+
2,
63+
12,
64+
13,
65+
19,
66+
23,
67+
15,
68+
4,
69+
24,
70+
21,
71+
8,
72+
16,
73+
5,
74+
3,
75+
18,
76+
17,
77+
11,
78+
7,
79+
10,
80+
]
81+
RC = [
82+
0x0000000000000001,
83+
0x0000000000008082,
84+
0x800000000000808A,
85+
0x8000000080008000,
86+
0x000000000000808B,
87+
0x0000000080000001,
88+
0x8000000080008081,
89+
0x8000000000008009,
90+
0x000000000000008A,
91+
0x0000000000000088,
92+
0x0000000080008009,
93+
0x000000008000000A,
94+
0x000000008000808B,
95+
0x800000000000008B,
96+
0x8000000000008089,
97+
0x8000000000008003,
98+
0x8000000000008002,
99+
0x8000000000000080,
100+
0x000000000000800A,
101+
0x800000008000000A,
102+
0x8000000080008081,
103+
0x8000000000008080,
104+
0x0000000080000001,
105+
0x8000000080008008,
106+
]
107+
108+
for rnd in range(0, 24):
109+
# AddColumnParity (Theta)
110+
c = [0] * 5
111+
d = [0] * 5
112+
for i in range(0, 25):
113+
c[i % 5] ^= s[i]
114+
for i in range(0, 5):
115+
d[i] = c[(i + 4) % 5] ^ _rol(c[(i + 1) % 5], 1)
116+
for i in range(0, 25):
117+
s[i] ^= d[i % 5]
118+
# RotateWords (Rho)
119+
for i in range(0, 25):
120+
s[i] = _rol(s[i], ROTATIONS[i])
121+
# PermuteWords (Pi)
122+
t = s[PERMUTATION[0]]
123+
for i in range(0, len(PERMUTATION) - 1):
124+
s[PERMUTATION[i]] = s[PERMUTATION[i + 1]]
125+
s[PERMUTATION[-1]] = t
126+
# NonlinearMixRows (Chi)
127+
for i in range(0, 25, 5):
128+
t = [
129+
s[i],
130+
s[i + 1],
131+
s[i + 2],
132+
s[i + 3],
133+
s[i + 4],
134+
s[i],
135+
s[i + 1],
136+
]
137+
for j in range(0, 5):
138+
s[i + j] = t[j] ^ ((~t[j + 1]) & (t[j + 2]))
139+
# AddRoundConstant (Iota)
140+
s[0] ^= RC[rnd]
141+
142+
# Reinterpret octet array b to word array and XOR it to state s.
143+
def _reinterpret_to_words_and_xor(s, b):
144+
for j in range(0, len(b) // 8):
145+
s[j] ^= _from_le(b[8 * j : 8 * j + 8])
146+
147+
# Reinterpret word array w to octet array and return it.
148+
def _reinterpret_to_octets(w):
149+
mp = bytearray()
150+
for j in range(0, len(w)):
151+
mp += int_to_bytes(w[j], 8, byteorder="little")
152+
return mp
153+
154+
def _sha3_raw(msg, r_w, o_p, e_b):
155+
"""Semi-generic SHA-3 implementation"""
156+
r_b = 8 * r_w
157+
s = [0] * 25
158+
# Handle whole blocks.
159+
idx = 0
160+
blocks = len(msg) // r_b
161+
for i in range(0, blocks):
162+
_reinterpret_to_words_and_xor(s, msg[idx : idx + r_b])
163+
idx += r_b
164+
_sha3_transform(s)
165+
# Handle last block padding.
166+
m = bytearray(msg[idx:])
167+
m.append(o_p)
168+
while len(m) < r_b:
169+
m.append(0)
170+
m[len(m) - 1] |= 128
171+
# Handle padded last block.
172+
_reinterpret_to_words_and_xor(s, m)
173+
_sha3_transform(s)
174+
# Output.
175+
out = bytearray()
176+
while len(out) < e_b:
177+
out += _reinterpret_to_octets(s[:r_w])
178+
_sha3_transform(s)
179+
return out[:e_b]
180+
181+
def shake_256(msg, outlen):
182+
return _sha3_raw(msg, 17, 31, outlen)

0 commit comments

Comments
 (0)