Skip to content

Commit

Permalink
🗓 Sep 4, 2023 4:50:13 PM
Browse files Browse the repository at this point in the history
✨ rabbit encryption
🧪 tests added/updated
  • Loading branch information
securisec committed Sep 4, 2023
1 parent 01d273d commit f9d1700
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 3 deletions.
3 changes: 1 addition & 2 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ New ideas:
☐ ✨ zero-width encode
☐ ✨ hill cipher encode/decode/brute
☐ 💡 maybe a decorator function to convert all inputs into bytes when possible? this will allow for a consistant bytes approach to all functions
☐ ✨ jwt hmac confusion
☐ ✨ amf encode/decode
☐ ✨ rabbit encryption
☐ ✨ aes cmac
☐ ✨ whitespace encoding https://www.dcode.fr/whitespace-language

Expand Down Expand Up @@ -59,6 +57,7 @@ Misc:
☐ cyberchef recipe to chepy recipe converter

Archive:
✔ ✨ rabbit encryption
✔ ✨ cetacean encode/decode
✔ ✨ huffman encode/decode
✔ ✨ new plugin that can help detect encoding type trained on random data
Expand Down
16 changes: 15 additions & 1 deletion chepy/modules/encryptionencoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
decrypt_pad as _ls47_dec,
derive_key as _derive_key,
)
from .internal.constants import Ciphers
from .internal.constants import Ciphers, Rabbit

import lazy_import

Expand Down Expand Up @@ -1757,3 +1757,17 @@ def cetacean_decode(self) -> EncryptionEncodingT:

self.state = "".join([chr(int(byte, 2)) for byte in byteArray])
return self

@ChepyDecorators.call_stack
def rabbit(self, key: str, iv: Union[None, str] = None) -> EncryptionEncodingT:
"""Rabbit encryption/decryption
Args:
key (str): Key
iv (Union[None,str], optional): IV. Defaults to None.
Returns:
Chepy: The Chepy object.
"""
self.state = Rabbit(key, iv).encrypt(self._convert_to_str())
return self
1 change: 1 addition & 0 deletions chepy/modules/encryptionencoding.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ class EncryptionEncoding(ChepyCore):
def huffman_decode(self: EncryptionEncodingT, huffman_codes: Dict[str, str]) -> EncryptionEncodingT: ...
def cetacean_encode(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
def cetacean_decode(self: EncryptionEncodingT) -> EncryptionEncodingT: ...
def rabbit(self: EncryptionEncodingT, key: str, iv: Union[None, str]=...) -> EncryptionEncodingT: ...
210 changes: 210 additions & 0 deletions chepy/modules/internal/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,216 @@
from collections import Counter


class Rabbit: # pragma: no cover
# https://github.com/mozilla/positron/blob/master/python/PyECC/ecc/Rabbit.py
# ------------------------------------------------------------------------------
#
# R A B B I T Stream Cipher
# by M. Boesgaard, M. Vesterager, E. Zenner (specified in RFC 4503)
#
#
# Pure Python Implementation by Toni Mattis
#
# ------------------------------------------------------------------------------
def __init__(self, key, iv=None):
"""Initialize Rabbit cipher using a 128 bit integer/string"""
self.WORDSIZE = 0x100000000

self.rot08 = lambda x: ((x << 8) & 0xFFFFFFFF) | (x >> 24)
self.rot16 = lambda x: ((x << 16) & 0xFFFFFFFF) | (x >> 16)

if isinstance(key, str):
# interpret key string in big endian byte order
if len(key) < 16:
key = "\x00" * (16 - len(key)) + key
# if len(key) > 16 bytes only the first 16 will be considered
k = [ord(key[i + 1]) | (ord(key[i]) << 8) for i in range(14, -1, -2)]
else:
# k[0] = least significant 16 bits
# k[7] = most significant 16 bits
k = [(key >> i) & 0xFFFF for i in range(0, 128, 16)]

# State and counter initialization
x = [
(k[(j + 5) % 8] << 16) | k[(j + 4) % 8]
if j & 1
else (k[(j + 1) % 8] << 16) | k[j]
for j in range(8)
]
c = [
(k[j] << 16) | k[(j + 1) % 8]
if j & 1
else (k[(j + 4) % 8] << 16) | k[(j + 5) % 8]
for j in range(8)
]

self.x = x
self.c = c
self.b = 0
self._buf = 0 # output buffer
self._buf_bytes = 0 # fill level of buffer

self.next()
self.next()
self.next()
self.next()

for j in range(8):
c[j] ^= x[(j + 4) % 8]

self.start_x = self.x[:] # backup initial key for IV/reset
self.start_c = self.c[:]
self.start_b = self.b

if iv != None:
self.set_iv(iv)

def _nsf(self, u, v):
"""Internal non-linear state transition"""
s = (u + v) % self.WORDSIZE
s = s * s
return (s ^ (s >> 32)) % self.WORDSIZE

def reset(self, iv=None):
"""Reset the cipher and optionally set a new IV (int64 / string)."""

self.c = self.start_c[:]
self.x = self.start_x[:]
self.b = self.start_b
self._buf = 0
self._buf_bytes = 0
if iv != None:
self.set_iv(iv)

def set_iv(self, iv):
"""Set a new IV (64 bit integer / bytestring)."""

if isinstance(iv, str):
i = 0
for c in iv:
i = (i << 8) | ord(c)
iv = i

c = self.c
i0 = iv & 0xFFFFFFFF
i2 = iv >> 32
i1 = ((i0 >> 16) | (i2 & 0xFFFF0000)) % self.WORDSIZE
i3 = ((i2 << 16) | (i0 & 0x0000FFFF)) % self.WORDSIZE

c[0] ^= i0
c[1] ^= i1
c[2] ^= i2
c[3] ^= i3
c[4] ^= i0
c[5] ^= i1
c[6] ^= i2
c[7] ^= i3

self.next()
self.next()
self.next()
self.next()

def next(self):
"""Proceed to the next internal state"""

c = self.c
x = self.x
b = self.b

t = c[0] + 0x4D34D34D + b
c[0] = t % self.WORDSIZE
t = c[1] + 0xD34D34D3 + t // self.WORDSIZE
c[1] = t % self.WORDSIZE
t = c[2] + 0x34D34D34 + t // self.WORDSIZE
c[2] = t % self.WORDSIZE
t = c[3] + 0x4D34D34D + t // self.WORDSIZE
c[3] = t % self.WORDSIZE
t = c[4] + 0xD34D34D3 + t // self.WORDSIZE
c[4] = t % self.WORDSIZE
t = c[5] + 0x34D34D34 + t // self.WORDSIZE
c[5] = t % self.WORDSIZE
t = c[6] + 0x4D34D34D + t // self.WORDSIZE
c[6] = t % self.WORDSIZE
t = c[7] + 0xD34D34D3 + t // self.WORDSIZE
c[7] = t % self.WORDSIZE
b = t // self.WORDSIZE

g = [self._nsf(x[j], c[j]) for j in range(8)]

x[0] = (g[0] + self.rot16(g[7]) + self.rot16(g[6])) % self.WORDSIZE
x[1] = (g[1] + self.rot08(g[0]) + g[7]) % self.WORDSIZE
x[2] = (g[2] + self.rot16(g[1]) + self.rot16(g[0])) % self.WORDSIZE
x[3] = (g[3] + self.rot08(g[2]) + g[1]) % self.WORDSIZE
x[4] = (g[4] + self.rot16(g[3]) + self.rot16(g[2])) % self.WORDSIZE
x[5] = (g[5] + self.rot08(g[4]) + g[3]) % self.WORDSIZE
x[6] = (g[6] + self.rot16(g[5]) + self.rot16(g[4])) % self.WORDSIZE
x[7] = (g[7] + self.rot08(g[6]) + g[5]) % self.WORDSIZE

self.b = b
return self

def derive(self):
"""Derive a 128 bit integer from the internal state"""

x = self.x
return (
((x[0] & 0xFFFF) ^ (x[5] >> 16))
| (((x[0] >> 16) ^ (x[3] & 0xFFFF)) << 16)
| (((x[2] & 0xFFFF) ^ (x[7] >> 16)) << 32)
| (((x[2] >> 16) ^ (x[5] & 0xFFFF)) << 48)
| (((x[4] & 0xFFFF) ^ (x[1] >> 16)) << 64)
| (((x[4] >> 16) ^ (x[7] & 0xFFFF)) << 80)
| (((x[6] & 0xFFFF) ^ (x[3] >> 16)) << 96)
| (((x[6] >> 16) ^ (x[1] & 0xFFFF)) << 112)
)

def keystream(self, n):
"""Generate a keystream of n bytes"""

res = ""
b = self._buf
j = self._buf_bytes
next = self.next
derive = self.derive

for i in range(n):
if not j:
j = 16
next()
b = derive()
res += chr(b & 0xFF)
j -= 1
b >>= 1

self._buf = b
self._buf_bytes = j
return res

def encrypt(self, data):
"""Encrypt/Decrypt data of arbitrary length."""

res = ""
b = self._buf
j = self._buf_bytes
next = self.next
derive = self.derive

for c in data:
if not j: # empty buffer => fetch next 128 bits
j = 16
next()
b = derive()
res += chr(ord(c) ^ (b & 0xFF))
j -= 1
b >>= 1
self._buf = b
self._buf_bytes = j
return res

decrypt = encrypt


class HuffmanNode:
def __init__(self, char, freq):
self.char = char
Expand Down
4 changes: 4 additions & 0 deletions tests/test_encryptionencoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,3 +824,7 @@ def test_cetacean():
.o
== b"hello {}"
)


def test_rabbit():
assert Chepy("hello").rabbit("pass", "iv").rabbit("pass", "iv").o == b"hello"

0 comments on commit f9d1700

Please sign in to comment.