summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pysnmp/crypto/__init__.py102
-rw-r--r--pysnmp/crypto/aes.py48
-rw-r--r--pysnmp/crypto/des.py49
-rw-r--r--pysnmp/crypto/des3.py48
-rw-r--r--pysnmp/proto/secmod/eso/priv/des3.py6
-rw-r--r--pysnmp/proto/secmod/rfc3414/priv/des.py6
-rw-r--r--pysnmp/proto/secmod/rfc3826/priv/aes.py6
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)