diff options
-rw-r--r-- | pysnmp/crypto/__init__.py | 102 | ||||
-rw-r--r-- | pysnmp/crypto/aes.py | 48 | ||||
-rw-r--r-- | pysnmp/crypto/des.py | 49 | ||||
-rw-r--r-- | pysnmp/crypto/des3.py | 48 | ||||
-rw-r--r-- | pysnmp/proto/secmod/eso/priv/des3.py | 6 | ||||
-rw-r--r-- | pysnmp/proto/secmod/rfc3414/priv/des.py | 6 | ||||
-rw-r--r-- | pysnmp/proto/secmod/rfc3826/priv/aes.py | 6 |
7 files changed, 207 insertions, 58 deletions
diff --git a/pysnmp/crypto/__init__.py b/pysnmp/crypto/__init__.py index 1ed4488d..e8e92b1c 100644 --- a/pysnmp/crypto/__init__.py +++ b/pysnmp/crypto/__init__.py @@ -1,9 +1,15 @@ -"""Backend-independent cryptographic implementations to allow migration to pyca/cryptography +"""Backend-selecting cryptographic logic to allow migration to pyca/cryptography without immediately dropping support for legacy minor Python versions. + +On installation, the correct backend dependency is selected based on the Python +version. Versions that are supported by pyca/cryptography use that backend; all +other versions (currently 2.4, 2.5, 2.6, 3.2, and 3.3) fall back to Pycryptodome. """ from pysnmp.proto import errind, error CRYPTOGRPAHY = 'cryptography' CRYPTODOME = 'Cryptodome' + +# Determine the available backend. Always prefer cryptography if it is available. try: import cryptography backend = CRYPTOGRPAHY @@ -15,53 +21,111 @@ except ImportError: backend = None -def raise_backend_error(*args, **kwargs): - raise error.StatusInformation( - errorIndication=errind.decryptionError - ) - - def _cryptodome_encrypt(cipher_factory, plaintext, key, iv): - """""" + """Use a Pycryptodome cipher factory to encrypt data. + + :param cipher_factory: Factory callable that builds a Pycryptodome Cipher instance based + on the key and IV + :type cipher_factory: callable + :param bytes plaintext: Plaintext data to encrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Encrypted ciphertext + :rtype: bytes + """ encryptor = cipher_factory(key, iv) return encryptor.encrypt(plaintext) def _cryptodome_decrypt(cipher_factory, ciphertext, key, iv): - """""" + """Use a Pycryptodome cipher factory to decrypt data. + + :param cipher_factory: Factory callable that builds a Pycryptodome Cipher instance based + on the key and IV + :type cipher_factory: callable + :param bytes ciphertext: Ciphertext data to decrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Decrypted plaintext + :rtype: bytes + """ decryptor = cipher_factory(key, iv) return decryptor.decrypt(ciphertext) def _cryptography_encrypt(cipher_factory, plaintext, key, iv): - """""" + """Use a cryptography cipher factory to encrypt data. + + :param cipher_factory: Factory callable that builds a cryptography Cipher instance based + on the key and IV + :type cipher_factory: callable + :param bytes plaintext: Plaintext data to encrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Encrypted ciphertext + :rtype: bytes + """ encryptor = cipher_factory(key, iv).encryptor() return encryptor.update(plaintext) + encryptor.finalize() def _cryptography_decrypt(cipher_factory, ciphertext, key, iv): - """""" + """Use a cryptography cipher factory to decrypt data. + + :param cipher_factory: Factory callable that builds a cryptography Cipher instance based + on the key and IV + :type cipher_factory: callable + :param bytes ciphertext: Ciphertext data to decrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Decrypted plaintext + :rtype: bytes + """ decryptor = cipher_factory(key, iv).decryptor() return decryptor.update(ciphertext) + decryptor.finalize() _DECRYPT_MAP = { CRYPTOGRPAHY: _cryptography_decrypt, - CRYPTODOME: _cryptodome_decrypt, - None: raise_backend_error + CRYPTODOME: _cryptodome_decrypt } _ENCRYPT_MAP = { CRYPTOGRPAHY: _cryptography_encrypt, - CRYPTODOME: _cryptodome_encrypt, - None: raise_backend_error + CRYPTODOME: _cryptodome_encrypt } def generic_encrypt(cipher_factory_map, plaintext, key, iv): - """""" + """Encrypt data using the available backend. + + :param dict cipher_factory_map: Dictionary that maps the backend name to a cipher factory + callable for that backend + :param bytes plaintext: Plaintext data to encrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Encrypted ciphertext + :rtype: bytes + """ + if backend is None: + raise error.StatusInformation( + errorIndication=errind.encryptionError + ) return _ENCRYPT_MAP[backend](cipher_factory_map[backend], plaintext, key, iv) -def generic_decrypt(cipher_factory_map, plaintext, key, iv): - """""" - return _DECRYPT_MAP[backend](cipher_factory_map[backend], plaintext, key, iv) +def generic_decrypt(cipher_factory_map, ciphertext, key, iv): + """Decrypt data using the available backend. + + :param dict cipher_factory_map: Dictionary that maps the backend name to a cipher factory + callable for that backend + :param bytes ciphertext: Ciphertext data to decrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Decrypted plaintext + :rtype: bytes + """ + if backend is None: + raise error.StatusInformation( + errorIndication=errind.decryptionError + ) + return _DECRYPT_MAP[backend](cipher_factory_map[backend], ciphertext, key, iv) diff --git a/pysnmp/crypto/aes.py b/pysnmp/crypto/aes.py index 44ed831a..0bb048c8 100644 --- a/pysnmp/crypto/aes.py +++ b/pysnmp/crypto/aes.py @@ -1,5 +1,9 @@ -"""""" -from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, generic_decrypt, generic_encrypt, raise_backend_error +""" +Crypto logic for RFC3826. + +https://tools.ietf.org/html/rfc3826 +""" +from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, generic_decrypt, generic_encrypt if backend == CRYPTOGRPAHY: from cryptography.hazmat.backends import default_backend @@ -9,12 +13,23 @@ elif backend == CRYPTODOME: def _cryptodome_cipher(key, iv): - """""" + """Build a Pycryptodome AES Cipher object. + + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: DES3 Cipher instance + """ return AES.new(key, AES.MODE_CFB, iv, segment_size=128) def _cryptography_cipher(key, iv): - """""" + """Build a cryptography AES Cipher object. + + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: TripleDES Cipher instance + :rtype: cryptography.hazmat.primitives.ciphers.Cipher + """ return Cipher( algorithm=algorithms.AES(key), mode=modes.CFB(iv), @@ -24,16 +39,29 @@ def _cryptography_cipher(key, iv): _CIPHER_FACTORY_MAP = { CRYPTOGRPAHY: _cryptography_cipher, - CRYPTODOME: _cryptodome_cipher, - None: raise_backend_error + CRYPTODOME: _cryptodome_cipher } def encrypt(plaintext, key, iv): - """""" + """Encrypt data using AES on the available backend. + + :param bytes plaintext: Plaintext data to encrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Encrypted ciphertext + :rtype: bytes + """ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv) -def decrypt(plaintext, key, iv): - """""" - return generic_decrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv) +def decrypt(ciphertext, key, iv): + """Decrypt data using AES on the available backend. + + :param bytes ciphertext: Ciphertext data to decrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Decrypted plaintext + :rtype: bytes + """ + return generic_decrypt(_CIPHER_FACTORY_MAP, ciphertext, key, iv) diff --git a/pysnmp/crypto/des.py b/pysnmp/crypto/des.py index 1e6aba01..5dc368b3 100644 --- a/pysnmp/crypto/des.py +++ b/pysnmp/crypto/des.py @@ -1,30 +1,59 @@ -"""""" -from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, des3, generic_decrypt, generic_encrypt, raise_backend_error +""" +Crypto logic for RFC3414. + +https://tools.ietf.org/html/rfc3414 +""" +from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, des3, generic_decrypt, generic_encrypt if backend == CRYPTODOME: from Cryptodome.Cipher import DES def _cryptodome_cipher(key, iv): - """""" + """Build a Pycryptodome DES Cipher object. + + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: DES Cipher instance + """ return DES.new(key, DES.MODE_CBC, iv) _CIPHER_FACTORY_MAP = { - CRYPTODOME: _cryptodome_cipher, - None: raise_backend_error + CRYPTODOME: _cryptodome_cipher } +# Cryptography does not support DES directly because it is a seriously old, insecure, +# and deprecated algorithm. However, triple DES is just three rounds of DES (encrypt, +# decrypt, encrypt) done by taking a key three times the size of a DES key and breaking +# it into three pieces. So triple DES with des_key * 3 is equivalent to DES. +# Pycryptodome's triple DES implementation will actually throw an error if it receives +# a key that reduces to DES. + def encrypt(plaintext, key, iv): - """""" + """Encrypt data using DES on the available backend. + + :param bytes plaintext: Plaintext data to encrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Encrypted ciphertext + :rtype: bytes + """ if backend == CRYPTOGRPAHY: return des3.encrypt(plaintext, key * 3, iv) return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv) -def decrypt(plaintext, key, iv): - """""" +def decrypt(ciphertext, key, iv): + """Decrypt data using DES on the available backend. + + :param bytes ciphertext: Ciphertext data to decrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Decrypted plaintext + :rtype: bytes + """ if backend == CRYPTOGRPAHY: - return des3.decrypt(plaintext, key * 3, iv) - return generic_decrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv) + return des3.decrypt(ciphertext, key * 3, iv) + return generic_decrypt(_CIPHER_FACTORY_MAP, ciphertext, key, iv) diff --git a/pysnmp/crypto/des3.py b/pysnmp/crypto/des3.py index 2f9b8d6e..481fe6a4 100644 --- a/pysnmp/crypto/des3.py +++ b/pysnmp/crypto/des3.py @@ -1,5 +1,9 @@ -"""""" -from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, generic_decrypt, generic_encrypt, raise_backend_error +""" +Crypto logic for Reeder 3DES-EDE for USM (Internet draft). + +https://tools.ietf.org/html/draft-reeder-snmpv3-usm-3desede-00 +""" +from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, generic_decrypt, generic_encrypt if backend == CRYPTOGRPAHY: from cryptography.hazmat.backends import default_backend @@ -9,12 +13,23 @@ elif backend == CRYPTODOME: def _cryptodome_cipher(key, iv): - """""" + """Build a Pycryptodome DES3 Cipher object. + + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: DES3 Cipher instance + """ return DES3.new(key, DES3.MODE_CBC, iv) def _cryptography_cipher(key, iv): - """""" + """Build a cryptography TripleDES Cipher object. + + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: TripleDES Cipher instance + :rtype: cryptography.hazmat.primitives.ciphers.Cipher + """ return Cipher( algorithm=algorithms.TripleDES(key), mode=modes.CBC(iv), @@ -24,16 +39,29 @@ def _cryptography_cipher(key, iv): _CIPHER_FACTORY_MAP = { CRYPTOGRPAHY: _cryptography_cipher, - CRYPTODOME: _cryptodome_cipher, - None: raise_backend_error + CRYPTODOME: _cryptodome_cipher } def encrypt(plaintext, key, iv): - """""" + """Encrypt data using triple DES on the available backend. + + :param bytes plaintext: Plaintext data to encrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Encrypted ciphertext + :rtype: bytes + """ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv) -def decrypt(plaintext, key, iv): - """""" - return generic_decrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv) +def decrypt(ciphertext, key, iv): + """Decrypt data using triple DES on the available backend. + + :param bytes ciphertext: Ciphertext data to decrypt + :param bytes key: Encryption key + :param bytes IV: Initialization vector + :returns: Decrypted plaintext + :rtype: bytes + """ + return generic_decrypt(_CIPHER_FACTORY_MAP, ciphertext, key, iv) diff --git a/pysnmp/proto/secmod/eso/priv/des3.py b/pysnmp/proto/secmod/eso/priv/des3.py index 2edfa7a7..bf39a8ff 100644 --- a/pysnmp/proto/secmod/eso/priv/des3.py +++ b/pysnmp/proto/secmod/eso/priv/des3.py @@ -5,7 +5,7 @@ # License: http://snmplabs.com/pysnmp/license.html # import random -from pysnmp.crypto.des3 import decrypt, encrypt +from pysnmp.crypto import des3 from pysnmp.proto.secmod.rfc3414.priv import base from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha from pysnmp.proto.secmod.rfc3414 import localkey @@ -117,7 +117,7 @@ class Des3(base.AbstractEncryptionService): privParameters = univ.OctetString(salt) plaintext = dataToEncrypt + univ.OctetString((0,) * (8 - len(dataToEncrypt) % 8)).asOctets() - ciphertext = encrypt(plaintext, des3Key, iv) + ciphertext = des3.encrypt(plaintext, des3Key, iv) return univ.OctetString(ciphertext), privParameters @@ -138,6 +138,6 @@ class Des3(base.AbstractEncryptionService): ) ciphertext = encryptedData.asOctets() - plaintext = decrypt(ciphertext, des3Key, iv) + plaintext = des3.decrypt(ciphertext, des3Key, iv) return plaintext diff --git a/pysnmp/proto/secmod/rfc3414/priv/des.py b/pysnmp/proto/secmod/rfc3414/priv/des.py index 7a46e2af..b874162a 100644 --- a/pysnmp/proto/secmod/rfc3414/priv/des.py +++ b/pysnmp/proto/secmod/rfc3414/priv/des.py @@ -5,7 +5,7 @@ # License: http://snmplabs.com/pysnmp/license.html # import random -from pysnmp.crypto.des import decrypt, encrypt +from pysnmp.crypto import des from pysnmp.proto.secmod.rfc3414.priv import base from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha from pysnmp.proto.secmod.rfc3414 import localkey @@ -107,7 +107,7 @@ class Des(base.AbstractEncryptionService): # 8.1.1.2 plaintext = dataToEncrypt + univ.OctetString((0,) * (8 - len(dataToEncrypt) % 8)).asOctets() - ciphertext = encrypt(plaintext, desKey, iv) + ciphertext = des.encrypt(plaintext, desKey, iv) # 8.3.1.3 & 4 return univ.OctetString(ciphertext), privParameters @@ -134,4 +134,4 @@ class Des(base.AbstractEncryptionService): ) # 8.3.2.6 - return decrypt(encryptedData.asOctets(), desKey, iv) + return des.decrypt(encryptedData.asOctets(), desKey, iv) diff --git a/pysnmp/proto/secmod/rfc3826/priv/aes.py b/pysnmp/proto/secmod/rfc3826/priv/aes.py index 6ee351ce..82fa0da5 100644 --- a/pysnmp/proto/secmod/rfc3826/priv/aes.py +++ b/pysnmp/proto/secmod/rfc3826/priv/aes.py @@ -6,7 +6,7 @@ # import random from pyasn1.type import univ -from pysnmp.crypto.aes import decrypt, encrypt +from pysnmp.crypto import aes from pysnmp.proto.secmod.rfc3414.priv import base from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha from pysnmp.proto.secmod.rfc7860.auth import hmacsha2 @@ -110,7 +110,7 @@ class Aes(base.AbstractEncryptionService): # PyCrypto seems to require padding dataToEncrypt = dataToEncrypt + univ.OctetString((0,) * (16 - len(dataToEncrypt) % 16)).asOctets() - ciphertext = encrypt(dataToEncrypt, aesKey, iv) + ciphertext = aes.encrypt(dataToEncrypt, aesKey, iv) # 3.3.1.4 return univ.OctetString(ciphertext), univ.OctetString(salt) @@ -134,4 +134,4 @@ class Aes(base.AbstractEncryptionService): encryptedData = encryptedData + univ.OctetString((0,) * (16 - len(encryptedData) % 16)).asOctets() # 3.3.2.4-6 - return decrypt(encryptedData.asOctets(), aesKey, iv) + return aes.decrypt(encryptedData.asOctets(), aesKey, iv) |