View difference between Paste ID: 6K999DAK and B5WZmJEH
SHOW: | | - or go back to the newest paste.
1-
"""Copyright (c) 2026 Jonathan Voss (k98kurz)
1+
"""Copyright (c) 2025 Jonathan Voss (k98kurz)
2
3
Permission to use, copy, modify, and/or distribute this software
4
for any purpose with or without fee is hereby granted, provided
5
that the above copyleft notice and this permission notice appear in
6
all copies.
7
8
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
11
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
12
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
13
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
14
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
15
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
"""
17
18
from hashlib import sha256
19
from struct import pack, unpack
20
from os import urandom
21
22
IV_SIZE = 16
23
24
def xor(b1: bytes, b2: bytes) -> bytes:
25
    """XOR two equal-length byte strings together."""
26
    b3 = bytearray()
27
    for i in range(len(b1)):
28
        b3.append(b1[i] ^ b2[i])
29
    return bytes(b3)
30
31-
def derive_key(*parts: bytes) -> bytes:
31+
def derive_key(*parts: list[bytes]) -> bytes:
32
    """Derives a one-time key from parts."""
33
    pad = b''.join([sha256(p).digest() for p in parts])
34
    return sha256(pad).digest()
35
36
def keystream(key: bytes, iv: bytes, length: int, start: int = 0) -> bytes:
37
    """Get a keystream of bytes. If start is specified, it will skip
38
      that many bytes; if it is a multiple of 32, it will skip those
39
      hashes.
40
    """
41
    key = derive_key(key, iv, b'enc')
42
    data = b''
43
    counter = 0
44
    if start // 32 > 0:
45
        counter = start // 32
46
        start -= 32 * counter
47
    while len(data) < length + start:
48
        data += sha256(key+counter.to_bytes(4, 'big')).digest()
49
        counter += 1
50
    data = data[start:]
51
    return data[:length]
52
53
def symcrypt(key: bytes, iv: bytes, data: bytes) -> bytes:
54
    """Get a keystream of bytes equal in length to the data bytes,
55
      then XOR the data with the keystream.
56
    """
57
    pad = keystream(key, iv, len(data))
58
    return xor(data, pad)
59
60
def encrypt(key: bytes, data: bytes, iv: bytes | None = None) -> tuple[bytes, bytes]:
61
    """Encrypt the plaintext, returning the IV and ciphertext together."""
62
    iv = iv or urandom(IV_SIZE)
63
    return (iv, symcrypt(key, iv, data))
64
65
def decrypt(key: bytes, iv: bytes, ct: bytes) -> bytes:
66
    """Decrypt the iv+ciphertext. Return the plaintext."""
67
    return symcrypt(key, iv, ct)
68
69
def hmac(key: bytes, iv: bytes, message: bytes) -> bytes:
70
    """Create an hmac according to rfc 2104 specifications."""
71
    # set up variables
72
    B = 136
73
    ipad_byte = 0x36
74
    opad_byte = 0x5c
75
    null_byte = 0x00
76
    ipad = bytes([ipad_byte] * B)
77
    opad = bytes([opad_byte] * B)
78
79
    key = derive_key(key, iv, b'mac')
80
81
    # pad key with null bytes
82
    key = key + bytes([null_byte] * (B - len(key)))
83
84
    # compute and return the hmac
85
    partial = sha256(xor(key, ipad) + message).digest()
86
    return sha256(xor(key, opad) + partial).digest()
87
88
def check_hmac(key: bytes, iv: bytes, message: bytes, mac: bytes) -> bool:
89
    """Check an hmac."""
90
    # first compute the proper hmac
91
    computed = hmac(key, iv, message)
92
93
    # if it is the wrong length, reject
94
    if len(mac) != len(computed):
95
        return False
96
97
    # compute difference without revealing anything through timing attack
98
    diff = 0
99
    for i in range(len(mac)):
100
        diff += mac[i] ^ computed[i]
101
102
    return diff == 0
103
104-
def seal(key: bytes, plaintext: bytes) -> bytes:
104+
def seal(key: bytes, plaintext: bytes) -> str:
105
    """Generate an iv, encrypt a message, and create an hmac all in one."""
106
    iv, ct = encrypt(key, plaintext)
107
    return pack(
108
        f'{IV_SIZE}s32s{len(ct)}s',
109
        iv,
110
        hmac(key, iv, ct),
111
        ct
112
    )
113
114
def unseal(key: bytes, ciphergram: bytes) -> bytes:
115
    """Checks hmac, then decrypts the message."""
116
    iv, ac, ct = unpack(f'{IV_SIZE}s32s{len(ciphergram)-32-IV_SIZE}s', ciphergram)
117
118
    if not check_hmac(key, iv, ct, ac):
119
        raise Exception('HMAC authentication failed')
120
121
    return decrypt(key, iv, ct)