从java加密并从python解密| AES GCM 加密

问题描述 投票:0回答:1

我想从 Java 中加密一个字符串并在 Python 中解密该加密值。使用 AEC GCM 算法。下面是我的java代码

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AESEncryptionUtil {
    public static void main(String[] args) {
        String encString = "Hello, World!";
        String secKey = "hellow world";
        String encrypted = encrypt(encString, secKey);
        System.out.println("Encrypted (Java): " + encrypted);
        String decrypted = decrypt(encrypted, secKey);
        System.out.println("Decrypted (Java): " + decrypted);
    }

    private static final Logger logger = LoggerFactory.getLogger(AESEncryption.class);
    private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";
    private static final int TAG_LENGTH_BIT = 128;
    private static final int IV_LENGTH_BYTE = 12;
    private static final int SALT_LENGTH_BYTE = 16;
    private static final Charset UTF_8 = StandardCharsets.UTF_8;

    public static String encrypt(String pText, String secKey) {
        try {
            if (pText == null || pText.equals("null")) {
                return null;
            }
            byte[] salt = getRandomNonce(SALT_LENGTH_BYTE);
            byte[] iv = getRandomNonce(IV_LENGTH_BYTE);
            byte[] keyBytes = secKey.getBytes(StandardCharsets.UTF_16);
            SecretKeySpec skeySpec = new SecretKeySpec(Arrays.copyOf(keyBytes, 16), "AES");
            Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
            byte[] cipherText = cipher.doFinal(pText.getBytes());
            byte[] cipherTextWithIvSalt =
                    ByteBuffer.allocate(iv.length + salt.length + cipherText.length)
                            .put(iv)
                            .put(salt)
                            .put(cipherText)
                            .array();
            return Base64.getEncoder().encodeToString(cipherTextWithIvSalt);
        } catch (Exception ex) {
            logger.error("Error while encrypting:", ex);
        }
        return null;
    }

    public static String decrypt(String cText, String secKey) {
        try {
            if (cText == null || cText.equals("null")) {
                return null;
            }
            byte[] decode = Base64.getDecoder().decode(cText.getBytes(UTF_8));
            ByteBuffer bb = ByteBuffer.wrap(decode);
            byte[] iv = new byte[IV_LENGTH_BYTE];
            bb.get(iv);
            byte[] salt = new byte[SALT_LENGTH_BYTE];
            bb.get(salt);
            byte[] cipherText = new byte[bb.remaining()];
            bb.get(cipherText);
            byte[] keyBytes = secKey.getBytes(StandardCharsets.UTF_16);
            SecretKeySpec skeySpec = new SecretKeySpec(Arrays.copyOf(keyBytes, 16), "AES");
            Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, new GCMParameterSpec(TAG_LENGTH_BIT, iv));
            byte[] plainText = cipher.doFinal(cipherText);
            return new String(plainText, UTF_8);
        } catch (Exception ex) {
            logger.error("Error while decrypting:", ex);
        }
        return null;
    }

    public static byte[] getRandomNonce(int numBytes) {
        byte[] nonce = new byte[numBytes];
        new SecureRandom().nextBytes(nonce);
        return nonce;
    }
}

我无法更改我的 Java 代码;我在Python中尝试了很多方法但未能实现。 大多数时候我都会收到 SecretKey 解码错误 和来自 Python 端的 cryptography.exceptions.InvalidTag 。 感谢您的建议。

Python代码:

import base64
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

def decrypt(cipher_text_base64, secret_key):
    cipher_text_with_iv_salt = base64.b64decode(cipher_text_base64)
    iv = cipher_text_with_iv_salt[:12]
    salt = cipher_text_with_iv_salt[12:28]
    tag = cipher_text_with_iv_salt[-16:]  # The last 16 bytes are the tag
    ciphertext = cipher_text_with_iv_salt[28:-16]  # Ciphertext excluding tag
    key = secret_key.encode('utf-16')
    key = key[:16].ljust(16, b'\0')
    decryptor = Cipher(algorithms.AES(key), modes.GCM(iv, tag), backend=default_backend()).decryptor()
    plaintext = decryptor.update(ciphertext) + decryptor.finalize()
    return plaintext.decode('utf-8')

if __name__ == "__main__":
    text_to_encrypt = "Hello, World!"
    secret_key = "hellow world"

    # Decrypt in Python
    decrypted_text = decrypt("encrypted_text", secret_key)
    print(f"Decrypted (Python): {decrypted_text}")
python java spring-boot aes aes-gcm
1个回答
0
投票

该问题是由于密钥的编码不同造成的。

Java 代码中的

secret_key.encode('utf-16')
根据定义表示带有 BOM(字节顺序标记)的 big 字节序,请参阅 Charset,秒。 标准字符集

解码时,UTF-16和UTF-32字符集...;编码时使用big-endian字节顺序,并写入big-endian字节顺序标记。

相比之下,Python 代码中的

encode('utf-16')
是带有 BOM 的 little 字节序(至少在我的机器上;这可能取决于平台的本机字节顺序)。

在Python代码中,可以可靠地实现带有BOM的big endian,例如与:

import codecs
...
key = codecs.BOM_UTF16_BE + secret_key.encode('utf-16be')
key = key[:16].ljust(16, b'\0')
...

通过此修复,可以使用 Python 代码进行解密。


虽然 Java 代码无法更改,但未来的读者应该意识到一个漏洞,即从具有字符集编码的字符串派生密钥。
相反,密钥导出函数(例如 Argon2 或至少 PBKDF2)应与随机盐结合使用。
有趣的是,Java 代码中会生成(然后连接)随机盐,但它不会应用于任何地方。有可能考虑过使用专用的密钥导出函数,但后来由于某种原因没有实现。

此外,对于文本编码(例如

pText.getBytes()
),应始终显式指定特定编码,否则将使用特定于平台的默认编码(至少对于较旧的 Java 版本)。

© www.soinside.com 2019 - 2024. All rights reserved.