Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/crypto/stream.py
4045 views
1
"""
2
Stream Cryptosystems
3
"""
4
5
#*****************************************************************************
6
# Copyright (C) 2007 David Kohel <[email protected]>
7
#
8
# Distributed under the terms of the GNU General Public License (GPL)
9
#
10
# http://www.gnu.org/licenses/
11
#*****************************************************************************
12
13
from cryptosystem import SymmetricKeyCryptosystem
14
from stream_cipher import LFSRCipher, ShrinkingGeneratorCipher
15
16
from sage.crypto.util import random_blum_prime
17
from sage.monoids.string_monoid import BinaryStrings
18
from sage.rings.arith import gcd
19
from sage.rings.arith import power_mod
20
from sage.rings.finite_rings.constructor import FiniteField
21
from sage.rings.finite_rings.integer_mod_ring import IntegerModFactory
22
from sage.rings.polynomial.polynomial_element import is_Polynomial
23
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
24
25
IntegerModRing = IntegerModFactory("IntegerModRing")
26
27
class LFSRCryptosystem(SymmetricKeyCryptosystem):
28
"""
29
Linear feedback shift register cryptosystem class
30
"""
31
def __init__(self, field = None):
32
"""
33
Create a linear feedback shift cryptosystem.
34
35
INPUT: A string monoid over a binary alphabet.
36
37
OUTPUT:
38
39
EXAMPLES::
40
41
sage: E = LFSRCryptosystem(FiniteField(2))
42
sage: E
43
LFSR cryptosystem over Finite Field of size 2
44
45
TESTS::
46
47
sage: E = LFSRCryptosystem(FiniteField(2))
48
sage: E == loads(dumps(E))
49
True
50
51
TODO: Implement LFSR cryptosystem for arbitrary rings. The current
52
implementation is limited to the finite field of 2 elements only
53
because of the dependence on binary strings.
54
"""
55
if field is None:
56
field = FiniteField(2)
57
if field.cardinality() != 2:
58
raise NotImplementedError, "Not yet implemented."
59
S = BinaryStrings()
60
P = PolynomialRing(FiniteField(2),'x')
61
SymmetricKeyCryptosystem.__init__(self, S, S, None)
62
self._field = field
63
64
def __eq__(self,right):
65
return type(self) == type(right) and self._field == right._field
66
67
def __call__(self, key):
68
"""
69
Create a LFSR cipher.
70
71
INPUT: A polynomial and initial state of the LFSR.
72
"""
73
if not isinstance(key, (list,tuple)) and len(key) == 2:
74
raise TypeError, "Argument key (= %s) must be a list of tuple of length 2" % key
75
poly = key[0]; IS = key[1]
76
if not is_Polynomial(poly):
77
raise TypeError, "poly (= %s) must be a polynomial." % poly
78
if not isinstance(IS, (list,tuple)):
79
raise TypeError, "IS (= %s) must be an initial in the key space."%K
80
if len(IS) != poly.degree():
81
raise TypeError, \
82
"The length of IS (= %s) must equal the degree of poly (= %s)" % (IS, poly)
83
return LFSRCipher(self, poly, IS)
84
85
def _repr_(self):
86
r"""
87
Return the string representation of this LFSR cryptosystem.
88
89
EXAMPLES::
90
91
sage: LFSRCryptosystem(FiniteField(2))
92
LFSR cryptosystem over Finite Field of size 2
93
"""
94
return "LFSR cryptosystem over %s" % self._field
95
96
def encoding(self,M):
97
S = self.cipher_domain()
98
try:
99
return S.encoding(M)
100
except:
101
raise TypeError, "Argument M = %s does not encode in the cipher domain" % M
102
103
class ShrinkingGeneratorCryptosystem(SymmetricKeyCryptosystem):
104
"""
105
Shrinking generator cryptosystem class
106
"""
107
def __init__(self, field = None):
108
"""
109
Create a shrinking generator cryptosystem.
110
111
INPUT: A string monoid over a binary alphabet.
112
113
OUTPUT:
114
115
EXAMPLES::
116
117
sage: E = ShrinkingGeneratorCryptosystem()
118
sage: E
119
Shrinking generator cryptosystem over Finite Field of size 2
120
"""
121
if field is None:
122
field = FiniteField(2)
123
if field.cardinality() != 2:
124
raise NotImplementedError, "Not yet implemented."
125
S = BinaryStrings()
126
P = PolynomialRing(field, 'x')
127
SymmetricKeyCryptosystem.__init__(self, S, S, None)
128
self._field = field
129
130
def __call__(self, key):
131
"""
132
Create a Shrinking generator cipher.
133
134
INPUT: A list or tuple consisting of two LFSR ciphers (e1,e2).
135
136
OUTPUT: The shrinking generator cipher with key stream generator e1
137
and decimating cipher e2.
138
"""
139
if not isinstance(key, (list,tuple)) and len(key) == 2:
140
raise TypeError, "Argument key (= %s) must be a list of tuple of length 2" % key
141
e1 = key[0]; e2 = key[1]
142
if not isinstance(e1, LFSRCipher) or not isinstance(e2, LFSRCipher):
143
raise TypeError, "The key (= (%s,%s)) must be a tuple of two LFSR ciphers." % key
144
return ShrinkingGeneratorCipher(self, e1, e2)
145
146
def _repr_(self):
147
r"""
148
Return the string representation of this shrinking generator
149
cryptosystem.
150
151
EXAMPLES::
152
153
sage: ShrinkingGeneratorCryptosystem()
154
Shrinking generator cryptosystem over Finite Field of size 2
155
"""
156
return "Shrinking generator cryptosystem over %s" % self._field
157
158
def encoding(self,M):
159
S = self.cipher_domain()
160
try:
161
return S.encoding(M)
162
except:
163
raise TypeError, "Argument M = %s does not encode in the cipher domain" % M
164
165
def blum_blum_shub(length, seed=None, p=None, q=None,
166
lbound=None, ubound=None, ntries=100):
167
r"""
168
The Blum-Blum-Shub (BBS) pseudorandom bit generator.
169
170
See the original paper by Blum, Blum and Shub [BlumBlumShub1986]_. The
171
BBS algorithm is also discussed in section 5.5.2 of [MenezesEtAl1996]_.
172
173
INPUT:
174
175
- ``length`` -- positive integer; the number of bits in the output
176
pseudorandom bit sequence.
177
178
- ``seed`` -- (default: ``None``) if `p` and `q` are Blum primes, then
179
``seed`` is a quadratic residue in the multiplicative group
180
`(\ZZ/n\ZZ)^{\ast}` where `n = pq`. If ``seed=None``, then the function
181
would generate its own random quadratic residue in `(\ZZ/n\ZZ)^{\ast}`.
182
If you provide a value for ``seed``, then it is your responsibility to
183
ensure that the seed is a quadratic residue in the multiplicative group
184
`(\ZZ/n\ZZ)^{\ast}`.
185
186
- ``p`` -- (default: ``None``) a large positive prime congruent to 3
187
modulo 4. Both ``p`` and ``q`` must be distinct. If ``p=None``, then
188
a value for ``p`` will be generated, where
189
``0 < lower_bound <= p <= upper_bound``.
190
191
- ``q`` -- (default: ``None``) a large positive prime congruence to 3
192
modulo 4. Both ``p`` and ``q`` must be distinct. If ``q=None``, then
193
a value for ``q`` will be generated, where
194
``0 < lower_bound <= q <= upper_bound``.
195
196
- ``lbound`` -- (positive integer, default: ``None``) the lower
197
bound on how small each random primes `p` and `q` can be. So we
198
have ``0 < lbound <= p, q <= ubound``. The lower bound must be
199
distinct from the upper bound.
200
201
- ``ubound`` -- (positive integer, default: ``None``) the upper
202
bound on how large each random primes `p` and `q` can be. So we have
203
``0 < lbound <= p, q <= ubound``. The lower bound must be distinct
204
from the upper bound.
205
206
- ``ntries`` -- (default: ``100``) the number of attempts to generate
207
a random Blum prime. If ``ntries`` is a positive integer, then
208
perform that many attempts at generating a random Blum prime. This
209
might or might not result in a Blum prime.
210
211
OUTPUT:
212
213
- A pseudorandom bit sequence whose length is specified by ``length``.
214
215
Here is a common use case for this function. If you want this
216
function to use pre-computed values for `p` and `q`, you should pass
217
those pre-computed values to this function. In that case, you only need
218
to specify values for ``length``, ``p`` and ``q``, and you do not need
219
to worry about doing anything with the parameters ``lbound`` and
220
``ubound``. The pre-computed values `p` and `q` must be Blum primes.
221
It is your responsibility to check that both `p` and `q` are Blum primes.
222
223
Here is another common use case. If you want the function to generate
224
its own values for `p` and `q`, you must specify the lower and upper
225
bounds within which these two primes must lie. In that case, you must
226
specify values for ``length``, ``lbound`` and ``ubound``, and you do
227
not need to worry about values for the parameters ``p`` and ``q``. The
228
parameter ``ntries`` is only relevant when you want this function to
229
generate ``p`` and ``q``.
230
231
.. NOTE::
232
233
Beware that there might not be any primes between the lower and
234
upper bounds. So make sure that these two bounds are
235
"sufficiently" far apart from each other for there to be primes
236
congruent to 3 modulo 4. In particular, there should be at least
237
two distinct primes within these bounds, each prime being congruent
238
to 3 modulo 4. This function uses the function
239
:func:`random_blum_prime() <sage.crypto.util.random_blum_prime>` to
240
generate random primes that are congruent to 3 modulo 4.
241
242
ALGORITHM:
243
244
The BBS algorithm as described below is adapted from the presentation
245
in Algorithm 5.40, page 186 of [MenezesEtAl1996]_.
246
247
#. Let `L` be the desired number of bits in the output bit sequence.
248
That is, `L` is the desired length of the bit string.
249
#. Let `p` and `q` be two large distinct primes, each congruent to 3
250
modulo 4.
251
#. Let `n = pq` be the product of `p` and `q`.
252
#. Select a random seed value `s \in (\ZZ/n\ZZ)^{\ast}`, where
253
`(\ZZ/n\ZZ)^{\ast}` is the multiplicative group of `\ZZ/n\ZZ`.
254
#. Let `x_0 = s^2 \bmod n`.
255
#. For `i` from 1 to `L`, do
256
257
#. Let `x_i = x_{i-1}^2 \bmod n`.
258
#. Let `z_i` be the least significant bit of `x_i`.
259
260
#. The output pseudorandom bit sequence is `z_1, z_2, \dots, z_L`.
261
262
EXAMPLES:
263
264
A BBS pseudorandom bit sequence with a specified seed::
265
266
sage: from sage.crypto.stream import blum_blum_shub
267
sage: blum_blum_shub(length=6, seed=3, p=11, q=19)
268
110000
269
270
You could specify the length of the bit string, with given values for
271
``p`` and ``q``::
272
273
sage: blum_blum_shub(length=6, p=11, q=19) # random
274
001011
275
276
Or you could specify the length of the bit string, with given values for
277
the lower and upper bounds::
278
279
sage: blum_blum_shub(length=6, lbound=10**4, ubound=10**5) # random
280
110111
281
282
Under some reasonable hypotheses, Blum-Blum-Shub [BlumBlumShub1982]_
283
sketch a proof that the period of the BBS stream cipher is equal to
284
`\lambda(\lambda(n))`, where `\lambda(n)` is the Carmichael function of
285
`n`. This is verified below in a few examples by using the function
286
:func:`lfsr_connection_polynomial() <sage.crypto.lfsr.lfsr_connection_polynomial>`
287
(written by Tim Brock) which computes the connection polynomial of a
288
linear feedback shift register sequence. The degree of that polynomial
289
is the period. ::
290
291
sage: from sage.crypto.stream import blum_blum_shub
292
sage: from sage.crypto.util import carmichael_lambda
293
sage: carmichael_lambda(carmichael_lambda(7*11))
294
4
295
sage: s = [GF(2)(int(str(x))) for x in blum_blum_shub(60, p=7, q=11, seed=13)]
296
sage: lfsr_connection_polynomial(s)
297
x^3 + x^2 + x + 1
298
sage: carmichael_lambda(carmichael_lambda(11*23))
299
20
300
sage: s = [GF(2)(int(str(x))) for x in blum_blum_shub(60, p=11, q=23, seed=13)]
301
sage: lfsr_connection_polynomial(s)
302
x^19 + x^18 + x^17 + x^16 + x^15 + x^14 + x^13 + x^12 + x^11 + x^10 + x^9 + x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + x^2 + x + 1
303
304
TESTS:
305
306
Make sure that there is at least one Blum prime between the lower and
307
upper bounds. In the following example, we have ``lbound=24`` and
308
``ubound=30`` with 29 being the only prime within those bounds. But 29
309
is not a Blum prime. ::
310
311
sage: from sage.crypto.stream import blum_blum_shub
312
sage: blum_blum_shub(6, lbound=24, ubound=30, ntries=10)
313
Traceback (most recent call last):
314
...
315
ValueError: No Blum primes within the specified closed interval.
316
317
Both the lower and upper bounds must be greater than 2::
318
319
sage: blum_blum_shub(6, lbound=2, ubound=3)
320
Traceback (most recent call last):
321
...
322
ValueError: Both the lower and upper bounds must be > 2.
323
sage: blum_blum_shub(6, lbound=3, ubound=2)
324
Traceback (most recent call last):
325
...
326
ValueError: Both the lower and upper bounds must be > 2.
327
sage: blum_blum_shub(6, lbound=2, ubound=2)
328
Traceback (most recent call last):
329
...
330
ValueError: Both the lower and upper bounds must be > 2.
331
332
The lower and upper bounds must be distinct from each other::
333
334
sage: blum_blum_shub(6, lbound=3, ubound=3)
335
Traceback (most recent call last):
336
...
337
ValueError: The lower and upper bounds must be distinct.
338
339
The lower bound must be less than the upper bound::
340
341
sage: blum_blum_shub(6, lbound=4, ubound=3)
342
Traceback (most recent call last):
343
...
344
ValueError: The lower bound must be less than the upper bound.
345
346
REFERENCES:
347
348
.. [BlumBlumShub1982] L. Blum, M. Blum, and M. Shub.
349
Comparison of Two Pseudo-Random Number Generators.
350
*Advances in Cryptology: Proceedings of Crypto '82*,
351
pp.61--78, 1982.
352
353
.. [BlumBlumShub1986] L. Blum, M. Blum, and M. Shub.
354
A Simple Unpredictable Pseudo-Random Number Generator.
355
*SIAM Journal on Computing*, 15(2):364--383, 1986.
356
"""
357
# sanity checks
358
if length < 0:
359
raise ValueError("The length of the bit string must be positive.")
360
if (p is None) and (p == q == lbound == ubound):
361
raise ValueError("Either specify values for p and q, or specify values for the lower and upper bounds.")
362
# Use pre-computed Blum primes. Both the parameters p and q are
363
# assumed to be Blum primes. No attempts are made to ensure that they
364
# are indeed Blum primes.
365
randp = 0
366
randq = 0
367
if (p is not None) and (q is not None):
368
randp = p
369
randq = q
370
# generate random Blum primes within specified bounds
371
elif (lbound is not None) and (ubound is not None):
372
randp = random_blum_prime(lbound, ubound, ntries=ntries)
373
randq = random_blum_prime(lbound, ubound, ntries=ntries)
374
while randp == randq:
375
randq = random_blum_prime(lbound, ubound, ntries=ntries)
376
# no pre-computed primes given, and no appropriate bounds given
377
else:
378
raise ValueError("Either specify values for p and q, or specify values for the lower and upper bounds.")
379
# By now, we should have two distinct Blum primes.
380
n = randp * randq
381
# If no seed is provided, select a random seed.
382
x0 = seed
383
if seed is None:
384
zmod = IntegerModRing(n)
385
s = zmod.random_element().lift()
386
while gcd(s, n) != 1:
387
s = zmod.random_element().lift()
388
x0 = power_mod(s, 2, n)
389
# start generating pseudorandom bits
390
z = []
391
for i in xrange(length):
392
x1 = power_mod(x0, 2, n)
393
z.append(x1 % 2)
394
x0 = x1
395
bin = BinaryStrings()
396
return bin(z)
397
398