class BlockCipherMode(EncryptionAlg): SYMMETRY_TYPE = SymmetryType.SYMMETRIC KEY_SIZE = SizeSpec(size_type=SizeType.DEPENDENT, selector=lambda mode: mode.cipher.KEY_SIZE) INPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY) OUTPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY) BLOCK_SIZE = SizeSpec(size_type=SizeType.DEPENDENT, selector=lambda mode: mode.cipher.BLOCK_SIZE) IO_RELATION_TYPE = IORelationType.EQUAL
class SignatureAlg(NumberTheoreticalAlg): PRIMITIVE_TYPE = PrimitiveType.SIGNING KEY_SIZE = SizeSpec(size_type=SizeType.ARBITRARY, typical=[160, 224, 256]) OUTPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY, typical=[320, 448, 512]) CIPHER_TYPE = CipherType.NONE EPHEMERAL = EphemeralSpec(ephemeral_type=EphemeralType.KEY, size=SizeSpec( size_type=SizeType.DEPENDENT, selector=lambda signer: signer.KEY_SIZE))
class StreamCipher(EncryptionAlg): SYMMETRY_TYPE = SymmetryType.SYMMETRIC CIPHER_TYPE = CipherType.STREAM_CIPHER KEY_SIZE = SizeSpec(size_type=SizeType.RANGE, sizes=[128, 256]) INPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY) OUTPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY) BLOCK_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=8) IO_RELATION_TYPE = IORelationType.EQUAL def encrypt(self, plaintext: bytes) -> Bytes: return self.generate(len(plaintext)) ^ plaintext def decrypt(self, ciphertext: bytes) -> Bytes: return self.encrypt(ciphertext)
class SHA3_224(SHA3): OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=224) def __init__(self): super().__init__(r=1152, c=448, bits=224, padding=0x06) Primitive.__init__(self)
class SHA1(MerkleDamgardConstruction): """ Cryptographic hash function considered to be broken but is still widely used. """ OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=160) USAGE_FREQUENCY = FrequencyType.PROLIFIC def __init__(self, initial_state: bytes = state_to_bytes([ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xC3D2E1F0 ])): """ Parameters: initial_state (bytes): (Optional) Initial internal state. """ if type(initial_state) is list: initial_state = state_to_bytes(initial_state) super().__init__( initial_state=initial_state, compression_func=compression_func, digest_size=20, ) Primitive.__init__(self) def __repr__(self): return f"<SHA1: initial_state={self.initial_state}, block_size={self.block_size}>" def __str__(self): return self.__repr__()
class MD5(MerkleDamgardConstruction): """ Popular but completely broken cryptographic hash function. """ OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=128) USAGE_FREQUENCY = FrequencyType.PROLIFIC def __init__(self, initial_state: bytes=state_to_bytes([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476])): """ Parameters: initial_state (bytes): (Optional) Initial internal state. """ super().__init__( initial_state=initial_state, compression_func=compression_func, digest_size=16, endianness='little' ) Primitive.__init__(self) def __repr__(self): return f"<MD5: initial_state={self.initial_state}, block_size={self.block_size}>" def __str__(self): return self.__repr__()
class DH25519(KeyExchangeAlg): """ Elliptical curve Diffie-Hellman using Montgomery curves. """ KEY_SIZE = SizeSpec(size_type=SizeType.ARBITRARY, typical=[255, 448]) USAGE_FREQUENCY = FrequencyType.OFTEN def __init__(self, d: int = None, pub: int = None, base: int = None, curve: MontgomeryCurve = Curve25519): """ Parameters: d (int): Secret key that will be clamped to the curve. base (int): Base multiplier used in generating the challenge. curve (MontgomeryCurve): The curve used. """ Primitive.__init__(self) self.d = Bytes.wrap(d or random_int_between(1, curve.ring.order)).int() self.curve = curve self.key = curve.clamp_to_curve(self.d) self.base = base or curve.U self.pub = pub if not pub: self.recompute_public() def __repr__(self): return f"<DH25519: d={self.d}, key={self.key}, pub={self.pub}, base={self.base}>" def __str__(self): return self.__repr__() def recompute_public(self) -> int: """ Gets the challenge. Returns: int: The integer challenge representing an `x` value on the curve. """ self.pub = self.key * self.base def get_pub_bytes(self) -> Bytes: return Bytes(self.pub, 'little') def derive_key(self, challenge: int) -> Bytes: """ Derives the shared key from the other instance's challenge. Parameters: challenge (int): The other instance's challenge. Returns: int: Shared key. """ return Bytes(self.key * challenge).zfill( (self.curve.p.bit_length() + 7) // 8)
class SHA3_512(SHA3): OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=512) def __init__(self): super().__init__(r=576, c=1024, bits=512, padding=0x06) Primitive.__init__(self)
class SHA3_256(SHA3): OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=256) def __init__(self): super().__init__(r=1088, c=512, bits=256, padding=0x06) Primitive.__init__(self)
class SHA3_384(SHA3): OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=384) def __init__(self): super().__init__(r=832, c=768, bits=384, padding=0x06) Primitive.__init__(self)
class RIPEMD160(MerkleDamgardConstruction): """ Stands for RACE Integrity Primitives Evaluation Message Digest (RIPEMD). While there exist other versions of RIPEMD (128, 256, and 320), 160 is the most popular. """ OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=160) def __init__(self, initial_state: bytes = INIT_STATE): """ Parameters: initial_state (bytes): (Optional) Initial internal state. """ super().__init__(initial_state=initial_state, compression_func=COMPRESS, digest_size=20, endianness='little') Primitive.__init__(self) def __repr__(self): return f"<RIPEMD160: initial_state={self.initial_state}, block_size={self.block_size}>" def __str__(self): return self.__repr__()
class MAC(Primitive): PRIMITIVE_TYPE = PrimitiveType.MAC KEY_SIZE = SizeSpec(size_type=SizeType.DEPENDENT, selector=lambda cipher: cipher.KEY_SIZE) SYMMETRY_TYPE = SymmetryType.SYMMETRIC INPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY) BLOCK_SIZE = SizeSpec(size_type=SizeType.DEPENDENT, selector=lambda cipher: cipher.BLOCK_SIZE) OUTPUT_SIZE = SizeSpec(size_type=SizeType.DEPENDENT, selector=lambda cipher: cipher.OUTPUT_SIZE) @abstractmethod def generate(self, *args, **kwargs): pass def verify(self, message: bytes, signature: bytes) -> bool: return self.generate(message) == signature
class BlockCipher(EncryptionAlg): SYMMETRY_TYPE = SymmetryType.SYMMETRIC CIPHER_TYPE = CipherType.BLOCK_CIPHER CONSTRUCTION_TYPES = [ConstructionType.FEISTEL_NETWORK] KEY_SIZE = SizeSpec(size_type=SizeType.RANGE, sizes=[128, 192, 256], typical=[128, 256]) BLOCK_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=128) IO_RELATION_TYPE = IORelationType.EQUAL @classproperty def INPUT_SIZE(cls): return cls.BLOCK_SIZE @classproperty def OUTPUT_SIZE(cls): return cls.BLOCK_SIZE
class OFB(StreamingBlockCipherMode): """Output feedback block cipher mode.""" EPHEMERAL = EphemeralSpec( ephemeral_type=EphemeralType.NONCE, size=SizeSpec( size_type=SizeType.DEPENDENT, selector=lambda block_mode: block_mode.cipher.BLOCK_SIZE)) def __init__(self, cipher: EncryptionAlg, iv: bytes): """ Parameters: cipher (EncryptionAlg): Instantiated encryption algorithm. iv (bytes): Bytes-like initialization vector. """ Primitive.__init__(self) self.cipher = cipher self.iv = iv self.cbc = CBC(cipher, iv) def __repr__(self): return f"<OFB: cipher={self.cipher}, iv={self.iv}>" def __str__(self): return self.__repr__() def encrypt(self, plaintext: bytes) -> Bytes: """ Encrypts `plaintext`. Parameters: plaintext (bytes): Bytes-like object to be encrypted. Returns: Bytes: Resulting ciphertext. """ plaintext = Bytes.wrap(plaintext) num_blocks = ceil(len(plaintext) / self.cipher.block_size) keystream = self.cbc.encrypt( b'\x00' * self.cipher.block_size * num_blocks, False) return keystream[:len(plaintext)] ^ plaintext def decrypt(self, ciphertext: bytes) -> Bytes: """ Decrypts `ciphertext`. Parameters: ciphertext (bytes): Bytes-like object to be decrypted. Returns: Bytes: Resulting plaintext. """ return self.encrypt(ciphertext)
class SHAKE256(SHA3): OUTPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY, typical=[256]) def __init__(self, digest_bit_length: int): """ Parameters: digest_bit_length (int): Desired digest length in bits. """ super().__init__(r=1088, c=512, bits=digest_bit_length, padding=0x1F) Primitive.__init__(self)
class Hash(Primitive): PRIMITIVE_TYPE = PrimitiveType.HASH INPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY) CONSTRUCTION_TYPES = [ConstructionType.MERKLE_DAMGARD] @classproperty def BLOCK_SIZE(cls): return cls.OUTPUT_SIZE @abstractmethod def hash(self, *args, **kwargs): pass
class BLAKE2s(BLAKE2): BLOCK_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=64) OUTPUT_SIZE = SizeSpec(size_type=SizeType.ARBITRARY, typical=[256]) WORD_SIZE = 32 MASKBITS = 0xFFFFFFFF ROUNDS = 10 IMPL_BLOCK_SIZE = 64 IV = H_256 ROTATIONS = [16, 12, 8, 7] def __init__(self, desired_hash_len=32, key=b''): """ Parameters: key (bytes): (Optional) Bytes-like object to key the hash. desired_hash_len (int): Desired output length. """ super().__init__(key, desired_hash_len) def __repr__(self): return f"<BLAKE2s: iv={self.IV}, digest_size={self.digest_size}, key={self.key}>" def __str__(self): return self.__repr__()
class SHA384(SHA2): OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=384) def __init__(self, h: list=None): """ Parameters: h (list): Initial state as list of integers. """ super().__init__( initial_state=h or H_384, digest_size=384 // 8, state_size=8, block_size=128, rounds=80, rot=ROT_512, k=K_512 )
class KeyExchangeAlg(Primitive): PRIMITIVE_TYPE = PrimitiveType.KEY_EXCHANGE SYMMETRY_TYPE = SymmetryType.ASYMMETRIC SECURITY_PROOF = SecurityProofType.DISCRETE_LOGARITHM KEY_SIZE = SizeSpec(size_type=SizeType.ARBITRARY, typical=[1024, 2048, 4096]) @classproperty def INPUT_SIZE(cls): return cls.KEY_SIZE @classproperty def OUTPUT_SIZE(cls): return cls.KEY_SIZE @classproperty def BLOCK_SIZE(cls): return cls.KEY_SIZE
class SHA256(SHA2): OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=256) def __init__(self, h: list=None): """ Parameters: h (list): Initial state as list of integers. """ super().__init__( initial_state=h or H_256, digest_size=256 // 8, state_size=4, block_size=64, rounds=64, rot=ROT_256, k=K_256 )
class NumberTheoreticalAlg(Primitive): SYMMETRY_TYPE = SymmetryType.ASYMMETRIC PRIMITIVE_TYPE = PrimitiveType.CIPHER CIPHER_TYPE = CipherType.NUMBER_THEORETICAL_CIPHER KEY_SIZE = SizeSpec(size_type=SizeType.ARBITRARY, typical=[1024, 2048, 4096]) SECURITY_PROOF = SecurityProofType.DISCRETE_LOGARITHM @classproperty def INPUT_SIZE(cls): return cls.KEY_SIZE @classproperty def OUTPUT_SIZE(cls): return cls.KEY_SIZE @classproperty def BLOCK_SIZE(cls): return cls.OUTPUT_SIZE
class SHA512(SHA2): OUTPUT_SIZE = SizeSpec(size_type=SizeType.RANGE, sizes=range(513), typical=[512]) def __init__(self, h: list=None, trunc: int=None): """ Parameters: h (list): Initial state as list of integers. trunc (int): Truncation length for SHA-512/t. """ # FIPS 180-4 if trunc: h = h or H_512 h_doubleprime = [h ^ 0xa5a5a5a5a5a5a5a5 for h in H_512] h = [chunk.int() for chunk in SHA512(h=h_doubleprime).hash(f'SHA-512/{trunc}'.encode('utf-8')).chunk(8)] super().__init__( initial_state=h or H_512, digest_size=512 // 8, state_size=8, block_size=128, rounds=80, rot=ROT_512, k=K_512 ) self.trunc = trunc or 0 def hash(self, message: bytes) -> Bytes: """ Yields the final, hashed state of the `message`. Parameters: message (bytes): Message to be hashed. Returns: Bytes: Fully-hashed state. """ final_state = super().hash(message) return final_state[:math.ceil((self.trunc or 512) / 8)]
class MD4(MerkleDamgardConstruction): """ Obsolete cryptographic hash function and predecessor to MD5. """ OUTPUT_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=128) def __init__(self, initial_state: bytes = state_to_bytes(iv)): """ Parameters: initial_state (bytes): (Optional) Initial internal state. """ super().__init__(initial_state=initial_state, compression_func=compression_func, digest_size=16, endianness='little') Primitive.__init__(self) def __repr__(self): return f"<MD4: initial_state={self.initial_state}, block_size={self.block_size}>" def __str__(self): return self.__repr__()
class DSA(EncodablePKI, SignatureAlg): """ Digital Signature Algorithm """ PRIV_ENCODINGS = { PKIEncoding.OpenSSH: OpenSSHDSAPrivateKey, PKIEncoding.PKCS1: PKCS1DSAPrivateKey, PKIEncoding.PKCS8: PKCS8DSAPrivateKey } PUB_ENCODINGS = { PKIEncoding.OpenSSH: OpenSSHDSAPublicKey, PKIEncoding.SSH2: SSH2DSAPublicKey, PKIEncoding.X509_CERT: X509DSACertificate, PKIEncoding.X509: X509DSAPublicKey } X509_SIGNING_ALGORITHMS = X509DSASigningAlgorithms X509_SIGNING_DEFAULT = X509DSASigningAlgorithms.id_dsa_with_sha256 EPHEMERAL = EphemeralSpec(ephemeral_type=EphemeralType.KEY, size=SizeSpec(size_type=SizeType.DEPENDENT, selector=lambda dsa: dsa.q.bit_length())) USAGE_FREQUENCY = FrequencyType.OFTEN def __init__(self, hash_obj: object=SHA256(), p: int=None, q: int=None, g: int=None, x: int=None, L: int=2048, N: int=256): """ Parameters: hash_obj (object): Instantiated object with compatible hash interface. p (int): (Optional) Prime modulus. q (int): (Optional) Prime modulus. g (int): (Optional) Generator. x (int): (Optional) Private key. L (int): (Optional) Bit length of `p`. N (int): (Optional) Bit length of `q`. """ Primitive.__init__(self) # Parameter generation # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf if not q: q = find_prime(N) # Start somewhere in 2**(L-1); ensure it's even i = Bytes.random((L-1) // 8).int() // 2 * 2 # Construct the base as an even multiple of `q` base = 2**(L-1) // (2*q) * 2 while not is_prime((base + i) * q + 1): i += 2 p = (base + i) * q + 1 assert (p-1) % q == 0 # Construct `g` while True: h = Bytes.random(N // 8).int() % (p-1) g = pow(h, (p-1) // q, p) if h > 1 and h < (p-1) and g > 1: break self.p = p self.q = q self.g = g self.x = x or random_int_between(1, self.q) self.y = pow(self.g, self.x, self.p) self.hash_obj = hash_obj def __repr__(self): return f"<DSA: hash_obj={self.hash_obj}, p={self.p}, q={self.q}, g={self.g}, x={self.x}, y={self.y}>" def __str__(self): return self.__repr__() def sign(self, message: bytes, k: int=None) -> (int, int): """ Signs a `message`. Parameters: message (bytes): Message to sign. k (int): (Optional) Ephemeral key. Returns: (int, int): Signature formatted as (r, s). """ k = k or random_int_between(1, self.q) inv_k = mod_inv(k, self.q) r = pow(self.g, k, self.p) % self.q s = (inv_k * (self.hash_obj.hash(message).int() + self.x * r)) % self.q return (r, s) def verify(self, message: bytes, sig: (int, int)) -> bool: """ Verifies a `message` against a `sig`. Parameters: message (bytes): Message. sig ((int, int)): Signature of `message`. Returns: bool: Whether the signature is valid or not. """ (r, s) = sig w = mod_inv(s, self.q) u_1 = (self.hash_obj.hash(message).int() * w) % self.q u_2 = (r * w) % self.q v = (pow(self.g, u_1, self.p) * pow(self.y, u_2, self.p) % self.p) % self.q return v == r # Confirmed works on ECDSA as well def derive_k_from_sigs(self, msg_a: bytes, sig_a: (int, int), msg_b: bytes, sig_b: (int, int)) -> int: """ Derives `k` from signatures that share an `r` value. Parameters: msg_a (bytes): Message A. msg_b (bytes): Message B. sig_a ((int, int)): Signature of `msg_a`. sig_b ((int, int)): Signature of `msg_b`. Returns: int: Derived `k`. """ (r_a, s_a) = sig_a (r_b, s_b) = sig_b assert r_a == r_b s = (s_a - s_b) % self.q m = (self.hash_obj.hash(msg_a).int() - self.hash_obj.hash(msg_b).int()) % self.q return mod_inv(s, self.q) * m % self.q # Confirmed works on ECDSA as well def derive_x_from_k(self, message: bytes, k: int, sig: (int, int)) -> int: """ Derives `x` from a known `k`. Parameters: message (bytes): Message. k (int): `k` used in `message`'s signature. sig ((int, int)): Signature of `message`. Returns: int: Derived `x`. """ (r, s) = sig return ((s * k) - self.hash_obj.hash(message).int()) * mod_inv(r, self.q) % self.q
class DES(FeistelNetwork, BlockCipher): """ Structure: Feistel Network Key size: 64 (56, actually) Block size: 64 """ KEY_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=64) BLOCK_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=64) USAGE_FREQUENCY = FrequencyType.UNUSUAL def __init__(self, key: bytes): """ Parameters: key (bytes): Bytes-like object to key the cipher. """ super().__init__(round_func, key_schedule) PrimitiveType.__init__(self) self.key = Bytes.wrap(key).zfill(8) self.block_size = 8 def __repr__(self): return f"<DES: key={self.key}>" def __str__(self): return self.__repr__() def process_plaintext(self, plaintext): plaintext_bitstring = bytes_to_bitstring(plaintext) return int.to_bytes( int(''.join([plaintext_bitstring[IP[i] - 1] for i in range(64)]), 2), 8, 'big') def process_ciphertext(self, ciphertext): ciphertext_bitstring = bytes_to_bitstring(ciphertext) return int.to_bytes( int(''.join([ciphertext_bitstring[FP[i] - 1] for i in range(64)]), 2), 8, 'big') def encrypt(self, plaintext: bytes) -> Bytes: """ Encrypts `plaintext`. Parameters: plaintext (bytes): Bytes-like object to be encrypted. Returns: Bytes: Resulting ciphertext. """ permuted_plaintext = self.process_plaintext(plaintext) result = FeistelNetwork.encrypt(self, self.key, permuted_plaintext) return Bytes(self.process_ciphertext(result)) def decrypt(self, ciphertext: bytes) -> Bytes: """ Decrypts `ciphertext`. Parameters: ciphertext (bytes): Bytes-like object to be decrypted. Returns: Bytes: Resulting plaintext. """ permuted_ciphertext = self.process_plaintext(ciphertext) result = FeistelNetwork.decrypt(self, self.key, permuted_ciphertext) return Bytes(self.process_ciphertext(result))
class RC5(BlockCipher): """ Structure: Feistel Network Key size: 0-2040 bits Block size: 32, 64, 128 bits """ KEY_SIZE = SizeSpec(size_type=SizeType.RANGE, sizes=range(0, 2041)) BLOCK_SIZE = SizeSpec(size_type=SizeType.RANGE, sizes=[32, 64, 128]) def __init__(self, key: bytes, num_rounds: int = 12, block_size: int = 128): """ Parameters: key (bytes): Bytes-like object to key the cipher. num_rounds (int): Number of rounds to perform. block_size (int): The desired block size in bits. """ Primitive.__init__(self) if not block_size in [32, 64, 128]: raise Exception("Invalid block size: must be 32, 64, or 128 bits") self.key = Bytes.wrap(key) self.num_rounds = num_rounds self.block_size = block_size // 2 self.mod = 2**self.block_size self.S = self._key_expansion() def __repr__(self): return f"<RC5: key={self.key}, num_rounds={self.num_rounds}, block_size={self.block_size}, S={self.S}>" def __str__(self): return self.__repr__() def _key_expansion(self): b = len(self.key) u = self.block_size // 8 t = 2 * (self.num_rounds + 1) if b == 0: c = 1 elif b % u: self.key = self.key.zfill(u - b % u + b) b = len(self.key) c = b // u else: c = b // u const_idx = int(math.log(self.block_size, 2) - 4) if b == 0: L = [0] else: L = self.key.chunk(b // c) for i in range(b - 1, -1, -1): L[i // u] = Bytes.wrap(L[i // u] << 8).int() + self.key[i] S = [(P_w[const_idx] + (Q_w[const_idx] * i)) % self.mod for i in range(t)] i = j = 0 A = B = 0 for _ in range(3 * max(t, c)): A = S[i] = left_rotate((S[i] + A + B), 3, bits=self.block_size) B = L[j] = left_rotate((L[j] + A + B), (A + B) % self.block_size, bits=self.block_size) i = (i + 1) % t j = (j + 1) % c return S def encrypt(self, plaintext: bytes) -> Bytes: """ Encrypts `plaintext`. Parameters: plaintext (bytes): Bytes-like object to be encrypted. Returns: Bytes: Resulting ciphertext. """ plaintext = Bytes.wrap(plaintext).zfill(self.block_size // 4) A = plaintext[self.block_size // 8:].int() B = plaintext[:self.block_size // 8].int() A = (A + self.S[0]) % self.mod B = (B + self.S[1]) % self.mod for i in range(1, self.num_rounds + 1): A = (left_rotate(A ^ B, B % self.block_size, bits=self.block_size) + self.S[2 * i]) % self.mod B = (left_rotate(B ^ A, A % self.block_size, bits=self.block_size) + self.S[2 * i + 1]) % self.mod return (Bytes(A, 'little').zfill(self.block_size // 8) + Bytes(B, 'little').zfill(self.block_size // 8)) def decrypt(self, ciphertext: bytes) -> Bytes: """ Decrypts `ciphertext`. Parameters: ciphertext (bytes): Bytes-like object to be decrypted. Returns: Bytes: Resulting plaintext. """ ciphertext = Bytes.wrap(ciphertext).zfill(self.block_size // 4) A = ciphertext[:self.block_size // 8].int() B = ciphertext[self.block_size // 8:].int() for i in range(self.num_rounds, 0, -1): B = right_rotate((B - self.S[2 * i + 1]) % self.mod, A % self.block_size, bits=self.block_size) ^ A A = right_rotate((A - self.S[2 * i]) % self.mod, B % self.block_size, bits=self.block_size) ^ B A = (A - self.S[0]) % self.mod B = (B - self.S[1]) % self.mod return Bytes( (Bytes(A, 'little').zfill(self.block_size // 8) + Bytes(B, 'little').zfill(self.block_size // 8)).int()).zfill( self.block_size // 4)
class CFB(StreamingBlockCipherMode): """Cipher feedback block cipher mode.""" EPHEMERAL = EphemeralSpec( ephemeral_type=EphemeralType.NONCE, size=SizeSpec(size_type=SizeType.DEPENDENT, selector=lambda block_mode: block_mode.cipher.BLOCK_SIZE, typical=[128])) def __init__(self, cipher: EncryptionAlg, iv: bytes): """ Parameters: cipher (EncryptionAlg): Instantiated encryption algorithm. iv (bytes): Bytes-like initialization vector. """ Primitive.__init__(self) self.cipher = cipher self.iv = iv def __repr__(self): return f"<CFB: cipher={self.cipher}, iv={self.iv}>" def __str__(self): return self.__repr__() def encrypt(self, plaintext: bytes) -> Bytes: """ Encrypts `plaintext`. Parameters: plaintext (bytes): Bytes-like object to be encrypted. Returns: Bytes: Resulting ciphertext. """ ciphertext = b'' plaintext = Bytes.wrap(plaintext) last_block = self.iv for block in get_blocks(plaintext, self.cipher.block_size, allow_partials=True): enc_block = self.cipher.encrypt( bytes(last_block))[:len(block)] ^ block ciphertext += enc_block last_block = enc_block return ciphertext def decrypt(self, ciphertext: bytes) -> Bytes: """ Decrypts `ciphertext`. Parameters: ciphertext (bytes): Bytes-like object to be decrypted. Returns: Bytes: Resulting plaintext. """ plaintext = b'' ciphertext = Bytes.wrap(ciphertext) last_block = self.iv for block in get_blocks(ciphertext, self.cipher.block_size, allow_partials=True): enc_block = self.cipher.encrypt( bytes(last_block))[:len(block)] ^ block plaintext += enc_block last_block = block return plaintext
class SNOW3G(StreamCipher): """ SNOW3G stream cipher Used in 4G LTE encryption. """ CONSTRUCTION_TYPES = [ConstructionType.LFSR] USAGE_TYPE = UsageType.CELLULAR USAGE_FREQUENCY = FrequencyType.OFTEN EPHEMERAL = EphemeralSpec(ephemeral_type=EphemeralType.NONCE, size=SizeSpec(size_type=SizeType.SINGLE, sizes=128)) def __init__(self, key: bytes, iv: bytes): """ Parameters: key (bytes): Key (128 or 256 bits). iv (bytes): Initialization vector (16 bytes). """ Primitive.__init__(self) self.key = Bytes.wrap(key) self.iv = Bytes.wrap(iv) k0, k1, k2, k3 = [chunk.to_int() for chunk in self.key.chunk(4)] iv0, iv1, iv2, iv3 = [chunk.to_int() for chunk in self.iv.chunk(4)] s = [None] * 16 s[15] = k3 ^ iv0 s[14] = k2 s[13] = k1 s[12] = k0 ^ iv1 s[11] = k3 ^ 0xFFFFFFFF s[10] = k2 ^ 0xFFFFFFFF ^ iv2 s[9] = k1 ^ 0xFFFFFFFF ^ iv3 s[8] = k0 ^ 0xFFFFFFFF s[7] = k3 s[6] = k2 s[5] = k1 s[4] = k0 s[3] = k3 ^ 0xFFFFFFFF s[2] = k2 ^ 0xFFFFFFFF s[1] = k1 ^ 0xFFFFFFFF s[0] = k0 ^ 0xFFFFFFFF self.s = s self.R1 = 0 self.R2 = 0 self.R3 = 0 for _ in range(32): F = self.clock_FSM() self.clock_lfsr(F) def __repr__(self): return f"<SNOW3G: key={self.key}, iv={self.iv}, s={self.s}, R1={self.self.R1}, R2={self.self.R2}, R3={self.self.R3}>" def __str__(self): return self.__repr__() def MULx(self, V: int, c: int) -> int: if V >> 7: return ((V << 1) % 256) ^ c else: return V << 1 def MULa(self, c: int) -> int: return (self.MULxPOW(c, 23, 0xA9) << 24) + (self.MULxPOW(c, 245, 0xA9) << 16) + (self.MULxPOW(c, 48, 0xA9) << 8) + self.MULxPOW(c, 239, 0xA9) def DIVa(self, c: int) -> int: return (self.MULxPOW(c, 16, 0xA9) << 24) + (self.MULxPOW(c, 39, 0xA9) << 16) + (self.MULxPOW(c, 6, 0xA9) << 8) + self.MULxPOW(c, 64, 0xA9) def MULxPOW(self, V: int, i: int, c: int) -> int: if i == 0: return V else: return self.MULx(self.MULxPOW(V, i - 1, c), c) def S1(self, w: int) -> Bytes: return self._perform_sbox_transform(Bytes.wrap(w).zfill(4), RIJ_SBOX, 0x1B, True) def S2(self, w: int) -> Bytes: return self._perform_sbox_transform(Bytes.wrap(w).zfill(4), SQ, 0x69) def _perform_sbox_transform(self, w: bytes, sbox: list, val: int, s2=False) -> Bytes: sqw0, sqw1, sqw2, sqw3 = [sbox[w_i] for w_i in w] r0 = self.MULx(sqw0, val) ^ sqw1 ^ sqw2 ^ self.MULx(sqw3, val) ^ sqw3 r1 = self.MULx(sqw0, val) ^ sqw0 ^ self.MULx(sqw1, val) ^ sqw2 ^ sqw3 r2 = sqw0 ^ self.MULx(sqw1, val) ^ sqw1 ^ self.MULx(sqw2, val) ^ sqw3 r3 = sqw0 ^ sqw1 ^ self.MULx(sqw2, val) ^ sqw2 ^ self.MULx(sqw3, val) return Bytes([r0, r1, r2, r3]).to_int() def clock_FSM(self) -> int: """ Used internally. Clocks the internal FSM. Returns: int: Next value of `F`. """ F = ((self.s[15] + self.R1) % (2 ** 32)) ^ self.R2 r = (self.R2 + (self.R3 ^ self.s[5])) % (2 ** 32) self.R3 = self.S2(self.R2) self.R2 = self.S1(self.R1) self.R1 = r return F def clock_lfsr(self, F: int=None): """ Used internally. Clocks the LFSR and possibly combines with `F`. Parameters: F (int): Value of `F` to be clocked with. """ v = ((self.s[0] << 8) & 0xFFFFFF00) ^ self.MULa((self.s[0] >> 24) & 0xFF) ^ self.s[2] ^ ((self.s[11] >> 8) & 0x00FFFFFF) ^ self.DIVa(self.s[11] & 0xFF) if F: v^= F for i in range(15): self.s[i] = self.s[i + 1] self.s[15] = v def generate(self, length: int) -> Bytes: """ Generates `length` of keystream. Parameters: length (int): Desired length of keystream in bytes. Returns: Bytes: Keystream. """ _F = self.clock_FSM() self.clock_lfsr() ks = [] for _ in range(length): F = self.clock_FSM() ks.append(F ^ self.s[0]) self.clock_lfsr() return sum([Bytes(i).zfill(4) for i in ks])
class Blowfish(BlockCipher): """ Structure: Feistel Network Key size: 32-448 bits Block size: 64 bits """ KEY_SIZE = SizeSpec(size_type=SizeType.RANGE, sizes=range(32, 449)) BLOCK_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=64) def __init__(self, key: bytes, run_key_schedule: bool=True): """ Parameters: key (bytes): Bytes-like object to key the cipher. run_key_schedule (bool): Whether or not to run the key schedule. Useful when extending Blowfish (i.e. bcrypt). """ Primitive.__init__(self) self.key = Bytes.wrap(key) self.P = deepcopy(P) self.S = [deepcopy(S1), deepcopy(S2), deepcopy(S3), deepcopy(S4)] self.block_size = 8 if run_key_schedule: self.key_schedule() def __repr__(self): return f"<Blowfish: key={self.key}, P={self.P}>" def __str__(self): return self.__repr__() def key_schedule(self): self.P = [(subkey ^ P[i]).to_int() for i, subkey in enumerate(self.key.stretch(len(P) * 4).chunk(4))] L = R = 0 for i in range(0, 18, 2): R, L = self.enc_L_R(L, R) self.P[i] = L self.P[i + 1] = R for i in range(4): for j in range(0, 256, 2): R, L = self.enc_L_R(L, R) self.S[i][j] = L self.S[i][j +1] = R def enc_L_R(self, L: int, R: int) -> (int, int): """ Internal function. Used to encrypt integers directly. Parameters: L (int): Left side integer. R (int): Right side integer. Returns: (int, int): The encrypted versions of `L` and `R`. """ L_i, R_i = L, R for i in range(16): L_i ^= self.P[i] L_i &= 0xFFFFFFFF R_i ^= round_func( self.S[0][(L_i >> 24) & 0xFF], self.S[1][(L_i >> 16) & 0xFF], self.S[2][(L_i >> 8) & 0xFF], self.S[3][L_i & 0xFF] ) & 0xFFFFFFFF R_i, L_i = L_i, R_i L_i ^= self.P[16] R_i ^= self.P[17] R_i, L_i = L_i, R_i return R_i, L_i def encrypt(self, plaintext: bytes) -> Bytes: """ Encrypts `plaintext`. Parameters: plaintext (bytes): Bytes-like object to be encrypted. Returns: Bytes: Resulting ciphertext. """ plaintext = Bytes.wrap(plaintext) half = len(plaintext) // 2 L_i, R_i = plaintext[:half].to_int(), plaintext[half:].to_int() R_i, L_i = self.enc_L_R(L_i, R_i) return Bytes(L_i).zfill(4) + Bytes(R_i).zfill(4) def decrypt(self, ciphertext: bytes) -> Bytes: """ Decrypts `ciphertext`. Parameters: ciphertext (bytes): Bytes-like object to be decrypted. Returns: Bytes: Resulting plaintext. """ ciphertext = Bytes.wrap(ciphertext) half = len(ciphertext) // 2 L_i, R_i = ciphertext[:half].to_int(), ciphertext[half:].to_int() L_i ^= self.P[17] R_i ^= self.P[16] R_i, L_i = L_i, R_i for i in range(15, -1, -1): R_i, L_i = L_i, R_i R_i ^= round_func( self.S[0][(L_i >> 24) & 0xFF], self.S[1][(L_i >> 16) & 0xFF], self.S[2][(L_i >> 8) & 0xFF], self.S[3][L_i & 0xFF] ) & 0xFFFFFFFF L_i ^= self.P[i] L_i &= 0xFFFFFFFF return Bytes(L_i).zfill(4) + Bytes(R_i).zfill(4)
class GCM(StreamingBlockCipherMode): """Galois counter mode (GCM) block cipher mode""" EPHEMERAL = EphemeralSpec(ephemeral_type=EphemeralType.NONCE, size=SizeSpec(size_type=SizeType.SINGLE, sizes=96)) AUTH_TAG_SIZE = SizeSpec(size_type=SizeType.SINGLE, sizes=128) USAGE_FREQUENCY = FrequencyType.PROLIFIC def __init__(self, cipher: EncryptionAlg): """ Parameters: cipher (EncryptionAlg): Instantiated encryption algorithm. """ Primitive.__init__(self) self.cipher = cipher self.H = self.cipher.encrypt(b'\x00' * 16).int() self.ctr = CTR(self.cipher, b'\x00' * 8) # Precompute the product table self.product_table = [0] * 16 self.product_table[reverse_bits(1)] = self.H for i in range(2, 16, 2): self.product_table[reverse_bits(i)] = self.gcm_shift( self.product_table[reverse_bits(i // 2)]) self.product_table[reverse_bits( i + 1)] = self.product_table[reverse_bits(i)] ^ self.H def __repr__(self): return f"<GCM: cipher={self.cipher}, H={self.H}, ctr={self.ctr}>" def __str__(self): return self.__repr__() def clock_ctr(self, nonce: bytes) -> Bytes: nonce = Bytes.wrap(nonce) if len(nonce) == 12: self.ctr.nonce = nonce self.ctr.counter = 1 else: payload = nonce.pad_congruent_right(16) + (b'\x00' * 8) + Bytes( len(nonce) * 8).zfill(8) J_0 = Bytes(self.update(0, payload)).zfill(16) self.ctr.nonce = J_0[:15] self.ctr.counter = J_0[-1] return self.ctr.encrypt(Bytes(b'').zfill(16)) def encrypt(self, nonce: bytes, plaintext: bytes, data: bytes = b'') -> Bytes: """ Encrypts `plaintext`. Parameters: nonce (bytes): Bytes-like nonce. plaintext (bytes): Bytes-like object to be encrypted. data (bytes): Bytes-like additional data to be authenticated but not encrypted. Returns: Bytes: Resulting ciphertext. """ tag_mask = self.clock_ctr(nonce) data = Bytes.wrap(data) ciphertext = self.ctr.encrypt(plaintext) tag = self.auth(ciphertext, data, tag_mask) return ciphertext + tag def decrypt(self, nonce: bytes, authed_ciphertext: bytes, data: bytes = b'') -> Bytes: """ Decrypts `ciphertext`. Parameters: nonce (bytes): Bytes-like nonce. authed_ciphertext (bytes): Bytes-like object to be decrypted. data (bytes): Bytes-like additional data to be authenticated. Returns: Bytes: Resulting plaintext. """ from samson.utilities.runtime import RUNTIME authed_ciphertext = Bytes.wrap(authed_ciphertext) ciphertext, orig_tag = authed_ciphertext[:-16], authed_ciphertext[-16:] tag_mask = self.clock_ctr(nonce) data = Bytes.wrap(data) tag = self.auth(ciphertext, data, tag_mask) if not RUNTIME.compare_bytes(tag, orig_tag): raise Exception('Tag mismatch: authentication failed!') return self.ctr.decrypt(ciphertext) def gcm_shift(self, x: int) -> int: high_bit_set = x & 1 x >>= 1 if high_bit_set: x ^= 0xe1 << (128 - 8) return x def mul(self, y): ret = 0 for _ in range(0, 128, 4): high_bit = ret & 0xF ret >>= 4 ret ^= GCM_REDUCTION_TABLE[high_bit] << (128 - 16) ret ^= self.product_table[y & 0xF] y >>= 4 return ret def auth(self, ciphertext: Bytes, ad: Bytes, tag_mask: Bytes) -> Bytes: y = 0 y = self.update(y, ad) y = self.update(y, ciphertext) y ^= (len(ad) << (3 + 64)) | (len(ciphertext) << 3) y = self.mul(y) y ^= tag_mask.int() return Bytes(int.to_bytes(y, 16, 'big')) def update(self, y: int, data: Bytes) -> int: for chunk in data.chunk(16): y ^= chunk.int() y = self.mul(y) extra = len(data) % 16 if extra != 0: block = bytearray(16) block[:extra] = data[-extra:] y ^= int.from_bytes(block, 'big') y = self.mul(y) return y