Skip to content

Commit 64506f7

Browse files
committed
Merge branch 'key_wrap'
2 parents 18f6809 + 2165641 commit 64506f7

File tree

19 files changed

+4611
-61
lines changed

19 files changed

+4611
-61
lines changed

Changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ Changelog
44
Under development
55
++++++++++++++++++++++++++
66

7+
New features
8+
---------------
9+
* Added cipher modes Key Wrap (KW, RFC3394) and Key Wrap with Padding (KWP, RFC5649).
10+
Both are defined also in NIST SP 800-38F.
11+
712
Resolved issues
813
---------------
914
* GH#862: For HashEdDSA and Ed448, sign() and verify() modified the state of the XOF.

Doc/index.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Welcome to PyCryptodome's documentation
33

44
.. toctree::
55
:maxdepth: 3
6-
6+
77
src/introduction
88
src/features
99
src/installation
@@ -12,6 +12,5 @@ Welcome to PyCryptodome's documentation
1212
src/examples
1313
src/faq
1414
src/contribute_support
15-
src/future
1615
src/changelog
1716
src/license

Doc/src/cipher/modern.rst

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ This is the state machine for a cipher object:
2828
.. figure:: aead.png
2929
:align: center
3030
:figwidth: 80%
31-
31+
3232
Generic state diagram for a AEAD cipher mode
3333

3434
Beside the usual :meth:`encrypt()` and :meth:`decrypt()` already
@@ -550,3 +550,106 @@ Example (decryption with multiple chunks)::
550550
>>> except (ValueError, KeyError):
551551
>>> print("Incorrect decryption")
552552

553+
.. _kw_mode:
554+
555+
KW mode
556+
--------
557+
`Key Wrapping <https://datatracker.ietf.org/doc/html/rfc3394>`_
558+
(or `NIST SP 800-38F <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf>`_)
559+
is an ad-hoc authenticated cipher mode designed to wrap cryptographic keys,
560+
and not other types of data.
561+
It is deterministic (it doesn't use nonces or IVs) and
562+
only works with ciphers with a block size of 128 bits (like AES).
563+
564+
The cryptographic key to wrap must have a length multiple of 8.
565+
566+
There is no reason to prefer this ad-hoc mode to one like SIV.
567+
568+
The :func:`new` function at the module level under ``Crypto.Cipher`` instantiates
569+
a new KW cipher object for the relevant base algorithm.
570+
In the following definition, ``<algorithm>`` can only be ``AES`` today:
571+
572+
.. function:: Crypto.Cipher.<algorithm>.new(key, <algorithm>.MODE_KW)
573+
574+
Create a new Key Wrapping (KW) cipher object,
575+
using <algorithm> as the base block cipher.
576+
577+
:param bytes key: the cryptographic key
578+
:return: a KW cipher object
579+
580+
The cipher object has one read-only attribute :attr:`block_size`,
581+
and two methods (``seal`` and ``unseal``).
582+
583+
Example of encryption::
584+
585+
>>> from Crypto.Cipher import AES
586+
>>> from Crypto.Random import get_random_bytes
587+
>>>
588+
>>> key_to_wrap = b"1234567890123456"
589+
>>> wrapping_key = get_random_bytes(16)
590+
>>> cipher = AES.new(wrapping_key, AES.MODE_KW)
591+
>>> ciphertext = cipher.seal(key_to_wrap)
592+
593+
Example of decryption::
594+
595+
>>> from Crypto.Cipher import AES
596+
>>> from Crypto.Random import get_random_bytes
597+
>>>
598+
>>> wrapping_key = get_random_bytes(16)
599+
>>> cipher = AES.new(wrapping_key, AES.MODE_KW)
600+
>>> try:
601+
>>> unwrapped_key = cipher.unseal(ciphertext)
602+
>>> except ValueError:
603+
>>> print("Invalid decryption")
604+
605+
.. _kwp_mode:
606+
607+
KWP mode
608+
--------
609+
`Key Wrapping with Padding <https://datatracker.ietf.org/doc/html/rfc5649>`_
610+
(or `NIST SP 800-38F <https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-38F.pdf>`_)
611+
is an ad-hoc authenticated cipher mode designed to wrap cryptographic keys,
612+
and not other types of data.
613+
It is deterministic (it doesn't use nonces or IVs) and
614+
only works with ciphers with a block size of 128 bits (like AES).
615+
616+
The cryptographic key to wrap can have any length.
617+
618+
There is no reason to prefer this ad-hoc mode to one like SIV.
619+
620+
The :func:`new` function at the module level under ``Crypto.Cipher`` instantiates
621+
a new KWP cipher object for the relevant base algorithm.
622+
In the following definition, ``<algorithm>`` can only be ``AES`` today:
623+
624+
.. function:: Crypto.Cipher.<algorithm>.new(key, <algorithm>.MODE_KWP)
625+
626+
Create a new Key Wrapping with Padding (KWP) cipher object,
627+
using <algorithm> as the base block cipher.
628+
629+
:param bytes key: the cryptographic key
630+
:return: a KWP cipher object
631+
632+
The cipher object has one read-only attribute :attr:`block_size`,
633+
and two methods (``seal`` and ``unseal``).
634+
635+
Example of encryption::
636+
637+
>>> from Crypto.Cipher import AES
638+
>>> from Crypto.Random import get_random_bytes
639+
>>>
640+
>>> key_to_wrap = b"1234567890123456"
641+
>>> wrapping_key = get_random_bytes(16)
642+
>>> cipher = AES.new(wrapping_key, AES.MODE_KWP)
643+
>>> ciphertext = cipher.seal(key_to_wrap)
644+
645+
Example of decryption::
646+
647+
>>> from Crypto.Cipher import AES
648+
>>> from Crypto.Random import get_random_bytes
649+
>>>
650+
>>> wrapping_key = get_random_bytes(16)
651+
>>> cipher = AES.new(wrapping_key, AES.MODE_KWP)
652+
>>> try:
653+
>>> unwrapped_key = cipher.unseal(ciphertext)
654+
>>> except ValueError:
655+
>>> print("Invalid decryption")

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ For faster public key operations in Unix, you should install `GMP`_ in your syst
4444
PyCryptodome is a fork of PyCrypto. It brings the following enhancements
4545
with respect to the last official version of PyCrypto (2.6.1):
4646

47-
* Authenticated encryption modes (GCM, CCM, EAX, SIV, OCB)
47+
* Authenticated encryption modes (GCM, CCM, EAX, SIV, OCB, KW, KWP)
4848
* Hybrid Public Key Encryption (HPKE)
4949
* Accelerated AES on Intel platforms via AES-NI
5050
* First class support for PyPy

lib/Crypto/Cipher/AES.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
MODE_SIV = 10 #: Synthetic Initialization Vector (:ref:`siv_mode`)
4242
MODE_GCM = 11 #: Galois Counter Mode (:ref:`gcm_mode`)
4343
MODE_OCB = 12 #: Offset Code Book (:ref:`ocb_mode`)
44-
44+
MODE_KW = 13 #: Key Wrap (:ref:`kw_mode`)
45+
MODE_KWP = 14 #: Key Wrap with Padding (:ref:`kwp_mode`)
4546

4647
_cproto = """
4748
int AES_start_operation(const uint8_t key[],

lib/Crypto/Cipher/__init__.py

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,45 +22,10 @@
2222
#
2323
# where mode_state is a a pointer to base_cipher_state plus mode-specific data.
2424

25-
import os
26-
27-
from Crypto.Cipher._mode_ecb import _create_ecb_cipher
28-
from Crypto.Cipher._mode_cbc import _create_cbc_cipher
29-
from Crypto.Cipher._mode_cfb import _create_cfb_cipher
30-
from Crypto.Cipher._mode_ofb import _create_ofb_cipher
31-
from Crypto.Cipher._mode_ctr import _create_ctr_cipher
32-
from Crypto.Cipher._mode_openpgp import _create_openpgp_cipher
33-
from Crypto.Cipher._mode_ccm import _create_ccm_cipher
34-
from Crypto.Cipher._mode_eax import _create_eax_cipher
35-
from Crypto.Cipher._mode_siv import _create_siv_cipher
36-
from Crypto.Cipher._mode_gcm import _create_gcm_cipher
37-
from Crypto.Cipher._mode_ocb import _create_ocb_cipher
38-
39-
_modes = { 1:_create_ecb_cipher,
40-
2:_create_cbc_cipher,
41-
3:_create_cfb_cipher,
42-
5:_create_ofb_cipher,
43-
6:_create_ctr_cipher,
44-
7:_create_openpgp_cipher,
45-
9:_create_eax_cipher
46-
}
47-
48-
_extra_modes = { 8:_create_ccm_cipher,
49-
10:_create_siv_cipher,
50-
11:_create_gcm_cipher,
51-
12:_create_ocb_cipher
52-
}
53-
5425
def _create_cipher(factory, key, mode, *args, **kwargs):
5526

5627
kwargs["key"] = key
5728

58-
modes = dict(_modes)
59-
if kwargs.pop("add_aes_modes", False):
60-
modes.update(_extra_modes)
61-
if not mode in modes:
62-
raise ValueError("Mode not supported")
63-
6429
if args:
6530
if mode in (8, 9, 10, 11, 12):
6631
if len(args) > 1:
@@ -76,4 +41,51 @@ def _create_cipher(factory, key, mode, *args, **kwargs):
7641
elif mode == 1:
7742
raise TypeError("IV is not meaningful for the ECB mode")
7843

79-
return modes[mode](factory, **kwargs)
44+
res = None
45+
extra_modes = kwargs.pop("add_aes_modes", False)
46+
47+
if mode == 1:
48+
from Crypto.Cipher._mode_ecb import _create_ecb_cipher
49+
res = _create_ecb_cipher(factory, **kwargs)
50+
elif mode == 2:
51+
from Crypto.Cipher._mode_cbc import _create_cbc_cipher
52+
res = _create_cbc_cipher(factory, **kwargs)
53+
elif mode == 3:
54+
from Crypto.Cipher._mode_cfb import _create_cfb_cipher
55+
res = _create_cfb_cipher(factory, **kwargs)
56+
elif mode == 5:
57+
from Crypto.Cipher._mode_ofb import _create_ofb_cipher
58+
res = _create_ofb_cipher(factory, **kwargs)
59+
elif mode == 6:
60+
from Crypto.Cipher._mode_ctr import _create_ctr_cipher
61+
res = _create_ctr_cipher(factory, **kwargs)
62+
elif mode == 7:
63+
from Crypto.Cipher._mode_openpgp import _create_openpgp_cipher
64+
res = _create_openpgp_cipher(factory, **kwargs)
65+
elif mode == 9:
66+
from Crypto.Cipher._mode_eax import _create_eax_cipher
67+
res = _create_eax_cipher(factory, **kwargs)
68+
elif extra_modes:
69+
if mode == 8:
70+
from Crypto.Cipher._mode_ccm import _create_ccm_cipher
71+
res = _create_ccm_cipher(factory, **kwargs)
72+
elif mode == 10:
73+
from Crypto.Cipher._mode_siv import _create_siv_cipher
74+
res = _create_siv_cipher(factory, **kwargs)
75+
elif mode == 11:
76+
from Crypto.Cipher._mode_gcm import _create_gcm_cipher
77+
res = _create_gcm_cipher(factory, **kwargs)
78+
elif mode == 12:
79+
from Crypto.Cipher._mode_ocb import _create_ocb_cipher
80+
res = _create_ocb_cipher(factory, **kwargs)
81+
elif mode == 13:
82+
from Crypto.Cipher._mode_kw import _create_kw_cipher
83+
res = _create_kw_cipher(factory, **kwargs)
84+
elif mode == 14:
85+
from Crypto.Cipher._mode_kwp import _create_kwp_cipher
86+
res = _create_kwp_cipher(factory, **kwargs)
87+
88+
if res is None:
89+
raise ValueError("Mode not supported")
90+
91+
return res

0 commit comments

Comments
 (0)