Add stream_xor(); deprecate encrypt/decrypt_unauthenticated
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:
1 parent
9d36d3b81b
commit
1eb0b6e8c0
9 files changed
+492
-7
No files matched your search
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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))
|
||||||
Reference in new issue
Block a user