在python中实现java jasypt的PBEWITHHMACSHA512ANDAES_256

Dim*_*ris 4 python java cryptography hmac jasypt

我正在尝试在 python 中加密密码,并通过jasypt 插件使用jasypt 库在 java springboot 应用程序中解密它。

到目前为止我做了什么

  • 为简单起见,我使用了零盐和固定静脉注射
  • 我已经编写了 python 脚本来使用 hselvarajan 的pkcs12kdf执行加密
    import sys
    import math
    import base64
    import hashlib
    from Crypto.Cipher import AES
    from Crypto.Hash import SHA512
    
    from binascii import hexlify
    from binascii import unhexlify
    
    PY2 = sys.version_info[0] == 2
    PY3 = sys.version_info[0] == 3
    if PY2:
            str_encode = lambda s: str(s)
    elif PY3:
            str_encode = lambda s: str(s, 'utf-8')
    
    iterations          = 10000
    salt_block_size     = AES.block_size
    key_size            = 256
    
    password             = "test1"
    plaintext_to_encrypt = "password1"
    salt                 = "0000000000000000"
    iv                   = "0000000000000000"
    
    # -----------------------------------------------------------------------------
    # This is a pure copy paste of
    #  https://github.com/hselvarajan/pkcs12kdf/blob/master/pkcs12kdf.py
    # -----------------------------------------------------------------------------
    class PKCS12KDF:
            """This class generates keys and initialization vectors from passwords as specified in RFC 7292"""
    
            #
            # IDs for Key and IV material as in RFC
            #
    
            KEY_MATERIAL = 1
            IV_MATERIAL = 2
    
            def __init__(self, password, salt, iteration_count, hash_algorithm, key_length_bits):
                    self._password = password
                    self._salt = salt
                    self._iteration_count = iteration_count
                    self._block_size_bits = None
                    self._hash_length_bits = None
                    self._key_length_bytes = key_length_bits/8
                    self._key = None
                    self._iv = None
                    self._hash_algorithm = hash_algorithm
            #
            # Turns a byte array into a long
            #
    
            @staticmethod
            def byte_array_to_long(byte_array, nbytes=None):
                    #
                    # If nbytes is not present
                    #
                    if nbytes is None:
                            #
                            # Convert byte -> hex -> int/long
                            #
                            return int(hexlify(byte_array), 16)
                    else:
                            #
                            # Convert byte -> hex -> int/long
                            #
                            return int(hexlify(byte_array[-nbytes:]), 16)
    
            #
            # Turn a long into a byte array
            #
    
            @staticmethod
            def long_to_byte_array(val, nbytes=None):
                    hexval = hex(val)[2:-1] if type(val) is long else hex(val)[2:]
                    if nbytes is None:
                            return unhexlify('0' * (len(hexval) & 1) + hexval)
                    else:
                            return unhexlify('0' * (nbytes * 2 - len(hexval)) + hexval[-nbytes * 2:])
    
            #
            # Run the PKCS12 algorithm for either the key or the IV, specified by id
            #
    
            def generate_derived_parameters(self, id):
    
                    #
                    # Let r be the iteration count
                    #
    
                    r = self._iteration_count
    
                    if self._hash_algorithm not in hashlib.algorithms_available:
                            raise NotImplementedError("Hash function: "+self._hash_algorithm+" not available")
    
                    hash_function = hashlib.new(self._hash_algorithm)
    
                    #
                    # Block size, bytes
                    #
                    #v = self._block_size_bits / 8
                    v = hash_function.block_size
    
                    #
                    # Hash function output length, bits
                    #
                    #u = self._hash_length_bits / 8
                    u = hash_function.digest_size
    
                    # In this specification however, all passwords are created from BMPStrings with a NULL
                    # terminator. This means that each character in the original BMPString is encoded in 2
                    # bytes in big-endian format (most-significant byte first). There are no Unicode byte order
                    # marks. The 2 bytes produced from the last character in the BMPString are followed by
                    # two additional bytes with the value 0x00.
    
                    password = (unicode(self._password) + u'\0').encode('utf-16-be') if self._password is not None else b''
    
                    #
                    # Length of password string, p
                    #
                    p = len(password)
    
                    #
                    # Length of salt, s
                    #
                    s = len(self._salt)
    
                    #
                    # Step 1: Construct a string, D (the "diversifier"), by concatenating v copies of ID.
                    #
    
                    D = chr(id) * v
    
                    #
                    # Step 2: Concatenate copies of the salt, s, together to create a string S of length v * [s/v] bits (the
                    # final copy of the salt may be truncated to create S). Note that if the salt is the empty
                    # string, then so is S
                    #
    
                    S = b''
    
                    if self._salt is not None:
                            limit = int(float(v) * math.ceil((float(s)/float(v))))
                            for i in range(0, limit):
                                    S += (self._salt[i % s])
                    else:
                            S += '0'
    
                    #
                    # Step 3: Concatenate copies of the password, p, together to create a string P of length v * [p/v] bits
                    # (the final copy of the password may be truncated to create P). Note that if the
                    # password is the empty string, then so is P.
                    #
    
                    P = b''
    
                    if password is not None:
                            limit = int(float(v) * math.ceil((float(p)/float(v))))
                            for i in range(0, limit):
                                    P += password[i % p]
                    else:
                            P += '0'
    
                    #
                    # Step 4: Set I=S||P to be the concatenation of S and P.\00\00
                    #
    
                    I = bytearray(S) + bytearray(P)
    
                    #
                    # 5. Set c=[n/u]. (n = length of key/IV required)
                    #
    
                    n = self._key_length_bytes
                    c = int(math.ceil(float(n)/float(u)))
    
                    #
                    # Step 6 For i=1, 2,..., c, do the following:
                    #
    
                    Ai = bytearray()
    
                    for i in range(0, c):
                            #
                            # Step 6a.Set Ai=Hr(D||I). (i.e. the rth hash of D||I, H(H(H(...H(D||I))))
                            #
    
                            hash_function = hashlib.new(self._hash_algorithm)
                            hash_function.update(bytearray(D))
                            hash_function.update(bytearray(I))
    
                            Ai = hash_function.digest()
    
                            for j in range(1, r):
                                    hash_function = hashlib.sha256()
                                    hash_function.update(Ai)
                                    Ai = hash_function.digest()
    
                            #
                            # Step 6b: Concatenate copies of Ai to create a string B of length v bits (the final copy of Ai
                            # may be truncated to create B).
                            #
    
                            B = b''
    
                            for j in range(0, v):
                                    B += Ai[j % len(Ai)]
    
                            #
                            # Step 6c: Treating I as a concatenation I0, I1,..., Ik-1 of v-bit blocks, where k=[s/v]+[p/v],
                            # modify I by setting Ij=(Ij+B+1) mod 2v for each j.
                            #
    
                            k = int(math.ceil(float(s)/float(v)) + math.ceil((float(p)/float(v))))
    
                            for j in range(0, k-1):
                                    I = ''.join([
                                            self.long_to_byte_array(
                                                    self.byte_array_to_long(I[j:j + v]) + self.byte_array_to_long(bytearray(B)), v
                                            )
                                    ])
    
                    return Ai[:self._key_length_bytes]
    
            #
            # Generate the key and IV
            #
    
            def generate_key_and_iv(self):
                    self._key = self.generate_derived_parameters(self.KEY_MATERIAL)
                    self._iv = self.generate_derived_parameters(self.IV_MATERIAL)
                    return self._key, self._iv
    
    # -----------------------------------------------------------------------------
    # Main execution
    # -----------------------------------------------------------------------------
    kdf = PKCS12KDF(
        password        = password,
        salt            = salt,
        iteration_count = iterations,
        hash_algorithm  = "sha512",
        key_length_bits = key_size
    )
    (key, iv_tmp) = kdf.generate_key_and_iv()
    aes_key = key[:32]
    
    pad = salt_block_size - len(plaintext_to_encrypt) % salt_block_size
    plaintext_to_encrypt = plaintext_to_encrypt + pad * chr(pad)
    
    cipher = AES.new(aes_key, AES.MODE_CBC, iv)
    encrypted = cipher.encrypt(plaintext_to_encrypt)
    
    # Since we selt the salt to be zero's,
    # jasypt needs only the iv + encrypted value,
    # not the salt + iv + encrypted
    result = str_encode(base64.b64encode(iv + encrypted))
    
    # Python output : MDAwMDAwMDAwMDAwMDAwMKWsWH+Ku37n7ddfj0ayxp8=
    # Java output   : MDAwMDAwMDAwMDAwMDAwMAtqAfBtuxf+F5qqzC8QiFc=
    print(result)
    
    
    Run Code Online (Sandbox Code Playgroud) 运行它作为
    python2.7 test-PBEWITHHMACSHA512ANDAES_256.py
    paxYf4q7fuft11+PRrLGnw==
    
    Run Code Online (Sandbox Code Playgroud)
  • 我在 jasypt 存储库中编写了一个单元测试来解密请参阅PBEWITHHMACSHA512ANDAES_256EncryptorTest。运行它作为
    $ cd jasypt
    $ mvn clean test -Dtest=org.jasypt.encryption.pbe.PBEWITHHMACSHA512ANDAES_256EncryptorTest
    
    Run Code Online (Sandbox Code Playgroud)

问题:上述设置在 python 和 java 中产生不同的结果

  • Python输出:MDAwMDAwMDAwMDAwMDAwMKWsWH+Ku37n7ddfj0ayxp8=
  • Java输出:MDAwMDAwMDAwMDAwMDAwMAtqAfBtuxf+F5qqzC8QiFc=

我知道的

  • 失败的原因是没有在 python 中使用正确的密钥。添加额外的日志,错误为
    EncryptionOperationNotPossibleException: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
    
    Run Code Online (Sandbox Code Playgroud)
  • PBEWITHHMACSHA512ANDAES_256 使用 pkcs12 密钥派生函数。我不明白 HMAC 在哪里使用。
  • 我也尝试过使用以下实现,但无济于事。我在所有这些中都收到“”错误。
    self.aes_key = HKDF(master = self.password, key_len = 32, salt = self.salt, hashmod = SHA512, num_keys = 1)
    
    Run Code Online (Sandbox Code Playgroud)

我想要一些关于我做错了什么的指导。任何帮助,任何指示将不胜感激。


更新以下 Cryptodome 的 PBKDF2 和 AES 这里是 python 脚本

import sys
import base64
from Cryptodome.Cipher import AES
from Cryptodome.Hash import SHA512
from Cryptodome.Protocol.KDF import PBKDF2
from Cryptodome.Util.Padding import pad

iterations          = 10000
password             = b'test1'
plaintext_to_encrypt = b'password1'
salt                 = b'0000000000000000'
iv                   = b'0000000000000000'

# -----------------------------------------------------------------------------
# Main execution
# -----------------------------------------------------------------------------
keys = PBKDF2(password, salt, 64, count=iterations, hmac_hash_module=SHA512)
aes_key = keys[:32]

cipher = AES.new(aes_key, AES.MODE_CBC, iv)
ct_bytes = cipher.encrypt(pad(plaintext_to_encrypt, AES.block_size))
encrypted = base64.b64encode(ct_bytes).decode('utf-8')

# Since we selt the salt to be zero's,
# jasypt needs only the iv + encrypted value,
# not the salt + iv + encrypted
result = encrypted

# Python output : 6tCAZbswCh9DZ1EK8utRuA==
# Java output   : C2oB8G27F/4XmqrMLxCIVw==
print(result)

Run Code Online (Sandbox Code Playgroud)

及其输出

python2.7 test-PBEWITHHMACSHA512ANDAES_256-2.py
6tCAZbswCh9DZ1EK8utRuA==
Run Code Online (Sandbox Code Playgroud)

我尝试使用测试在java中解密它并出现以下错误

mvn clean test -Dtest=org.jasypt.encryption.pbe.PBEWITHHMACSHA512ANDAES_256EncryptorTest

[...]

Running org.jasypt.encryption.pbe.PBEWITHHMACSHA512ANDAES_256EncryptorTest
Test encr: C2oB8G27F/4XmqrMLxCIVw==
Error: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.524 sec <<< FAILURE!
test1(org.jasypt.encryption.pbe.PBEWITHHMACSHA512ANDAES_256EncryptorTest)  Time elapsed: 0.522 sec  <<< ERROR!
org.jasypt.exceptions.EncryptionOperationNotPossibleException
    at org.jasypt.encryption.pbe.StandardPBEByteEncryptor.decrypt(StandardPBEByteEncryptor.java:1173)
    at org.jasypt.encryption.pbe.StandardPBEStringEncryptor.decrypt(StandardPBEStringEncryptor.java:738)
    at org.jasypt.encryption.pbe.PBEWITHHMACSHA512ANDAES_256EncryptorTest.test1(PBEWITHHMACSHA512ANDAES_256EncryptorTest.java:27)


Results :

Tests in error:
  test1(org.jasypt.encryption.pbe.PBEWITHHMACSHA512ANDAES_256EncryptorTest)

Tests run: 1, Failures: 0, Errors: 1, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.648 s
[INFO] Finished at: 2020-06-24T17:40:04+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project jasypt: There are test failures.
[ERROR]
[ERROR] Please refer to /space/openbet/git/github-jasypt-jasypt/jasypt/target/surefire-reports for the individual test results.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
Run Code Online (Sandbox Code Playgroud)

Top*_*aco 6

PBEWITHHMACSHA512ANDAES_256应用 PBKDF2 生成密钥。使用 AES-256、CBC 进行加密。

(最初)发布的 Jasypt 测试函数使用了RandomIvGenerator,它创建了一个随机 IV。对于盐,ZeroSaltGenerator应用它生成由 16 个零字节组成的盐。

要实现您正在寻找的Python函数,最好使用固定的IV,例如使用StringFixedIvGenerator. StringFixedSaltGenerator为 salt 提供相应的功能(FixedStringSaltGenerator具有相同的功能,但自 1.9.2 起已弃用)。StringFixedSaltGeneratorStringFixedIvGenerator默认使用UTF-8对传递的字符串进行编码(但可以指定其他编码),以便salt(或IV)0000000000000000是十六进制编码的0x30303030303030303030303030303030

请注意,固定盐和 IV 只能用于测试。实际上,每次加密都必须使用新的随机盐和新的随机 IV。由于salt和IV不是秘密的,因此它们通常与字节级别的密文连接(例如,按照salt、iv、密文的顺序)并发送到接收者,接收者将这些部分分开并使用它们进行解密。

如果双方使用相同的参数(尤其是相同的 salt 和 IV),则使用 Python 加密并使用 Java 解密就可以了。

使用 Python (PyCryptodome) 加密:

import base64
from Cryptodome.Cipher import AES
from Cryptodome.Hash import SHA512
from Cryptodome.Protocol.KDF import PBKDF2
from Cryptodome.Util.Padding import pad

# Key generation (PBKDF2)
iterations           = 10000
password             = b'test1'
plaintext_to_encrypt = b'password1'
salt                 = b'5432109876543210'
iv                   = b'0123456789012345'
key = PBKDF2(password, salt, 32, count=iterations, hmac_hash_module=SHA512)

# Encryption (AES-256, CBC)
cipher = AES.new(key, AES.MODE_CBC, iv)
ct_bytes = cipher.encrypt(pad(plaintext_to_encrypt, AES.block_size))
encrypted = base64.b64encode(ct_bytes).decode('utf-8')

print(encrypted) # Output: kzLd5qPlCLnHq5sT7LOXzQ==
Run Code Online (Sandbox Code Playgroud)

用Java(Jasypt)解密:

StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("test1");
encryptor.setSaltGenerator(new StringFixedSaltGenerator("5432109876543210"));
encryptor.setIvGenerator(new StringFixedIvGenerator("0123456789012345"));
encryptor.setKeyObtentionIterations(10000);
encryptor.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
    
String decryptedMsg = encryptor.decrypt("kzLd5qPlCLnHq5sT7LOXzQ==");
System.out.println("Test decr: " + decryptedMsg); // Output: Test decr: password1
Run Code Online (Sandbox Code Playgroud)