summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormattsb42-aws <bullocm@amazon.com>2018-02-06 23:22:48 -0800
committermattsb42-aws <bullocm@amazon.com>2018-02-09 11:12:29 -0800
commit9306d4b04006b52772208b2b84ad6117f9ee7288 (patch)
treeef0816afdfa2f439b91d4466866ea4ef2ab287c4
parent1fba74577361e941151c46f2c9b23923a23b1a0a (diff)
downloadpysnmp-git-9306d4b04006b52772208b2b84ad6117f9ee7288.tar.gz
initial migration to backend-selecting crypto
-rw-r--r--pysnmp/crypto/__init__.py67
-rw-r--r--pysnmp/crypto/aes.py39
-rw-r--r--pysnmp/crypto/des.py30
-rw-r--r--pysnmp/crypto/des3.py39
-rw-r--r--pysnmp/proto/secmod/eso/priv/des3.py24
-rw-r--r--pysnmp/proto/secmod/rfc3414/priv/des.py22
-rw-r--r--pysnmp/proto/secmod/rfc3826/priv/aes.py23
-rw-r--r--requirements.txt6
-rw-r--r--setup.py11
9 files changed, 198 insertions, 63 deletions
diff --git a/pysnmp/crypto/__init__.py b/pysnmp/crypto/__init__.py
new file mode 100644
index 00000000..1ed4488d
--- /dev/null
+++ b/pysnmp/crypto/__init__.py
@@ -0,0 +1,67 @@
+"""Backend-independent cryptographic implementations to allow migration to pyca/cryptography
+without immediately dropping support for legacy minor Python versions.
+"""
+from pysnmp.proto import errind, error
+CRYPTOGRPAHY = 'cryptography'
+CRYPTODOME = 'Cryptodome'
+try:
+ import cryptography
+ backend = CRYPTOGRPAHY
+except ImportError:
+ try:
+ import Cryptodome
+ backend = CRYPTODOME
+ except ImportError:
+ backend = None
+
+
+def raise_backend_error(*args, **kwargs):
+ raise error.StatusInformation(
+ errorIndication=errind.decryptionError
+ )
+
+
+def _cryptodome_encrypt(cipher_factory, plaintext, key, iv):
+ """"""
+ encryptor = cipher_factory(key, iv)
+ return encryptor.encrypt(plaintext)
+
+
+def _cryptodome_decrypt(cipher_factory, ciphertext, key, iv):
+ """"""
+ decryptor = cipher_factory(key, iv)
+ return decryptor.decrypt(ciphertext)
+
+
+def _cryptography_encrypt(cipher_factory, plaintext, key, iv):
+ """"""
+ encryptor = cipher_factory(key, iv).encryptor()
+ return encryptor.update(plaintext) + encryptor.finalize()
+
+
+def _cryptography_decrypt(cipher_factory, ciphertext, key, iv):
+ """"""
+ decryptor = cipher_factory(key, iv).decryptor()
+ return decryptor.update(ciphertext) + decryptor.finalize()
+
+
+_DECRYPT_MAP = {
+ CRYPTOGRPAHY: _cryptography_decrypt,
+ CRYPTODOME: _cryptodome_decrypt,
+ None: raise_backend_error
+}
+_ENCRYPT_MAP = {
+ CRYPTOGRPAHY: _cryptography_encrypt,
+ CRYPTODOME: _cryptodome_encrypt,
+ None: raise_backend_error
+}
+
+
+def generic_encrypt(cipher_factory_map, plaintext, key, iv):
+ """"""
+ 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)
diff --git a/pysnmp/crypto/aes.py b/pysnmp/crypto/aes.py
new file mode 100644
index 00000000..44ed831a
--- /dev/null
+++ b/pysnmp/crypto/aes.py
@@ -0,0 +1,39 @@
+""""""
+from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, generic_decrypt, generic_encrypt, raise_backend_error
+
+if backend == CRYPTOGRPAHY:
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
+elif backend == CRYPTODOME:
+ from Cryptodome.Cipher import AES
+
+
+def _cryptodome_cipher(key, iv):
+ """"""
+ return AES.new(key, AES.MODE_CFB, iv, segment_size=128)
+
+
+def _cryptography_cipher(key, iv):
+ """"""
+ return Cipher(
+ algorithm=algorithms.AES(key),
+ mode=modes.CFB(iv),
+ backend=default_backend()
+ )
+
+
+_CIPHER_FACTORY_MAP = {
+ CRYPTOGRPAHY: _cryptography_cipher,
+ CRYPTODOME: _cryptodome_cipher,
+ None: raise_backend_error
+}
+
+
+def encrypt(plaintext, key, iv):
+ """"""
+ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
+
+
+def decrypt(plaintext, key, iv):
+ """"""
+ return generic_decrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
diff --git a/pysnmp/crypto/des.py b/pysnmp/crypto/des.py
new file mode 100644
index 00000000..1e6aba01
--- /dev/null
+++ b/pysnmp/crypto/des.py
@@ -0,0 +1,30 @@
+""""""
+from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, des3, generic_decrypt, generic_encrypt, raise_backend_error
+
+if backend == CRYPTODOME:
+ from Cryptodome.Cipher import DES
+
+
+def _cryptodome_cipher(key, iv):
+ """"""
+ return DES.new(key, DES.MODE_CBC, iv)
+
+
+_CIPHER_FACTORY_MAP = {
+ CRYPTODOME: _cryptodome_cipher,
+ None: raise_backend_error
+}
+
+
+def encrypt(plaintext, key, iv):
+ """"""
+ if backend == CRYPTOGRPAHY:
+ return des3.encrypt(plaintext, key * 3, iv)
+ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
+
+
+def decrypt(plaintext, key, iv):
+ """"""
+ if backend == CRYPTOGRPAHY:
+ return des3.decrypt(plaintext, key * 3, iv)
+ return generic_decrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
diff --git a/pysnmp/crypto/des3.py b/pysnmp/crypto/des3.py
new file mode 100644
index 00000000..2f9b8d6e
--- /dev/null
+++ b/pysnmp/crypto/des3.py
@@ -0,0 +1,39 @@
+""""""
+from pysnmp.crypto import backend, CRYPTODOME, CRYPTOGRPAHY, generic_decrypt, generic_encrypt, raise_backend_error
+
+if backend == CRYPTOGRPAHY:
+ from cryptography.hazmat.backends import default_backend
+ from cryptography.hazmat.primitives.ciphers import algorithms, Cipher, modes
+elif backend == CRYPTODOME:
+ from Cryptodome.Cipher import DES3
+
+
+def _cryptodome_cipher(key, iv):
+ """"""
+ return DES3.new(key, DES3.MODE_CBC, iv)
+
+
+def _cryptography_cipher(key, iv):
+ """"""
+ return Cipher(
+ algorithm=algorithms.TripleDES(key),
+ mode=modes.CBC(iv),
+ backend=default_backend()
+ )
+
+
+_CIPHER_FACTORY_MAP = {
+ CRYPTOGRPAHY: _cryptography_cipher,
+ CRYPTODOME: _cryptodome_cipher,
+ None: raise_backend_error
+}
+
+
+def encrypt(plaintext, key, iv):
+ """"""
+ return generic_encrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
+
+
+def decrypt(plaintext, key, iv):
+ """"""
+ return generic_decrypt(_CIPHER_FACTORY_MAP, plaintext, key, iv)
diff --git a/pysnmp/proto/secmod/eso/priv/des3.py b/pysnmp/proto/secmod/eso/priv/des3.py
index 426df633..2edfa7a7 100644
--- a/pysnmp/proto/secmod/eso/priv/des3.py
+++ b/pysnmp/proto/secmod/eso/priv/des3.py
@@ -5,6 +5,7 @@
# License: http://snmplabs.com/pysnmp/license.html
#
import random
+from pysnmp.crypto.des3 import decrypt, encrypt
from pysnmp.proto.secmod.rfc3414.priv import base
from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha
from pysnmp.proto.secmod.rfc3414 import localkey
@@ -12,7 +13,6 @@ from pysnmp.proto.secmod.rfc7860.auth import hmacsha2
from pysnmp.proto import errind, error
from pyasn1.type import univ
from pyasn1.compat.octets import null
-from math import ceil
try:
from hashlib import md5, sha1
@@ -23,11 +23,6 @@ except ImportError:
md5 = md5.new
sha1 = sha.new
-try:
- from Cryptodome.Cipher import DES3
-except ImportError:
- DES3 = None
-
random.seed()
@@ -113,32 +108,21 @@ class Des3(base.AbstractEncryptionService):
# 5.1.1.2
def encryptData(self, encryptKey, privParameters, dataToEncrypt):
- if DES3 is None:
- raise error.StatusInformation(
- errorIndication=errind.encryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
des3Key, salt, iv = self.__getEncryptionKey(
encryptKey, snmpEngineBoots
)
- des3Obj = DES3.new(des3Key, DES3.MODE_CBC, iv)
-
privParameters = univ.OctetString(salt)
plaintext = dataToEncrypt + univ.OctetString((0,) * (8 - len(dataToEncrypt) % 8)).asOctets()
- ciphertext = des3Obj.encrypt(plaintext)
+ ciphertext = encrypt(plaintext, des3Key, iv)
return univ.OctetString(ciphertext), privParameters
# 5.1.1.3
def decryptData(self, decryptKey, privParameters, encryptedData):
- if DES3 is None:
- raise error.StatusInformation(
- errorIndication=errind.decryptionError
- )
snmpEngineBoots, snmpEngineTime, salt = privParameters
if len(salt) != 8:
@@ -153,9 +137,7 @@ class Des3(base.AbstractEncryptionService):
errorIndication=errind.decryptionError
)
- des3Obj = DES3.new(des3Key, DES3.MODE_CBC, iv)
-
ciphertext = encryptedData.asOctets()
- plaintext = des3Obj.decrypt(ciphertext)
+ plaintext = 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 b66889e2..7a46e2af 100644
--- a/pysnmp/proto/secmod/rfc3414/priv/des.py
+++ b/pysnmp/proto/secmod/rfc3414/priv/des.py
@@ -5,6 +5,7 @@
# License: http://snmplabs.com/pysnmp/license.html
#
import random
+from pysnmp.crypto.des import decrypt, encrypt
from pysnmp.proto.secmod.rfc3414.priv import base
from pysnmp.proto.secmod.rfc3414.auth import hmacmd5, hmacsha
from pysnmp.proto.secmod.rfc3414 import localkey
@@ -14,10 +15,6 @@ from pyasn1.type import univ
from sys import version_info
try:
- from Cryptodome.Cipher import DES
-except ImportError:
- DES = None
-try:
from hashlib import md5, sha1
except ImportError:
import md5
@@ -98,11 +95,6 @@ class Des(base.AbstractEncryptionService):
# 8.2.4.1
def encryptData(self, encryptKey, privParameters, dataToEncrypt):
- if DES is None:
- raise error.StatusInformation(
- errorIndication=errind.encryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 8.3.1.1
@@ -114,20 +106,14 @@ class Des(base.AbstractEncryptionService):
privParameters = univ.OctetString(salt)
# 8.1.1.2
- desObj = DES.new(desKey, DES.MODE_CBC, iv)
plaintext = dataToEncrypt + univ.OctetString((0,) * (8 - len(dataToEncrypt) % 8)).asOctets()
- ciphertext = desObj.encrypt(plaintext)
+ ciphertext = encrypt(plaintext, desKey, iv)
# 8.3.1.3 & 4
return univ.OctetString(ciphertext), privParameters
# 8.2.4.2
def decryptData(self, decryptKey, privParameters, encryptedData):
- if DES is None:
- raise error.StatusInformation(
- errorIndication=errind.decryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 8.3.2.1
@@ -147,7 +133,5 @@ class Des(base.AbstractEncryptionService):
errorIndication=errind.decryptionError
)
- desObj = DES.new(desKey, DES.MODE_CBC, iv)
-
# 8.3.2.6
- return desObj.decrypt(encryptedData.asOctets())
+ return decrypt(encryptedData.asOctets(), desKey, iv)
diff --git a/pysnmp/proto/secmod/rfc3826/priv/aes.py b/pysnmp/proto/secmod/rfc3826/priv/aes.py
index c702a418..6ee351ce 100644
--- a/pysnmp/proto/secmod/rfc3826/priv/aes.py
+++ b/pysnmp/proto/secmod/rfc3826/priv/aes.py
@@ -6,6 +6,7 @@
#
import random
from pyasn1.type import univ
+from pysnmp.crypto.aes import decrypt, encrypt
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
@@ -13,10 +14,6 @@ from pysnmp.proto.secmod.rfc3414 import localkey
from pysnmp.proto import errind, error
try:
- from Cryptodome.Cipher import AES
-except ImportError:
- AES = None
-try:
from hashlib import md5, sha1
except ImportError:
import md5
@@ -102,11 +99,6 @@ class Aes(base.AbstractEncryptionService):
# 3.2.4.1
def encryptData(self, encryptKey, privParameters, dataToEncrypt):
- if AES is None:
- raise error.StatusInformation(
- errorIndication=errind.encryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 3.3.1.1
@@ -115,23 +107,16 @@ class Aes(base.AbstractEncryptionService):
)
# 3.3.1.3
- aesObj = AES.new(aesKey, AES.MODE_CFB, iv, segment_size=128)
-
# PyCrypto seems to require padding
dataToEncrypt = dataToEncrypt + univ.OctetString((0,) * (16 - len(dataToEncrypt) % 16)).asOctets()
- ciphertext = aesObj.encrypt(dataToEncrypt)
+ ciphertext = encrypt(dataToEncrypt, aesKey, iv)
# 3.3.1.4
return univ.OctetString(ciphertext), univ.OctetString(salt)
# 3.2.4.2
def decryptData(self, decryptKey, privParameters, encryptedData):
- if AES is None:
- raise error.StatusInformation(
- errorIndication=errind.decryptionError
- )
-
snmpEngineBoots, snmpEngineTime, salt = privParameters
# 3.3.2.1
@@ -145,10 +130,8 @@ class Aes(base.AbstractEncryptionService):
decryptKey, snmpEngineBoots, snmpEngineTime, salt
)
- aesObj = AES.new(aesKey, AES.MODE_CFB, iv, segment_size=128)
-
# PyCrypto seems to require padding
encryptedData = encryptedData + univ.OctetString((0,) * (16 - len(encryptedData) % 16)).asOctets()
# 3.3.2.4-6
- return aesObj.decrypt(encryptedData.asOctets())
+ return decrypt(encryptedData.asOctets(), aesKey, iv)
diff --git a/requirements.txt b/requirements.txt
index d90ebe40..dfbec048 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,7 @@
pysmi
-pycryptodomex
+pycryptodomex; python_version < '2.7'
+cryptography; python_version == '2.7'
+pycryptodomex; python_version == '3.2'
+pycryptodomex; python_version == '3.3'
+cryptography; python_version >= '3.4'
pyasn1>=0.2.3
diff --git a/setup.py b/setup.py
index 0fac0fca..4313d08f 100644
--- a/setup.py
+++ b/setup.py
@@ -54,11 +54,17 @@ if sys.version_info[:2] < (2, 4):
print("ERROR: this package requires Python 2.4 or later!")
sys.exit(1)
+py_version = sys.version_info[:2]
+if py_version < (2, 7) or (py_version >= (3, 0) and py_version < (3, 4)):
+ crypto_lib = 'pycryptodomex'
+else:
+ crypto_lib = 'cryptography'
+
try:
from setuptools import setup
params = {
- 'install_requires': ['pyasn1>=0.2.3', 'pysmi', 'pycryptodomex'],
+ 'install_requires': ['pyasn1>=0.2.3', 'pysmi', crypto_lib],
'zip_safe': True
}
@@ -72,7 +78,7 @@ except ImportError:
params = {}
if sys.version_info[:2] > (2, 4):
- params['requires'] = ['pyasn1(>=0.2.3)', 'pysmi', 'pycryptodomex']
+ params['requires'] = ['pyasn1(>=0.2.3)', 'pysmi', crypto_lib]
doclines = [x.strip() for x in (__doc__ or '').split('\n') if x]
@@ -101,6 +107,7 @@ params.update({
'pysnmp.carrier.twisted.dgram',
'pysnmp.carrier.asyncio',
'pysnmp.carrier.asyncio.dgram',
+ 'pysnmp.crypto',
'pysnmp.entity',
'pysnmp.entity.rfc3413',
'pysnmp.entity.rfc3413.oneliner',