1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import struct from typing import Generator, Tuple
class ChaCha20: def __init__(self, key: bytes, nonce: bytes, counter: int = 0): self.state = [ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, *struct.unpack('<8I', key), counter, *struct.unpack('<3I', nonce) ]
@staticmethod def _quarter_round(a: int, b: int, c: int, d: int) -> Tuple[int, int, int, int]: a = (a + b) & 0xFFFFFFFF d = ((d ^ a) << 16 | (d ^ a) >> 16) & 0xFFFFFFFF c = (c + d) & 0xFFFFFFFF b = ((b ^ c) << 12 | (b ^ c) >> 20) & 0xFFFFFFFF a = (a + b) & 0xFFFFFFFF d = ((d ^ a) << 8 | (d ^ a) >> 24) & 0xFFFFFFFF c = (c + d) & 0xFFFFFFFF b = ((b ^ c) << 7 | (b ^ c) >> 25) & 0xFFFFFFFF return a, b, c, d
def _generate_block(self) -> bytes: state = self.state.copy() for _ in range(10): state[0], state[4], state[8], state[12] = self._quarter_round(state[0], state[4], state[8], state[12]) state[1], state[5], state[9], state[13] = self._quarter_round(state[1], state[5], state[9], state[13]) state[2], state[6], state[10], state[14] = self._quarter_round(state[2], state[6], state[10], state[14]) state[3], state[7], state[11], state[15] = self._quarter_round(state[3], state[7], state[11], state[15]) state[0], state[5], state[10], state[15] = self._quarter_round(state[0], state[5], state[10], state[15]) state[1], state[6], state[11], state[12] = self._quarter_round(state[1], state[6], state[11], state[12]) state[2], state[7], state[8], state[13] = self._quarter_round(state[2], state[7], state[8], state[13]) state[3], state[4], state[9], state[14] = self._quarter_round(state[3], state[4], state[9], state[14]) block = bytearray() for i in range(16): block.extend(struct.pack('<I', (state[i] + self.state[i]) & 0xFFFFFFFF)) self.state[12] = (self.state[12] + 1) & 0xFFFFFFFF return bytes(block)
def keystream(self) -> Generator[bytes, None, None]: while True: yield self._generate_block()
def decrypt(ciphertext: bytes, key: str) -> bytes: key_bytes = key.ljust(32, '\0').encode()[:32] chacha = ChaCha20(key_bytes, b'\x00'*12) plain = bytearray() for i, (c, k) in enumerate(zip(ciphertext, (b for block in chacha.keystream() for b in block))): plain.append(((c - 1) % 256) ^ k) return bytes(plain)
ciphertext = bytes.fromhex("""12 1D 58 7A 7F 60 FE 96 74 D6 7B 56 67 CE 72 3F F2 D2 CE 47 19 59 9B 09 42 07 2C 55 B7 44 10 DE 46 E5 38 B7 74 95 78 42 95 BD 7D B5 64 55 CD 52 8D D2 69 C6 10 5F 82 29 29 9D 30 F6 B4""".replace(" ", "").replace("\n", ""))
print(decrypt(ciphertext, "qewuri").decode('utf-8'))
|