python 复现java中jasypt包的 PBEWITHSHA256AND256BITAES-CBC-BC加解密算法(可java & python双向加解密)

运行环境:python 3.6

依赖包Crypto的安装: pip3 install pycryptodome  

# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)

from abc import ABCMeta
from array import array
from base64 import b64encode, b64decode
from Crypto import Random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256


class PBEParameterGenerator(object):
    __metaclass__ = ABCMeta

    @staticmethod
    def pad(block_size, s):
        """
        Pad a string to the provided block size when using fixed block ciphers.
        :param block_size: int - the cipher block size
        :param s: str - the string to pad
        :return: a padded string that can be fed to the cipher
        """
        return s + (block_size - len(s) % block_size) * chr(block_size - len(s) % block_size)

    @staticmethod
    def unpad(s):
        """
        Remove padding from the string after decryption when using fixed block ciphers.
        :param s: str - the string to remove padding from
        :return: the unpadded string
        """
        s = s.decode('utf-8')
        s = s[0:-ord(s[-1])]
        # return s[0:-ord(s[-1])]
        return s.encode('utf-8')

    @staticmethod
    def adjust(a, a_off, b):
        """
        Adjusts the byte array as per PKCS12 spec
        :param a: byte[] - the target array
        :param a_off: int - offset to operate on
        :param b: byte[] - the bitsy array to pick from
        :return: nothing as operating on array by reference
        """
        x = (b[len(b) - 1] & 0xff) + (a[a_off + len(b) - 1] & 0xff) + 1

        a[a_off + len(b) - 1] = x & 0xff

        x = x >> 8

        for i in range(len(b) - 2, -1, -1):
            x = x + (b[i] & 0xff) + (a[a_off + i] & 0xff)
            a[a_off + i] = x & 0xff
            x = x >> 8

    @staticmethod
    def pkcs12_password_to_bytes(password):
        """
        Converts a password string to a PKCS12 v1.0 compliant byte array.
        :param password: byte[] - the password as simple string
        :return: The unsigned byte array holding the password
        """
        pkcs12_pwd = [0x00] * (len(password) + 1) * 2

        for i in range(0, len(password)):
            digit = ord(password[i])
            pkcs12_pwd[i * 2] = int(digit >> 8)
            pkcs12_pwd[i * 2 + 1] = int(digit)

        return array('B', pkcs12_pwd)


class PKCS12ParameterGenerator(PBEParameterGenerator):
    """
    Equivalent of the Bouncycastle PKCS12ParameterGenerator.
    """
    __metaclass__ = ABCMeta

    KEY_MATERIAL = 1
    IV_MATERIAL = 2
    MAC_MATERIAL = 3

    SALT_SIZE_BYTE = 16

    def __init__(self, digest_factory):
        super(PBEParameterGenerator, self).__init__()
        self.digest_factory = digest_factory

    def generate_derived_parameters(self, password, salt, iterations, key_size, iv_size):
        """
        Generates the key and iv that can be used with the cipher.
        :param password: str - the password used for the key material
        :param salt: byte[] - random salt
        :param iterations: int - number if hash iterations for key material
        :param key_size: int - key size in bits
        :param iv_size: int - iv size in bits
        :return: key and iv that can be used to setup the cipher
        """
        key_size = int(key_size / 8)
        iv_size = int(iv_size / 8)

        # pkcs12 padded password (unicode byte array with 2 trailing 0x0 bytes)
        password_bytes = PKCS12ParameterGenerator.pkcs12_password_to_bytes(password)

        d_key = self.generate_derived_key(password_bytes, salt, iterations, self.KEY_MATERIAL, key_size)
        if iv_size and iv_size > 0:
            d_iv = self.generate_derived_key(password_bytes, salt, iterations, self.IV_MATERIAL, iv_size)
        else:
            d_iv = None
        return d_key, d_iv

    def generate_derived_key(self, password, salt, iterations, id_byte, key_size):
        """
        Generate a derived key as per PKCS12 v1.0 spec
        :param password: byte[] - pkcs12 padded password (unicode byte array with 2 trailing 0x0 bytes)
        :param salt: byte[] - random salt
        :param iterations: int - number if hash iterations for key material
        :param id_byte: int - the material padding
        :param key_size: int - the key size in bytes (e.g. AES is 256/8 = 32, IV is 128/8 = 16)
        :return: the sha256 digested pkcs12 key
        """

        u = int(self.digest_factory.digest_size)
        v = int(self.digest_factory.block_size)

        d_key = [0x00] * key_size

        # Step 1
        D = [id_byte] * v

        # Step 2
        S = []
        if salt and len(salt) != 0:
            s_size = v * int((len(salt) + v - 1) / v)
            S = [0x00] * s_size

            salt_size = len(salt)
            for i in range(s_size):
                S[i] = salt[i % salt_size]

        # Step 3
        P = []
        if password and len(password) != 0:
            p_size = v * int((len(password) + v - 1) / v)
            P = [0x00] * p_size

            password_size = len(password)
            for i in range(p_size):
                P[i] = password[i % password_size]

        # Step 4
        I = array('B', S + P)
        B = array('B', [0x00] * v)

        # Step 5
        c = int((key_size + u - 1) / u)

        # Step 6
        for i in range(1, c + 1):
            # Step 6 - a
            digest = self.digest_factory.new()
            digest.update(array('B', D))
            digest.update(I)
            A = array('B', digest.digest())  # bouncycastle now resets the digest, we will create a new digest

            for j in range(1, iterations):
                A = array('B', self.digest_factory.new(A).digest())

                # Step 6 - b
            for k in range(0, v):
                B[k] = A[k % u]

            # Step 6 - c
            for j in range(0, int(len(I) / v)):
                self.adjust(I, j * v, B)

            if i == c:
                for j in range(0, key_size - ((i - 1) * u)):
                    d_key[(i - 1) * u + j] = A[j]
            else:
                for j in range(0, u):
                    d_key[(i - 1) * u + j] = A[j]

        return array('B', d_key)


def encrypt(password, text, iterations=1000):
    # some default from somewhere
    key_size_bits = 256
    iv_size_bits = 128

    # create sha256 PKCS12 secret generator
    generator = PKCS12ParameterGenerator(SHA256)

    # from PBE.Util.makePBEParameters()
    # generate a 16 byte salt which is used to generate key material and iv
    salt = array('B', Random.get_random_bytes(PKCS12ParameterGenerator.SALT_SIZE_BYTE))
    # print('enc-salt = %s' % binascii.hexlify(salt))

    # generate key material as per PBEWITHSHA256AND256BITAES-CBC-BC
    key, iv = generator.generate_derived_parameters(password, salt, iterations, key_size_bits, iv_size_bits)

    # setup AES cipher
    cipher = AES.new(key.tobytes(), AES.MODE_CBC, iv.tobytes())

    # pad the plain text secret to AES block size
    encrypted_message = cipher.encrypt(generator.pad(AES.block_size, text))

    # concatenate salt + encrypted message
    return b64encode(salt + array('B', encrypted_message))


def decrypt(password, ciphertext, iterations=1000):
    # some default from somewhere
    key_size_bits = 256
    iv_size_bits = 128

    # create sha256 PKCS12 secret generator
    generator = PKCS12ParameterGenerator(SHA256)

    # decode the base64 encoded and encrypted secret
    n_cipher_bytes = b64decode(ciphertext)

    # extract salt bytes 0 - SALT_SIZE
    salt = array('B', n_cipher_bytes[:PKCS12ParameterGenerator.SALT_SIZE_BYTE])
    # print('dec-salt = %s' % binascii.hexlify(salt))

    # create reverse key material
    key, iv = generator.generate_derived_parameters(password, salt, iterations, key_size_bits, iv_size_bits)

    cipher = AES.new(key.tobytes(), AES.MODE_CBC, iv.tobytes())

    # extract encrypted message bytes SALT_SIZE - len(cipher)
    n_cipher_message = array('B', n_cipher_bytes[PKCS12ParameterGenerator.SALT_SIZE_BYTE:])

    # decode the message and unpad
    decoded = cipher.decrypt(n_cipher_message.tostring())

    return generator.unpad(decoded)


if __name__ == "__main__":
    passcode = 'pssst...don\'t tell anyone'
    result = encrypt(passcode, 'secret value', 4000)

    print('enc = %s' % result)

    reverse = decrypt(passcode, result, 4000)

    print('dec = %s' % reverse)

    # run something like this on the jasypt command line
    # $JASYPT_HOME/bin/decrypt.sh keyObtentionIterations = 4000 \
    #                             providerClassName = "org.bouncycastle.jce.provider.BouncyCastleProvider" \
    #                             saltGeneratorClassName = "org.jasypt.salt.RandomSaltGenerator" \
    #                             algorithm = "PBEWITHSHA256AND256BITAES-CBC-BC" \
    #                             password = 'pssst...don\'t tell anyone' \
    #                             input = 'xgX5+yRbKhs4zSubkAPkg9gSBkZU6XWt7csceM/3xDY='

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值