├── .gitattributes ├── .gitignore ├── DEVELOPER.md ├── LICENSE.txt ├── MANIFEST ├── MANIFEST.in ├── README.md ├── jasypt4py ├── __init__.py ├── encryptor.py ├── exceptions.py └── generator.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_encryptor.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # force all files to LF endings 2 | *.yml text eol=lf 3 | *.md text eol=lf 4 | *.sh text eol=lf 5 | *.xml text eol=lf 6 | *.j2 text eol=lf 7 | *.py text eol=lf 8 | *.cfg text eol=lf 9 | *.txt text eol=lf 10 | *.in text eol=lf 11 | *.sql text eol=lf 12 | *.java text eol=lf 13 | .gitignore text eol=lf 14 | .gitattributes text eol=lf 15 | MAINFEST text eol=lf 16 | 17 | # only windoze files get CRLF endings 18 | *.bat text eol=crlf 19 | 20 | # ignore binary 21 | *.jar binary 22 | *.zip binary 23 | *.tar.gz binary -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | *.pyc 4 | *.class 5 | dist/ 6 | build/ 7 | tmp/ 8 | .eggs/ 9 | jasypt4py.egg-info/ 10 | .env/ 11 | -------------------------------------------------------------------------------- /DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Jasypt for Python Developer Notes 2 | 3 | ### Testing 4 | 5 | Run the unit tests using the python built in unittest runner: 6 | 7 | ```sh 8 | python -m unittest discover 9 | ``` 10 | 11 | or use nose testing: 12 | 13 | ```sh 14 | python setup.py nosetests 15 | ``` 16 | 17 | ### Build and Release 18 | 19 | This project uses the standard python setup mechanism. To build a distributable package simply use: 20 | 21 | ```sh 22 | python setup.py sdist --formats=gztar bdist_wheel 23 | ``` 24 | 25 | Optionally sign the artifacts: 26 | 27 | ```sh 28 | for f in dist/*.{gz,whl}; do 29 | gpg --detach-sign -a $f 30 | done 31 | ``` 32 | 33 | Uploading to pypi only works with a Cpython interpreter and also requires twine. Also make sure you have a valid `~/.pypirc` configuration file. This example should work: 34 | 35 | ```ini 36 | [distutils] 37 | index-servers = 38 | pypi 39 | pypitest 40 | 41 | [pypi] 42 | username:[your-username] 43 | password:[your-password] 44 | 45 | [pypitest] 46 | repository: https://test.pypi.org/legacy/ 47 | username:[your-username] 48 | password:[your-password] 49 | ``` 50 | 51 | And upload all resources: 52 | 53 | ```sh 54 | pip install -U twine 55 | twine upload -r pypitest dist/* 56 | ``` -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016-2017 Niels Bertram 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | setup.py 2 | jasypt4py\__init__.py 3 | jasypt4py\generator.py 4 | jasypt4py\encryptor.py 5 | jasypt4py\exceptions.py -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the license file 2 | include LICENSE.txt 3 | 4 | # Include the data files 5 | #recursive-include data * -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jasypt for Python 2 | 3 | A python module that produces Jasypt/Bouncycastle compatible hashes and encrypted passwords. 4 | 5 | ### Prerequisites 6 | 7 | Any python environment that can run `pycrypto`. 8 | 9 | ### Installation 10 | 11 | Download the packaged [jasypt4py module](https://github.com/fareliner/jasypt4py/releases/latest) and install it with pip. 12 | 13 | Example `pip` Installation: 14 | 15 | ```sh 16 | pip install -U jasypt4py 17 | ``` 18 | 19 | ### Limitations 20 | 21 | Currently only supports `PBEWITHSHA256AND256BITAES-CBC-BC` and `PBEWITHSHA256AND128BITAES-CBC-BC` from Jasypt/Bouncycastle. 22 | 23 | ### Usage 24 | 25 | See tests. 26 | 27 | #### Encryption 28 | 29 | Jasypt encryption function: 30 | 31 | ```sh 32 | $JASYPT_HOME/bin/encrypt.sh providerClassName = "org.bouncycastle.jce.provider.BouncyCastleProvider" \ 33 | saltGeneratorClassName = "org.jasypt.salt.RandomSaltGenerator" \ 34 | algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC" \ 35 | password = 'pssst...don\'t tell anyone' \ 36 | keyObtentionIterations = 4000 \ 37 | input = 'secret value' 38 | ``` 39 | 40 | is equivalent to: 41 | 42 | ```python 43 | from jasypt4py import StandardPBEStringEncryptor 44 | 45 | cryptor = StandardPBEStringEncryptor('PBEWITHSHA256AND256BITAES-CBC') 46 | 47 | cryptor.encrypt('pssst...don\'t tell anyone', 'secret value', 4000) 48 | ``` 49 | 50 | #### Decryption 51 | 52 | Jasypt decryption function: 53 | 54 | ```sh 55 | $JASYPT_HOME/bin/decrypt.sh providerClassName = "org.bouncycastle.jce.provider.BouncyCastleProvider" \ 56 | saltGeneratorClassName = "org.jasypt.salt.RandomSaltGenerator" \ 57 | algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC" \ 58 | password = 'pssst...don\'t tell anyone' \ 59 | keyObtentionIterations = 4000 \ 60 | input = 'xgX5+yRbKhs4zSubkAPkg9gSBkZU6XWt7csceM/3xDY=' 61 | ``` 62 | 63 | is equivalent to: 64 | 65 | ```python 66 | from jasypt4py import StandardPBEStringEncryptor 67 | 68 | cryptor = StandardPBEStringEncryptor('PBEWITHSHA256AND256BITAES-CBC') 69 | 70 | cryptor.decrypt('pssst...don\'t tell anyone', 'xgX5+yRbKhs4zSubkAPkg9gSBkZU6XWt7csceM/3xDY=', 4000) 71 | ``` 72 | -------------------------------------------------------------------------------- /jasypt4py/__init__.py: -------------------------------------------------------------------------------- 1 | # Make coding more python3-ish 2 | from __future__ import (absolute_import, division, print_function) 3 | 4 | from jasypt4py.encryptor import StandardPBEStringEncryptor 5 | 6 | __metaclass__ = type 7 | -------------------------------------------------------------------------------- /jasypt4py/encryptor.py: -------------------------------------------------------------------------------- 1 | # Make coding more python3-ish 2 | from __future__ import (absolute_import, division, print_function) 3 | 4 | import sys 5 | from abc import ABCMeta 6 | from base64 import b64encode, b64decode 7 | from Crypto.Cipher import AES 8 | from Crypto.Hash import SHA256 9 | 10 | from jasypt4py.generator import PKCS12ParameterGenerator, RandomSaltGenerator, FixedSaltGenerator 11 | 12 | PY2 = sys.version_info[0] == 2 13 | PY3 = sys.version_info[0] == 3 14 | 15 | # an encode function that takes a byte array and returns an encoded python string 16 | if PY2: 17 | str_encode = lambda s: str(s) 18 | elif PY3: 19 | str_encode = lambda s: str(s, 'utf-8') 20 | 21 | 22 | class StandardPBEStringEncryptor(object): 23 | __metaclass__ = ABCMeta 24 | 25 | def __init__(self, algorithm, salt_generator='Random', **kwargs): 26 | 27 | if salt_generator == 'Random': 28 | self.salt_generator = RandomSaltGenerator(**kwargs) 29 | elif salt_generator == 'Fixed': 30 | self.salt_generator = FixedSaltGenerator(**kwargs) 31 | else: 32 | raise NotImplementedError('Salt generator %s is not implemented' % salt_generator) 33 | 34 | # setup the generators and cipher 35 | if algorithm == 'PBEWITHSHA256AND256BITAES-CBC': 36 | 37 | # create sha256 PKCS12 secret generator 38 | self.key_generator = PKCS12ParameterGenerator(SHA256) 39 | 40 | # setup the AES cipher 41 | self._cipher_factory = AES.new 42 | self._cipher_mode = AES.MODE_CBC 43 | elif algorithm == 'PBEWITHSHA256AND128BITAES-CBC': 44 | 45 | # create sha256 PKCS12 secret generator 46 | self.key_generator = PKCS12ParameterGenerator(SHA256, key_size_bits=PKCS12ParameterGenerator.KEY_SIZE_128) 47 | 48 | # setup the AES cipher 49 | self._cipher_factory = AES.new 50 | self._cipher_mode = AES.MODE_CBC 51 | 52 | else: 53 | raise NotImplementedError('Algorithm %s is not implemented' % algorithm) 54 | 55 | @staticmethod 56 | def pad(block_size, s): 57 | """ 58 | Pad a string to the provided block size when using fixed block ciphers. 59 | 60 | :param block_size: int - the cipher block size 61 | :param s: str - the string to pad 62 | :return: a padded string that can be fed to the cipher 63 | """ 64 | return s + (block_size - len(s) % block_size) * chr(block_size - len(s) % block_size) 65 | 66 | @staticmethod 67 | def unpad(s): 68 | """ 69 | Remove padding from the string after decryption when using fixed block ciphers. 70 | 71 | :param s: str - the string to remove padding from 72 | :return: the unpadded string 73 | """ 74 | if PY2: 75 | return s[0:-ord(s[-1])] 76 | elif PY3: 77 | return s[0:-s[-1]] 78 | else: 79 | raise ImportError('Only Python 2 and 3 are supported') 80 | 81 | def encrypt(self, password, text, iterations=1000): 82 | 83 | # generate a 16 byte salt which is used to generate key material and iv 84 | salt = self.salt_generator.generate_salt() 85 | 86 | # generate key material 87 | key, iv = self.key_generator.generate_derived_parameters(password, salt, iterations) 88 | 89 | # setup AES cipher 90 | cipher = self._cipher_factory(key, self._cipher_mode, iv) 91 | 92 | # pad the plain text secret to AES block size 93 | encrypted_message = cipher.encrypt(self.pad(AES.block_size, text)) 94 | 95 | # concatenate salt + encrypted message 96 | return str_encode(b64encode(bytes(salt) + encrypted_message)) 97 | 98 | def decrypt(self, password, ciphertext, iterations=1000): 99 | 100 | # decode the base64 encoded and encrypted secret 101 | n_cipher_bytes = b64decode(ciphertext) 102 | 103 | # extract salt bytes 0 - SALT_SIZE 104 | salt = n_cipher_bytes[:self.salt_generator.salt_block_size] 105 | # print('dec-salt = %s' % binascii.hexlify(salt)) 106 | 107 | # create reverse key material 108 | key, iv = self.key_generator.generate_derived_parameters(password, salt, iterations) 109 | 110 | cipher = self._cipher_factory(key, self._cipher_mode, iv) 111 | 112 | # extract encrypted message bytes SALT_SIZE - len(cipher) 113 | n_cipher_message = n_cipher_bytes[self.salt_generator.salt_block_size:] 114 | 115 | # decode the message and unpad 116 | decoded = cipher.decrypt(n_cipher_message) 117 | 118 | return str_encode(self.unpad(decoded)) 119 | -------------------------------------------------------------------------------- /jasypt4py/exceptions.py: -------------------------------------------------------------------------------- 1 | # Make coding more python3-ish 2 | from __future__ import (absolute_import, division, print_function) 3 | 4 | class ArgumentError(Exception): 5 | """A problem with the supplied arguments to a class or function 6 | """ 7 | pass -------------------------------------------------------------------------------- /jasypt4py/generator.py: -------------------------------------------------------------------------------- 1 | # Make coding more python3-ish 2 | from __future__ import (absolute_import, division, print_function) 3 | 4 | from abc import ABCMeta, abstractmethod 5 | from Crypto import Random 6 | 7 | from jasypt4py.exceptions import ArgumentError 8 | 9 | 10 | class PBEParameterGenerator(object): 11 | __metaclass__ = ABCMeta 12 | 13 | @staticmethod 14 | def adjust(a, a_off, b): 15 | """ 16 | Adjusts the byte array as per PKCS12 spec 17 | 18 | :param a: byte[] - the target array 19 | :param a_off: int - offset to operate on 20 | :param b: byte[] - the bitsy array to pick from 21 | :return: nothing as operating on array by reference 22 | """ 23 | x = (b[len(b) - 1] & 0xff) + (a[a_off + len(b) - 1] & 0xff) + 1 24 | 25 | a[a_off + len(b) - 1] = x & 0xff 26 | 27 | x = x >> 8 28 | 29 | for i in range(len(b) - 2, -1, -1): 30 | x = x + (b[i] & 0xff) + (a[a_off + i] & 0xff) 31 | a[a_off + i] = x & 0xff 32 | x = x >> 8 33 | 34 | @staticmethod 35 | def pkcs12_password_to_bytes(password): 36 | """ 37 | Converts a password string to a PKCS12 v1.0 compliant byte array. 38 | 39 | :param password: byte[] - the password as simple string 40 | :return: The unsigned byte array holding the password 41 | """ 42 | pkcs12_pwd = [0x00] * (len(password) + 1) * 2 43 | 44 | for i in range(0, len(password)): 45 | digit = ord(password[i]) 46 | pkcs12_pwd[i * 2] = digit >> 8 47 | pkcs12_pwd[i * 2 + 1] = digit 48 | 49 | return bytearray(pkcs12_pwd) 50 | 51 | 52 | class PKCS12ParameterGenerator(PBEParameterGenerator): 53 | """ 54 | Equivalent of the Bouncycastle PKCS12ParameterGenerator. 55 | """ 56 | __metaclass__ = ABCMeta 57 | 58 | KEY_SIZE_256 = 256 59 | KEY_SIZE_128 = 128 60 | DEFAULT_IV_SIZE = 128 61 | 62 | KEY_MATERIAL = 1 63 | IV_MATERIAL = 2 64 | MAC_MATERIAL = 3 65 | 66 | def __init__(self, digest_factory, key_size_bits=KEY_SIZE_256, iv_size_bits=DEFAULT_IV_SIZE): 67 | """ 68 | 69 | :param digest_factory: object - the digest algoritm to use (e.g. SHA256 or MD5) 70 | :param key_size_bits: int - key size in bits 71 | :param iv_size_bits: int - iv size in bits 72 | """ 73 | super(PKCS12ParameterGenerator, self).__init__() 74 | self.digest_factory = digest_factory 75 | self.key_size_bits = key_size_bits 76 | self.iv_size_bits = iv_size_bits 77 | 78 | def generate_derived_parameters(self, password, salt, iterations=1000): 79 | """ 80 | Generates the key and iv that can be used with the cipher. 81 | 82 | :param password: str - the password used for the key material 83 | :param salt: byte[] - random salt 84 | :param iterations: int - number if hash iterations for key material 85 | 86 | :return: key and iv that can be used to setup the cipher 87 | """ 88 | key_size = (self.key_size_bits // 8) 89 | iv_size = (self.iv_size_bits // 8) 90 | 91 | # pkcs12 padded password (unicode byte array with 2 trailing 0x0 bytes) 92 | password_bytes = PKCS12ParameterGenerator.pkcs12_password_to_bytes(password) 93 | 94 | d_key = self.generate_derived_key(password_bytes, salt, iterations, self.KEY_MATERIAL, key_size) 95 | if iv_size and iv_size > 0: 96 | d_iv = self.generate_derived_key(password_bytes, salt, iterations, self.IV_MATERIAL, iv_size) 97 | else: 98 | d_iv = None 99 | return d_key, d_iv 100 | 101 | def generate_derived_key(self, password, salt, iterations, id_byte, key_size): 102 | """ 103 | Generate a derived key as per PKCS12 v1.0 spec 104 | 105 | :param password: bytearray - pkcs12 padded password (unicode byte array with 2 trailing 0x0 bytes) 106 | :param salt: bytearray - random salt 107 | :param iterations: int - number if hash iterations for key material 108 | :param id_byte: int - the material padding 109 | :param key_size: int - the key size in bytes (e.g. AES is 256/8 = 32, IV is 128/8 = 16) 110 | :return: the sha256 digested pkcs12 key 111 | """ 112 | 113 | u = int(self.digest_factory.digest_size) 114 | v = int(self.digest_factory.block_size) 115 | 116 | d_key = bytearray(key_size) 117 | 118 | # Step 1 119 | D = bytearray(v) 120 | for i in range(0, v): 121 | D[i] = id_byte 122 | 123 | # Step 2 124 | if salt and len(salt) != 0: 125 | salt_size = len(salt) 126 | s_size = v * ((salt_size + v - 1) // v) 127 | S = bytearray(s_size) 128 | 129 | for i in range(s_size): 130 | S[i] = salt[i % salt_size] 131 | else: 132 | S = bytearray(0) 133 | 134 | # Step 3 135 | if password and len(password) != 0: 136 | password_size = len(password) 137 | p_size = v * ((password_size + v - 1) // v) 138 | 139 | P = bytearray(p_size) 140 | 141 | for i in range(p_size): 142 | P[i] = password[i % password_size] 143 | else: 144 | P = bytearray(0) 145 | 146 | # Step 4 147 | I = S + P 148 | 149 | B = bytearray(v) 150 | 151 | # Step 5 152 | c = ((key_size + u - 1) // u) 153 | 154 | # Step 6 155 | for i in range(1, c + 1): 156 | # Step 6 - a 157 | digest = self.digest_factory.new() 158 | digest.update(bytes(D)) 159 | digest.update(bytes(I)) 160 | A = digest.digest() # bouncycastle now resets the digest, we will create a new digest 161 | 162 | for j in range(1, iterations): 163 | A = self.digest_factory.new(A).digest() 164 | 165 | # Step 6 - b 166 | for k in range(0, v): 167 | B[k] = A[k % u] 168 | 169 | # Step 6 - c 170 | for j in range(0, (len(I) // v)): 171 | self.adjust(I, j * v, B) 172 | 173 | if i == c: 174 | for j in range(0, key_size - ((i - 1) * u)): 175 | d_key[(i - 1) * u + j] = A[j] 176 | else: 177 | for j in range(0, u): 178 | d_key[(i - 1) * u + j] = A[j] 179 | 180 | # we string encode as Crypto functions need strings 181 | return bytes(d_key) 182 | 183 | 184 | class SaltGenerator(object): 185 | """ 186 | Base for a salt generator 187 | """ 188 | __metaclass__ = ABCMeta 189 | 190 | DEFAULT_SALT_SIZE_BYTE = 16 191 | 192 | def __init__(self, salt_block_size=DEFAULT_SALT_SIZE_BYTE): 193 | self.salt_block_size = salt_block_size 194 | 195 | @abstractmethod 196 | def generate_salt(self): 197 | pass 198 | 199 | 200 | class RandomSaltGenerator(SaltGenerator): 201 | """ 202 | A basic random salt generator 203 | """ 204 | __metaclass__ = ABCMeta 205 | 206 | def __init__(self, salt_block_size=SaltGenerator.DEFAULT_SALT_SIZE_BYTE, **kwargs): 207 | """ 208 | 209 | :param salt_block_size: the salt block size in bytes 210 | """ 211 | super(RandomSaltGenerator, self).__init__(salt_block_size) 212 | 213 | def generate_salt(self): 214 | return bytearray(Random.get_random_bytes(self.salt_block_size)) 215 | 216 | 217 | class FixedSaltGenerator(SaltGenerator): 218 | """ 219 | A fixed string salt generator 220 | """ 221 | __metaclass__ = ABCMeta 222 | 223 | def __init__(self, salt_block_size=SaltGenerator.DEFAULT_SALT_SIZE_BYTE, salt=None, **kwargs): 224 | """ 225 | 226 | :param salt_block_size: the salt block size in bytes 227 | """ 228 | super(FixedSaltGenerator, self).__init__(salt_block_size) 229 | if not salt: 230 | raise ArgumentError('salt not provided') 231 | # ensure supplied type matches 232 | if isinstance(salt, str): 233 | self.salt = bytearray(salt, 'utf-8') 234 | elif isinstance(salt, bytearray): 235 | self.salt = salt 236 | else: 237 | raise TypeError('salt must either be a string or bytearray but not %s' % type(salt)) 238 | 239 | def generate_salt(self): 240 | return self.salt 241 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pycrypto >= 2.6 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jython 2 | 3 | # 4 | # Licensed to the Apache Software Foundation (ASF) under one 5 | # or more contributor license agreements. See the NOTICE file 6 | # distributed with this work for additional information 7 | # regarding copyright ownership. The ASF licenses this file 8 | # to you under the Apache License, Version 2.0 (the 9 | # "License"); you may not use this file except in compliance 10 | # with the License. You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, 15 | # software distributed under the License is distributed on an 16 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 | # KIND, either express or implied. See the License for the 18 | # specific language governing permissions and limitations 19 | # under the License. 20 | # 21 | 22 | import sys 23 | 24 | try: 25 | from setuptools import setup, Extension 26 | except: 27 | from distutils.core import setup, Extension 28 | 29 | setup( 30 | name='jasypt4py', 31 | 32 | version='0.0.3', 33 | 34 | url='https://github.com/fareliner/jasypt4py', 35 | 36 | author='Niels Bertram', 37 | author_email='nielsbne@gmail.com', 38 | 39 | description='Cipher functions that produce Jasypt/Bouncycastle compatible password encryption.', 40 | 41 | license='Apache License 2.0', 42 | 43 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 44 | classifiers=[ 45 | # How mature is this project? Common values are 46 | # 3 - Alpha 47 | # 4 - Beta 48 | # 5 - Production/Stable 49 | 'Development Status :: 3 - Alpha', 50 | 51 | # Indicate who your project is intended for 52 | 'Intended Audience :: Developers', 53 | 'Topic :: Software Development :: Build Tools', 54 | 'Topic :: System :: Software Distribution', 55 | 'Topic :: System :: Systems Administration', 56 | 57 | # released under Apache 2 License 58 | 'License :: OSI Approved :: Apache Software License', 59 | 60 | # the language used by the author 61 | 'Natural Language :: English', 62 | 63 | # Specify the Python versions you support here. In particular, ensure 64 | # that you indicate whether you support Python 2, Python 3 or both. 65 | 'Programming Language :: Python :: 2', 66 | 'Programming Language :: Python :: 2.7', 67 | 'Programming Language :: Python :: 3', 68 | 'Programming Language :: Python :: 3.3', 69 | 'Programming Language :: Python :: 3.4', 70 | 'Programming Language :: Python :: 3.5', 71 | 72 | # works on anything that can run pycrypto 73 | 'Programming Language :: Python :: 2', 74 | 'Programming Language :: Python :: 2.7', 75 | 'Programming Language :: Python :: 3' 76 | ], 77 | 78 | keywords='jasypt bouncycastle AES crypto SHA256', 79 | 80 | install_requires=[ 81 | 'pycrypto' 82 | ], 83 | 84 | # prepare for testing with nose 85 | test_suite='nose.collector', 86 | tests_require=[ 87 | 'nose' 88 | ], 89 | 90 | # manually define packages 91 | py_modules=[ 92 | 'jasypt4py.exceptions', 93 | 'jasypt4py.generator', 94 | 'jasypt4py.encryptor' 95 | ] 96 | 97 | ) 98 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fareliner/jasypt4py/6ea7cdbb4ee1e3249cc9dcadfa3c54e603614458/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_encryptor.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from jasypt4py.encryptor import StandardPBEStringEncryptor 4 | 5 | 6 | class TestStandardPBEStringEncryptor(unittest.TestCase): 7 | def test_custom_salt_size(self): 8 | jasypt = StandardPBEStringEncryptor(algorithm='PBEWITHSHA256AND256BITAES-CBC', salt_block_size=32) 9 | 10 | self.assertFalse(jasypt.salt_generator is None, 'expect a salt generator to be configured') 11 | self.assertFalse(jasypt.key_generator is None, 'expect a key material generator to be configured') 12 | self.assertEqual(jasypt.salt_generator.salt_block_size, 32, 'expect a custom salt block size') 13 | 14 | def test_invalid_salt_generator_selected(self): 15 | with self.assertRaises(NotImplementedError) as context: 16 | StandardPBEStringEncryptor(algorithm=None, salt_generator=None) 17 | 18 | self.assertEqual('Salt generator None is not implemented', str(context.exception)) 19 | 20 | def test_invalid_algorithm_selected(self): 21 | with self.assertRaises(NotImplementedError) as context: 22 | StandardPBEStringEncryptor(algorithm=None) 23 | 24 | self.assertEqual('Algorithm None is not implemented', str(context.exception)) 25 | 26 | def test_encrypt_decrypt_with_custom_iteration(self): 27 | jasypt = StandardPBEStringEncryptor('PBEWITHSHA256AND256BITAES-CBC') 28 | pwd = 'pssst...don\'t tell anyone' 29 | message = 'secret value' 30 | 31 | encrypted_message = jasypt.encrypt(pwd, message, 4000) 32 | decrypted_message = jasypt.decrypt(pwd, encrypted_message, 4000) 33 | 34 | self.assertEqual(message, decrypted_message, 'expect same result from reverse function') 35 | 36 | def test_encrypt_decrypt_with_fixed_salt(self): 37 | jasypt = StandardPBEStringEncryptor(algorithm='PBEWITHSHA256AND256BITAES-CBC', 38 | salt_generator='Fixed', 39 | salt='0123456789ABCDEF') 40 | pwd = 'pssst...don\'t tell anyone' 41 | message = 'secret value' 42 | 43 | encrypted_message = jasypt.encrypt(pwd, message, 4000) 44 | decrypted_message = jasypt.decrypt(pwd, encrypted_message, 4000) 45 | 46 | # print('enc = %s' % encrypted_message) 47 | 48 | self.assertEqual('MDEyMzQ1Njc4OUFCQ0RFRpK/4i3JBHsMTN1Zf2OCZ0o=', encrypted_message, 49 | 'expected fixed crypted result') 50 | 51 | # print('dec = %s' % decrypted_message) 52 | 53 | # decrypt returns a byte array so need to compare apples with apples 54 | self.assertEqual(message, decrypted_message, 'expect same result from reverse function') 55 | 56 | def test_encrypt_decrypt_large_key(self): 57 | jasypt = StandardPBEStringEncryptor('PBEWITHSHA256AND256BITAES-CBC') 58 | pwd = 'CAX6MDwO+QwgPeGRTEjM+84LWWTfQ1icE3wj8IIc8nUAx1I2+EmbUzy8ntCB0m21SWE0IMWSr/qvRDOP1EQua2rs2RHtsGGu/dxCJQ4ct4qlcQFTKNPbhpewoxbTmaBbbrIXIny4dZzYWXte0kNS4FscUrZX1RSNGq2qoaw4MPuVSRi0WtNmtd5ZJ5HVUQohkApiecZe0TJvBppXePFEobuts+NYtpdf0vWLJtWWr3e03qP3AYelNN2GcHDZdtMaEXNT0wbBClbULDaYOC4vCmyfzbHZan6SFFX8bHvtsS1tBuCcxXzfQwUkAKJQYgNrNdOW3xyM6mVAWT4AOjtVjO3PdrmRacML3KSYv+BRktKJRgmQWF5Msg==' 59 | message = 'secret value' 60 | 61 | encrypted_message = jasypt.encrypt(pwd, message) 62 | decrypted_message = jasypt.decrypt(pwd, encrypted_message) 63 | 64 | self.assertEqual(message, decrypted_message, 'expect same result from reverse function') 65 | 66 | def test_encrypt_decrypt_large_message(self): 67 | jasypt = StandardPBEStringEncryptor('PBEWITHSHA256AND256BITAES-CBC') 68 | pwd = 'CAX6MDwO+QwgPeGZzYWXVAWT4AOjtVjO3PdrmRacML3KSYv+BRktKJRgmQWF5Msg==' 69 | message = """ 70 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras rhoncus, leo vel feugiat tempor, felis ex viverra nulla, vitae finibus massa risus sed massa. Nulla malesuada sapien vel massa eleifend bibendum. Nunc congue augue lobortis augue placerat, tempor ultrices tellus pretium. Etiam at molestie velit. Vestibulum tincidunt vestibulum purus, vitae volutpat ex condimentum vel. Aliquam erat volutpat. Curabitur cursus, neque nec fringilla tempus, nulla nunc egestas ex, in tincidunt metus enim accumsan diam. Aenean pellentesque tellus quis dui posuere cursus. Mauris gravida nisl a elit eleifend, quis lobortis turpis lobortis. 71 | 72 | Ut at hendrerit nibh, non pulvinar dui. Praesent rhoncus molestie nulla vel accumsan. In varius neque in eros posuere tempor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Phasellus interdum eget mauris id faucibus. Fusce eu odio ullamcorper, convallis libero sit amet, iaculis velit. Sed sollicitudin ligula arcu, ultrices pellentesque neque pharetra vitae. Morbi tristique imperdiet convallis. Donec non augue iaculis dolor lacinia consectetur in eu tellus. Maecenas sagittis erat vel fringilla aliquet. Donec vestibulum quam eget varius imperdiet. Phasellus mattis tristique orci nec efficitur. Aliquam condimentum mattis orci, non iaculis nisl scelerisque vel. 73 | 74 | Fusce tempus, elit dapibus rutrum rutrum, sem ante blandit lectus, sit amet bibendum arcu justo ut lorem. Mauris scelerisque lorem nec mauris dapibus, ut imperdiet quam elementum. Cras maximus lorem eget tincidunt feugiat. Vivamus at urna sollicitudin, euismod ipsum pulvinar, pretium libero. Sed sit amet interdum turpis. Integer mauris libero, ultricies eget hendrerit nec, blandit sit amet lorem. Nullam porttitor mi imperdiet felis aliquam, a porttitor augue semper. Ut massa justo, blandit ut neque malesuada, tempor placerat est. Maecenas in velit condimentum, commodo justo sit amet, mollis quam. Cras felis mi, iaculis non tempor ut, scelerisque nec eros. Suspendisse arcu magna, cursus et rutrum a, feugiat sed est. Proin ut risus mi. Duis imperdiet erat a augue consequat malesuada. Mauris lacinia nisl vel gravida facilisis. Curabitur a enim nisl. 75 | 76 | Morbi eget hendrerit turpis. Suspendisse sollicitudin scelerisque consectetur. Maecenas porta, leo eu sodales eleifend, velit turpis viverra mauris, in semper orci neque ut lorem. Aliquam sit amet interdum velit. Mauris elementum volutpat felis, sed ornare felis mollis in. Pellentesque vehicula placerat ex, in scelerisque sapien maximus quis. Donec vitae ante ipsum. Cras consectetur nulla at magna rutrum, et finibus tortor consectetur. Nulla facilisi. Duis nulla lectus, pulvinar ac molestie eu, placerat sit amet eros. Aenean id magna arcu. Vivamus commodo faucibus orci, sit amet accumsan justo imperdiet sit amet. Aenean dictum a arcu in ullamcorper. Vestibulum leo leo, congue nec justo eu, malesuada interdum nibh. Maecenas at sem efficitur, bibendum purus sed, tincidunt nulla. 77 | """ 78 | 79 | encrypted_message = jasypt.encrypt(pwd, message) 80 | decrypted_message = jasypt.decrypt(pwd, encrypted_message) 81 | 82 | self.assertEqual(message, decrypted_message, 'expect same result from reverse function') 83 | 84 | 85 | if __name__ == '__main__': 86 | unittest.main() 87 | --------------------------------------------------------------------------------