Add stream_xor(); deprecate encrypt/decrypt_unauthenticated
release / build-wheels (linux, bash) (push) Failing after 15s
release / build-wheels (windows, cmd) (push) Failing after 1m14s
release / build-wheels (macos, bash) (push) Failing after 18s
release / publish-pypi (push) Skipped

Following upstream libaegis 0.10.5 (874f884): the absorbing-mode
unauthenticated pair is deprecated in favor of the pure keystream XOR.
- stream_xor(key, nonce, data, into=None): same function encrypts and
  decrypts; output equals data XOR stream(key, nonce); supports in-place
  use via into (same buffer semantics as elsewhere).
- encrypt_unauthenticated/decrypt_unauthenticated kept for the old
  format but now emit DeprecationWarning; removed from README (stream_xor
  documented under Keystream generation instead).
This commit is contained in:
LeoVasanko committed 2026-10-04 15:57:28 +00:00
1 parent 9d36d3b81b
commit 1eb0b6e8c0
9 files changed
+492 -7

No files matched your search

+2 -5
View File
@@ -79,10 +79,6 @@ The MAC tag is handled separately of ciphertext:
- encrypt_detached(key, nonce, message, ad=None, maclen=16, ct_into=None, mac_into=None) -> (ct, mac) - encrypt_detached(key, nonce, message, ad=None, maclen=16, ct_into=None, mac_into=None) -> (ct, mac)
- decrypt_detached(key, nonce, ct, mac, ad=None, into=None) -> plaintext - decrypt_detached(key, nonce, ct, mac, ad=None, into=None) -> plaintext
No MAC tag, vulnerable to alterations:
- encrypt_unauthenticated(key, nonce, message, into=None) -> ciphertext (testing only)
- decrypt_unauthenticated(key, nonce, ct, into=None) -> plaintext (testing only)
### Incremental AEAD ### Incremental AEAD
Stateful classes that can be used for processing the data in separate chunks: Stateful classes that can be used for processing the data in separate chunks:
@@ -116,6 +112,7 @@ The `Mac` class follows the Python hashlib API for compatibility with code expec
Useful for creating pseudo random bytes as rapidly as possible. Reuse of the same (key, nonce) creates identical output. Useful for creating pseudo random bytes as rapidly as possible. Reuse of the same (key, nonce) creates identical output.
- stream(key, nonce=None, length=None, into=None) -> randombytes - stream(key, nonce=None, length=None, into=None) -> randombytes
- stream_xor(key, nonce, data, into=None) -> data XOR keystream (unauthenticated; encrypts and decrypts)
### Miscellaneous ### Miscellaneous
@@ -311,7 +308,7 @@ pt = ciph.decrypt(key, nonce, ct, into=buf)
print(bytes(pt)) print(bytes(pt))
``` ```
Detached and unauthenticated modes can use same size input and output (no MAC added to ciphertext). Detached encryption instead of `into` takes `ct_into` and `mac_into` separately and returns memoryviews to both. Detached mode can use same size input and output (no MAC added to ciphertext). Detached encryption instead of `into` takes `ct_into` and `mac_into` separately and returns memoryviews to both.
## Performance ## Performance
+10 -2
View File
@@ -100,7 +100,15 @@ class Cipher(Protocol):
into: "Buffer | None" = None, into: "Buffer | None" = None,
) -> "bytearray | Buffer": ... ) -> "bytearray | Buffer": ...
@staticmethod @staticmethod
def encrypt_unauthenticated( def stream_xor(
key: "Buffer",
nonce: "Buffer",
data: "Buffer",
*,
into: "Buffer | None" = None,
) -> bytearray | memoryview: ...
@staticmethod
def encrypt_unauthenticated( # deprecated: use stream_xor
key: "Buffer", key: "Buffer",
nonce: "Buffer", nonce: "Buffer",
message: "Buffer", message: "Buffer",
@@ -108,7 +116,7 @@ class Cipher(Protocol):
into: "Buffer | None" = None, into: "Buffer | None" = None,
) -> bytearray | memoryview: ... ) -> bytearray | memoryview: ...
@staticmethod @staticmethod
def decrypt_unauthenticated( def decrypt_unauthenticated( # deprecated: use stream_xor
key: "Buffer", key: "Buffer",
nonce: "Buffer", nonce: "Buffer",
ct: "Buffer", ct: "Buffer",
+71
View File
@@ -5,6 +5,7 @@
import errno import errno
import sys import sys
import threading import threading
import warnings
from typing import Literal from typing import Literal
from ._loader import ffi from ._loader import ffi
@@ -378,6 +379,55 @@ def stream(
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
def stream_xor(
key: Buffer,
nonce: Buffer,
data: Buffer,
*,
into: Buffer | None = None,
) -> bytearray | memoryview:
"""XOR data with the keystream from stream(); encrypts and decrypts.
Unauthenticated: only use when the data is authenticated some other
way. Never reuse a nonce with the same key.
Args:
key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()).
data: Input bytes to XOR.
into: Buffer to write the output into (default: bytearray created).
Returns:
Output as bytearray if into not provided, memoryview of into otherwise.
Raises:
TypeError: If lengths are invalid.
"""
key = memoryview(key)
nonce = memoryview(nonce)
data = memoryview(data)
if into is not None:
into = memoryview(into)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NONCEBYTES:
raise TypeError(f"nonce length must be {NONCEBYTES}")
if into is None:
out = bytearray(data.nbytes)
else:
if into.nbytes < data.nbytes:
raise TypeError("into length must be at least data.nbytes")
out = into
_lib.aegis128l_stream_xor(
ffi.from_buffer(out),
_ptr(data),
data.nbytes,
_ptr(nonce),
_ptr(key),
)
return out if into is None else memoryview(out)[: data.nbytes] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
key: Buffer, key: Buffer,
nonce: Buffer, nonce: Buffer,
@@ -387,6 +437,10 @@ def encrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (generate with random_key()). key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()). nonce: Public nonce (generate with random_nonce()).
@@ -399,6 +453,12 @@ def encrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"encrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
message = memoryview(message) message = memoryview(message)
@@ -433,6 +493,10 @@ def decrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (same key used during encryption). key: Secret key (same key used during encryption).
nonce: Public nonce (same nonce used during encryption). nonce: Public nonce (same nonce used during encryption).
@@ -445,6 +509,12 @@ def decrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"decrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
ct = memoryview(ct) ct = memoryview(ct)
@@ -1122,6 +1192,7 @@ __all__ = [
"encrypt", "encrypt",
"decrypt", "decrypt",
"stream", "stream",
"stream_xor",
"encrypt_unauthenticated", "encrypt_unauthenticated",
"decrypt_unauthenticated", "decrypt_unauthenticated",
"mac", "mac",
+71
View File
@@ -5,6 +5,7 @@
import errno import errno
import sys import sys
import threading import threading
import warnings
from typing import Literal from typing import Literal
from ._loader import ffi from ._loader import ffi
@@ -378,6 +379,55 @@ def stream(
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
def stream_xor(
key: Buffer,
nonce: Buffer,
data: Buffer,
*,
into: Buffer | None = None,
) -> bytearray | memoryview:
"""XOR data with the keystream from stream(); encrypts and decrypts.
Unauthenticated: only use when the data is authenticated some other
way. Never reuse a nonce with the same key.
Args:
key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()).
data: Input bytes to XOR.
into: Buffer to write the output into (default: bytearray created).
Returns:
Output as bytearray if into not provided, memoryview of into otherwise.
Raises:
TypeError: If lengths are invalid.
"""
key = memoryview(key)
nonce = memoryview(nonce)
data = memoryview(data)
if into is not None:
into = memoryview(into)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NONCEBYTES:
raise TypeError(f"nonce length must be {NONCEBYTES}")
if into is None:
out = bytearray(data.nbytes)
else:
if into.nbytes < data.nbytes:
raise TypeError("into length must be at least data.nbytes")
out = into
_lib.aegis128x2_stream_xor(
ffi.from_buffer(out),
_ptr(data),
data.nbytes,
_ptr(nonce),
_ptr(key),
)
return out if into is None else memoryview(out)[: data.nbytes] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
key: Buffer, key: Buffer,
nonce: Buffer, nonce: Buffer,
@@ -387,6 +437,10 @@ def encrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (generate with random_key()). key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()). nonce: Public nonce (generate with random_nonce()).
@@ -399,6 +453,12 @@ def encrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"encrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
message = memoryview(message) message = memoryview(message)
@@ -433,6 +493,10 @@ def decrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (same key used during encryption). key: Secret key (same key used during encryption).
nonce: Public nonce (same nonce used during encryption). nonce: Public nonce (same nonce used during encryption).
@@ -445,6 +509,12 @@ def decrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"decrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
ct = memoryview(ct) ct = memoryview(ct)
@@ -1122,6 +1192,7 @@ __all__ = [
"encrypt", "encrypt",
"decrypt", "decrypt",
"stream", "stream",
"stream_xor",
"encrypt_unauthenticated", "encrypt_unauthenticated",
"decrypt_unauthenticated", "decrypt_unauthenticated",
"mac", "mac",
+71
View File
@@ -5,6 +5,7 @@
import errno import errno
import sys import sys
import threading import threading
import warnings
from typing import Literal from typing import Literal
from ._loader import ffi from ._loader import ffi
@@ -378,6 +379,55 @@ def stream(
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
def stream_xor(
key: Buffer,
nonce: Buffer,
data: Buffer,
*,
into: Buffer | None = None,
) -> bytearray | memoryview:
"""XOR data with the keystream from stream(); encrypts and decrypts.
Unauthenticated: only use when the data is authenticated some other
way. Never reuse a nonce with the same key.
Args:
key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()).
data: Input bytes to XOR.
into: Buffer to write the output into (default: bytearray created).
Returns:
Output as bytearray if into not provided, memoryview of into otherwise.
Raises:
TypeError: If lengths are invalid.
"""
key = memoryview(key)
nonce = memoryview(nonce)
data = memoryview(data)
if into is not None:
into = memoryview(into)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NONCEBYTES:
raise TypeError(f"nonce length must be {NONCEBYTES}")
if into is None:
out = bytearray(data.nbytes)
else:
if into.nbytes < data.nbytes:
raise TypeError("into length must be at least data.nbytes")
out = into
_lib.aegis128x4_stream_xor(
ffi.from_buffer(out),
_ptr(data),
data.nbytes,
_ptr(nonce),
_ptr(key),
)
return out if into is None else memoryview(out)[: data.nbytes] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
key: Buffer, key: Buffer,
nonce: Buffer, nonce: Buffer,
@@ -387,6 +437,10 @@ def encrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (generate with random_key()). key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()). nonce: Public nonce (generate with random_nonce()).
@@ -399,6 +453,12 @@ def encrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"encrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
message = memoryview(message) message = memoryview(message)
@@ -433,6 +493,10 @@ def decrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (same key used during encryption). key: Secret key (same key used during encryption).
nonce: Public nonce (same nonce used during encryption). nonce: Public nonce (same nonce used during encryption).
@@ -445,6 +509,12 @@ def decrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"decrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
ct = memoryview(ct) ct = memoryview(ct)
@@ -1122,6 +1192,7 @@ __all__ = [
"encrypt", "encrypt",
"decrypt", "decrypt",
"stream", "stream",
"stream_xor",
"encrypt_unauthenticated", "encrypt_unauthenticated",
"decrypt_unauthenticated", "decrypt_unauthenticated",
"mac", "mac",
+71
View File
@@ -5,6 +5,7 @@
import errno import errno
import sys import sys
import threading import threading
import warnings
from typing import Literal from typing import Literal
from ._loader import ffi from ._loader import ffi
@@ -378,6 +379,55 @@ def stream(
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
def stream_xor(
key: Buffer,
nonce: Buffer,
data: Buffer,
*,
into: Buffer | None = None,
) -> bytearray | memoryview:
"""XOR data with the keystream from stream(); encrypts and decrypts.
Unauthenticated: only use when the data is authenticated some other
way. Never reuse a nonce with the same key.
Args:
key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()).
data: Input bytes to XOR.
into: Buffer to write the output into (default: bytearray created).
Returns:
Output as bytearray if into not provided, memoryview of into otherwise.
Raises:
TypeError: If lengths are invalid.
"""
key = memoryview(key)
nonce = memoryview(nonce)
data = memoryview(data)
if into is not None:
into = memoryview(into)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NONCEBYTES:
raise TypeError(f"nonce length must be {NONCEBYTES}")
if into is None:
out = bytearray(data.nbytes)
else:
if into.nbytes < data.nbytes:
raise TypeError("into length must be at least data.nbytes")
out = into
_lib.aegis256_stream_xor(
ffi.from_buffer(out),
_ptr(data),
data.nbytes,
_ptr(nonce),
_ptr(key),
)
return out if into is None else memoryview(out)[: data.nbytes] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
key: Buffer, key: Buffer,
nonce: Buffer, nonce: Buffer,
@@ -387,6 +437,10 @@ def encrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (generate with random_key()). key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()). nonce: Public nonce (generate with random_nonce()).
@@ -399,6 +453,12 @@ def encrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"encrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
message = memoryview(message) message = memoryview(message)
@@ -433,6 +493,10 @@ def decrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (same key used during encryption). key: Secret key (same key used during encryption).
nonce: Public nonce (same nonce used during encryption). nonce: Public nonce (same nonce used during encryption).
@@ -445,6 +509,12 @@ def decrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"decrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
ct = memoryview(ct) ct = memoryview(ct)
@@ -1122,6 +1192,7 @@ __all__ = [
"encrypt", "encrypt",
"decrypt", "decrypt",
"stream", "stream",
"stream_xor",
"encrypt_unauthenticated", "encrypt_unauthenticated",
"decrypt_unauthenticated", "decrypt_unauthenticated",
"mac", "mac",
+71
View File
@@ -5,6 +5,7 @@
import errno import errno
import sys import sys
import threading import threading
import warnings
from typing import Literal from typing import Literal
from ._loader import ffi from ._loader import ffi
@@ -378,6 +379,55 @@ def stream(
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
def stream_xor(
key: Buffer,
nonce: Buffer,
data: Buffer,
*,
into: Buffer | None = None,
) -> bytearray | memoryview:
"""XOR data with the keystream from stream(); encrypts and decrypts.
Unauthenticated: only use when the data is authenticated some other
way. Never reuse a nonce with the same key.
Args:
key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()).
data: Input bytes to XOR.
into: Buffer to write the output into (default: bytearray created).
Returns:
Output as bytearray if into not provided, memoryview of into otherwise.
Raises:
TypeError: If lengths are invalid.
"""
key = memoryview(key)
nonce = memoryview(nonce)
data = memoryview(data)
if into is not None:
into = memoryview(into)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NONCEBYTES:
raise TypeError(f"nonce length must be {NONCEBYTES}")
if into is None:
out = bytearray(data.nbytes)
else:
if into.nbytes < data.nbytes:
raise TypeError("into length must be at least data.nbytes")
out = into
_lib.aegis256x2_stream_xor(
ffi.from_buffer(out),
_ptr(data),
data.nbytes,
_ptr(nonce),
_ptr(key),
)
return out if into is None else memoryview(out)[: data.nbytes] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
key: Buffer, key: Buffer,
nonce: Buffer, nonce: Buffer,
@@ -387,6 +437,10 @@ def encrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (generate with random_key()). key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()). nonce: Public nonce (generate with random_nonce()).
@@ -399,6 +453,12 @@ def encrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"encrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
message = memoryview(message) message = memoryview(message)
@@ -433,6 +493,10 @@ def decrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (same key used during encryption). key: Secret key (same key used during encryption).
nonce: Public nonce (same nonce used during encryption). nonce: Public nonce (same nonce used during encryption).
@@ -445,6 +509,12 @@ def decrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"decrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
ct = memoryview(ct) ct = memoryview(ct)
@@ -1122,6 +1192,7 @@ __all__ = [
"encrypt", "encrypt",
"decrypt", "decrypt",
"stream", "stream",
"stream_xor",
"encrypt_unauthenticated", "encrypt_unauthenticated",
"decrypt_unauthenticated", "decrypt_unauthenticated",
"mac", "mac",
+71
View File
@@ -5,6 +5,7 @@
import errno import errno
import sys import sys
import threading import threading
import warnings
from typing import Literal from typing import Literal
from ._loader import ffi from ._loader import ffi
@@ -378,6 +379,55 @@ def stream(
return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore return out if into is None else memoryview(out)[: length or memoryview(out).nbytes] # type: ignore
def stream_xor(
key: Buffer,
nonce: Buffer,
data: Buffer,
*,
into: Buffer | None = None,
) -> bytearray | memoryview:
"""XOR data with the keystream from stream(); encrypts and decrypts.
Unauthenticated: only use when the data is authenticated some other
way. Never reuse a nonce with the same key.
Args:
key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()).
data: Input bytes to XOR.
into: Buffer to write the output into (default: bytearray created).
Returns:
Output as bytearray if into not provided, memoryview of into otherwise.
Raises:
TypeError: If lengths are invalid.
"""
key = memoryview(key)
nonce = memoryview(nonce)
data = memoryview(data)
if into is not None:
into = memoryview(into)
if key.nbytes != KEYBYTES:
raise TypeError(f"key length must be {KEYBYTES}")
if nonce.nbytes != NONCEBYTES:
raise TypeError(f"nonce length must be {NONCEBYTES}")
if into is None:
out = bytearray(data.nbytes)
else:
if into.nbytes < data.nbytes:
raise TypeError("into length must be at least data.nbytes")
out = into
_lib.aegis256x4_stream_xor(
ffi.from_buffer(out),
_ptr(data),
data.nbytes,
_ptr(nonce),
_ptr(key),
)
return out if into is None else memoryview(out)[: data.nbytes] # type: ignore
def encrypt_unauthenticated( def encrypt_unauthenticated(
key: Buffer, key: Buffer,
nonce: Buffer, nonce: Buffer,
@@ -387,6 +437,10 @@ def encrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Encrypt message without authentication (for testing/debugging). """Encrypt message without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (generate with random_key()). key: Secret key (generate with random_key()).
nonce: Public nonce (generate with random_nonce()). nonce: Public nonce (generate with random_nonce()).
@@ -399,6 +453,12 @@ def encrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"encrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
message = memoryview(message) message = memoryview(message)
@@ -433,6 +493,10 @@ def decrypt_unauthenticated(
) -> bytearray | memoryview: ) -> bytearray | memoryview:
"""Decrypt ciphertext without authentication (for testing/debugging). """Decrypt ciphertext without authentication (for testing/debugging).
.. deprecated::
Use stream_xor() in new code. Its output is different, so keep
this only for data in the old format.
Args: Args:
key: Secret key (same key used during encryption). key: Secret key (same key used during encryption).
nonce: Public nonce (same nonce used during encryption). nonce: Public nonce (same nonce used during encryption).
@@ -445,6 +509,12 @@ def decrypt_unauthenticated(
Raises: Raises:
TypeError: If lengths are invalid. TypeError: If lengths are invalid.
""" """
warnings.warn(
"decrypt_unauthenticated is deprecated; use stream_xor "
"(its output differs, so keep this only for existing data)",
DeprecationWarning,
stacklevel=2,
)
key = memoryview(key) key = memoryview(key)
nonce = memoryview(nonce) nonce = memoryview(nonce)
ct = memoryview(ct) ct = memoryview(ct)
@@ -1122,6 +1192,7 @@ __all__ = [
"encrypt", "encrypt",
"decrypt", "decrypt",
"stream", "stream",
"stream_xor",
"encrypt_unauthenticated", "encrypt_unauthenticated",
"decrypt_unauthenticated", "decrypt_unauthenticated",
"mac", "mac",
+54
View File
@@ -0,0 +1,54 @@
import pytest
from aeg import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
CIPHERS = [aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4]
def cipher_id(c):
return c.NAME
@pytest.mark.parametrize("c", CIPHERS, ids=cipher_id)
def test_stream_xor_roundtrip(c):
key, nonce = c.random_key(), c.random_nonce()
msg = b"stream cipher test" * 100 # spans multiple blocks, unaligned tail
ct = c.stream_xor(key, nonce, msg)
assert isinstance(ct, bytearray)
assert len(ct) == len(msg)
assert ct != msg
# the same function decrypts
assert c.stream_xor(key, nonce, ct) == msg
@pytest.mark.parametrize("c", CIPHERS, ids=cipher_id)
def test_stream_xor_is_stream_xor(c):
"""Output equals data XOR stream(key, nonce) keystream."""
key, nonce = c.random_key(), c.random_nonce()
msg = b"\x5a" * 500
ks = c.stream(key, nonce, len(msg))
ct = c.stream_xor(key, nonce, msg)
assert bytes(ct) == bytes(m ^ k for m, k in zip(msg, ks))
@pytest.mark.parametrize("c", CIPHERS, ids=cipher_id)
def test_stream_xor_into(c):
key, nonce = c.random_key(), c.random_nonce()
msg = b"into target" * 10
buf = bytearray(len(msg) + 10)
out = c.stream_xor(key, nonce, msg, into=buf)
assert len(out) == len(msg)
assert bytes(out) == bytes(c.stream_xor(key, nonce, msg))
@pytest.mark.parametrize("c", CIPHERS, ids=cipher_id)
def test_unauthenticated_deprecated(c):
key, nonce = c.random_key(), c.random_nonce()
msg = b"legacy format" * 20 # longer than one block: absorbing mode diverges
with pytest.warns(DeprecationWarning):
ct = c.encrypt_unauthenticated(key, nonce, msg)
with pytest.warns(DeprecationWarning):
pt = c.decrypt_unauthenticated(key, nonce, ct)
assert pt == msg
# deprecated pair is NOT compatible with stream_xor beyond the first block
assert bytes(ct) != bytes(c.stream_xor(key, nonce, msg))