Danger
Nothing here should be used for any security purposes.
Utility functions#
This module is imported with:
import toy_crypto.utils
- toy_crypto.utils.digit_count(n: int, base: int = 10) int [source]#
returns the number of digits (base b) of integer n.
- Raises:
ValueError – if base < 2
TypeError – if n is not an integer
- Parameters:
n (
int
)base (
int
, default:10
)
- Return type:
int
Coding this is a math problem, not a string representation problem. Ideally the solution would be to use
but that leads to erroneous results due to the precision limitations
of math.log()
.
So a different approach is taken which correctly handles cases that
would otherwise fail.
>>> from toy_crypto.utils import digit_count
>>> digit_count(999)
3
>>> digit_count(1000)
4
>>> digit_count(1001)
4
>>> digit_count(9999999999999998779999999999999999999999999999999999999999999)
61
>>> digit_count(9999999999999999999999999999999999999999999999999999999999999)
61
>>> digit_count(10000000000000000000000000000000000000000000000000000000000000)
62
>>> digit_count(0)
1
>>> digit_count(-10_000)
5
- toy_crypto.utils.next_power2(n: int) int [source]#
Returns smallest p such that \(2^p \geq n\).
- Raises:
ValueError – if n is less than 1.
- Parameters:
n (
int
)- Return type:
int
This is yet another function where talking a logarithm (base 2 this time) would be the mathematically nice way to do things,
but because we may want to use this with large numbers, we have to worry floating point precision.
Because we are dealing with base 2, we can do all of our multiplications and and divisions by powers of 2 using bit shifts. I am not sure how Pythonic that leaves things.
- toy_crypto.utils.nearest_multiple(n: int, factor: int, direction: str = 'round') int [source]#
Returns multiple of factor that is near
n
.Given an input number, n and a factor f returns m such that
\(f|m\) (f divides n);
- \(\left|n - m\right| < f\)
(There is no multiples of f between n and m);
As a consequence this always returns n if n is a multiple of factor.
When n is not a multiple of factor, which of the two possible solutions to those conditions is returned depends on the value of of the
direction
parameter.- Parameters:
n (
int
) – The integer get a nearby multiple of factor offactor (
int
) – The number that the returned values must be a multiple of.direction (
str
, default:'round'
) –Direction in which to round when n is not a multiple of factor
- ”next”
returns nearest multiple further from 0;
- ”previous”
returns nearest multiple toward 0;
- ”round”
returns nearest multiple and behaves like “previous” is if nearest multiples are equidistant from n
- Raises:
ValueError – if direction is not one of ‘next’, ‘previous’, ‘round’.
- Return type:
int
xor#
The utils.xor()
and the class utils.Xor
provide utilities for xoring strings of bytes together. There is some asymmetry between the two arguments. The message
can be an collections.abc.Iterator
as well as bytes
. The pad
arguement on the other hand, is expected to be bytes
only (in this version.) The pad
argument is will be repeated if it is shorter than the message.
Warning
The Byte
type is just a type alias for int
. There is no run time nor type checking mechanism that prevents you from passing an Iterator[Byte]
message that contains integers outside of the range that would be expected for a byte.
If you do so, bad things will happen. If you are lucky some exception from the bowels of Python will be raised in a way that will help you identify the error. If you are unlucky, you will silently get garbage results.
- class toy_crypto.utils.Xor(message: Iterator[int] | bytes, pad: bytes) None [source]#
Iterator that spits out xor of message with (repeated) pad.
The iterator will run through successful bytes of message xor-ing those with successive bytes of pad, repeating pad if pad is shorter than message.
Each iteration returns a non-negative int less than 256.
- Parameters:
message (
Iterator
[int
] |bytes
)pad (
bytes
)
- toy_crypto.utils.xor(message: bytes | Iterator[int], pad: bytes) bytes [source]#
Returns the xor of message with a (repeated) pad.
The pad is repeated if it is shorter than m. This can be thought of as bytewise Vigenère.
- Parameters:
message (
bytes
|Iterator
[int
])pad (
bytes
)
- Return type:
bytes
>>> from toy_crypto.utils import xor
>>> message = b"Attack at dawn!"
>>> pad = bytes(10) + bytes.fromhex("00 14 04 05 00")
>>> modified_message = xor(message, pad)
>>> modified_message
b'Attack at dusk!'
Encodings for the RSA 129 challenge#
Martin Gardner first reported the Rivest, Shamir, and Adleman (RSA) in 1977. The examples and challenge described in it used an encoding scheme between text and (large) integers. This class provides an encoder and decoder for that scheme.
We will take the magic words, decrypted in 1995 by Atkins, Graff, Lenstra, and Leyland with the help of a large number of volunteers, from that challenge for our example:
>>> from toy_crypto.utils import Rsa129
>>> decrypted = 200805001301070903002315180419000118050019172105011309190800151919090618010705
>>> Rsa129.decode(decrypted)
'THE MAGIC WORDS ARE SQUEAMISH OSSIFRAGE'
And we will use an example from [Gardner, 1977].
>>> latin_text = "ITS ALL GREEK TO ME"
>>> encoded = Rsa129.encode(latin_text)
>>> encoded
9201900011212000718050511002015001305
>>> assert Rsa129.decode(encoded) == latin_text