一、术语说明
·EncodingAESKey:注册应用提供的数据加密密钥。用于消息体的加密,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,是 AESKey 的 Base64 编码。解码后即为32字节长的 AESKey。
·AESKey:AESKey=Base64_Decode(EncodingAESKey + “=”),是 AES 算法的密钥,长度为32字节。AES 采用 CBC 模式,数据采用 PKCS#7 填充;IV 初始向量大小为16字节,取 AESKey 前16字节。具体请参见:Cryptographic Message Syntax。
·msg 为消息体明文,格式为 JSON。
二、明文 msg 的加密过程
注意:开发者无需自行加密,加密由会议业务内部实现。
三、加密方案对应的解密方案
·取出返回的 JSON 中的 data 字段。
·对密文 BASE64 解码:aes_msg=Base64_Decode(data)。
·使用 AESKey 做 AES 解密:msg=AES_Decrypt(aes_msg)。
四、解密示例代码
GoJavaC++PHPPython
package mainimport ( "crypto/aes" "crypto/cipher" "crypto/subtle" "encoding/base64" "errors" "fmt" "strings")// aesKeySize 固定使用 AES-256。const aesKeySize = 32var ( ErrBadKeyLength = errors.New("aes: 密钥 base64 解码后必须为 32 字节 (AES-256)") ErrEmptyCiphertext = errors.New("aes: 密文为空") ErrCiphertextNotAligned = errors.New("aes: 密文长度不是 AES 块大小(16)的整数倍") ErrBadPadding = errors.New("aes: PKCS#7 填充无效"))// AllowZeroPadding 为 true 时接受零填充(ZeroPadding)密文,即末字节为 0x00 时// 不剥离任何字节。仅在对端使用 ZeroPadding 时开启;标准 PKCS#7 请保持 false。var AllowZeroPadding = false// aesDecrypt 用 AES-256-CBC 解密 base64 编码的密文,IV 取自密钥前 16 字节。func aesDecrypt(encryptedText, key string) (string, error) { decodedKey, err := base64.StdEncoding.DecodeString(padBase64(key)) if err != nil { return "", fmt.Errorf("解码密钥失败: %w", err) } if len(decodedKey) != aesKeySize { return "", fmt.Errorf("%w, 实际 %d 字节", ErrBadKeyLength, len(decodedKey)) } aesKey := make([]byte, aesKeySize) copy(aesKey, decodedKey) // IV 取密钥前 16 字节,拷贝一份避免与 aesKey 共享底层数组 iv := make([]byte, aes.BlockSize) copy(iv, aesKey[:aes.BlockSize]) decodedText, err := base64.StdEncoding.DecodeString(encryptedText) if err != nil { return "", fmt.Errorf("解码密文失败: %w", err) } // CryptBlocks 要求输入非空且为块大小整数倍,否则 panic if len(decodedText) == 0 { return "", ErrEmptyCiphertext } if len(decodedText)%aes.BlockSize != 0 { return "", fmt.Errorf("%w, 实际 %d 字节", ErrCiphertextNotAligned, len(decodedText)) } block, err := aes.NewCipher(aesKey) if err != nil { return "", fmt.Errorf("创建 AES cipher 失败: %w", err) } // 原地解密,decodedText 被覆盖为明文 cipher.NewCBCDecrypter(block, iv).CryptBlocks(decodedText, decodedText) plaintext, err := pkcs7Unpad(decodedText, aes.BlockSize) if err != nil { return "", err } return string(plaintext), nil}// padBase64 为标准 base64 字符串补齐 '=' padding。func padBase64(s string) string { if m := len(s) % 4; m != 0 { s += strings.Repeat("=", 4-m) } return s}// pkcs7Unpad 校验并去除 PKCS#7 填充。func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) { n := len(data) if n == 0 || n%blockSize != 0 { return nil, ErrBadPadding } padding := int(data[n-1]) if padding == 0 && AllowZeroPadding { return data, nil } if padding == 0 || padding > blockSize || padding > n { return nil, fmt.Errorf("%w: 填充长度 %d", ErrBadPadding, padding) } // 常量时间比较,缓解 padding oracle 时序侧信道 want := make([]byte, padding) for i := range want { want[i] = byte(padding) } if subtle.ConstantTimeCompare(data[n-padding:], want) != 1 { return nil, ErrBadPadding } return data[:n-padding], nil}func main() { encryptedText := "your base64 encoded encrypted text" key := "your base64 encoded key" decryptedText, err := aesDecrypt(encryptedText, key) if err != nil { fmt.Println("Error:", err) return } fmt.Println("Decrypted Text:", decryptedText)}
五、Running Environment
Operating System: Ubuntu 24.04.3 LTS / x86_64Runtime Version: go version go1.21.4 linux/amd64
import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.nio.charset.StandardCharsets;import java.security.GeneralSecurityException;import java.security.MessageDigest;import java.util.Arrays;import java.util.Base64;/** * AES/CBC 解密工具。 * * <ul> * <li>密钥经 base64 解码后直接作为 AES key,长度决定 AES-128/192/256</li> * <li>IV 取自密钥前 16 字节</li> * <li>Cipher 使用 NoPadding,PKCS#7 填充由本类手工剥离</li> * </ul> */public final class Main { private static final int BLOCK_SIZE = 16; /** AES 允许的密钥长度(字节):AES-128 / AES-192 / AES-256 */ private static final int[] VALID_KEY_LENGTHS = {16, 24, 32}; /** * 为 true 时接受零填充(ZeroPadding)密文,即末字节为 0x00 时不剥离任何字节。 * 仅在对端使用 ZeroPadding 时开启;标准 PKCS#7 请保持 false。 */ public static volatile boolean allowZeroPadding = false; private Main() { } public static String decrypt(String encryptedText, String key) throws GeneralSecurityException { if (encryptedText == null || key == null) { throw new IllegalArgumentException("密文和密钥均不能为 null"); } byte[] keyBytes = decodeBase64(padBase64(key), "密钥"); if (!isValidKeyLength(keyBytes.length)) { throw new InvalidKeyLengthException(keyBytes.length); } byte[] encryptedBytes = decodeBase64(encryptedText, "密文"); if (encryptedBytes.length == 0) { throw new IllegalBlockSizeException("密文为空"); } if (encryptedBytes.length % BLOCK_SIZE != 0) { throw new IllegalBlockSizeException( "密文长度不是 AES 块大小(" + BLOCK_SIZE + ")的整数倍, 实际 " + encryptedBytes.length + " 字节"); } byte[] ivBytes = Arrays.copyOf(keyBytes, BLOCK_SIZE); Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(ivBytes)); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); byte[] unpaddedData = unpadPkcs7(decryptedBytes); try { return new String(unpaddedData, StandardCharsets.UTF_8); } finally { // 敏感数据及时清零,减少在堆中的驻留时间 Arrays.fill(decryptedBytes, (byte) 0); Arrays.fill(unpaddedData, (byte) 0); Arrays.fill(keyBytes, (byte) 0); } } /** 校验并剥离 PKCS#7 填充。 */ private static byte[] unpadPkcs7(byte[] data) throws BadPaddingException { int len = data.length; if (len == 0 || len % BLOCK_SIZE != 0) { throw new BadPaddingException("PKCS#7 填充无效: 数据长度 " + len); } // Java 的 byte 有符号,必须 & 0xFF 转为无符号,否则末字节 >= 0x80 时得到负数 int paddingLength = data[len – 1] & 0xFF; if (paddingLength == 0 && allowZeroPadding) { return data.clone(); } if (paddingLength < 1 || paddingLength > BLOCK_SIZE) { throw new BadPaddingException("PKCS#7 填充无效: 填充长度 " + paddingLength); } // 全部填充字节都要校验;用 MessageDigest.isEqual 做常量时间比较, // 缓解 padding oracle 时序侧信道 byte[] actual = Arrays.copyOfRange(data, len – paddingLength, len); byte[] expected = new byte[paddingLength]; Arrays.fill(expected, (byte) paddingLength); if (!MessageDigest.isEqual(actual, expected)) { throw new BadPaddingException("PKCS#7 填充无效"); } return Arrays.copyOf(data, len – paddingLength); } /** 为标准 base64 字符串补齐 '=' padding。 */ private static String padBase64(String s) { int remainder = s.length() % 4; if (remainder == 0) { return s; } return s + "=".repeat(4 – remainder); } /** base64 解码,把 IllegalArgumentException 转为受检异常。 */ private static byte[] decodeBase64(String s, String what) throws GeneralSecurityException { try { return Base64.getDecoder().decode(s); } catch (IllegalArgumentException e) { throw new GeneralSecurityException(what + " base64 解码失败: " + e.getMessage(), e); } } private static boolean isValidKeyLength(int len) { for (int valid : VALID_KEY_LENGTHS) { if (valid == len) { return true; } } return false; } /** 密钥长度非法,便于调用方区分处理。 */ public static class InvalidKeyLengthException extends GeneralSecurityException { private static final long serialVersionUID = 1L; public InvalidKeyLengthException(int actual) { super("AES 密钥 base64 解码后长度必须为 16/24/32 字节, 实际 " + actual + " 字节"); } } public static void main(String[] args) { String encryptedText = "your base64 encoded encrypted text"; String key = "your base64 encoded key"; try { String decryptedText = decrypt(encryptedText, key); System.out.println("Decrypted Text: " + decryptedText); } catch (NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { // 数据/填充相关,通常意味着密钥不对或密文被篡改 System.err.println("解密失败(数据或密钥错误): " + e.getMessage()); } catch (GeneralSecurityException e) { System.err.println("解密失败: " + e.getMessage()); } catch (IllegalArgumentException e) { System.err.println("参数错误: " + e.getMessage()); } }}
// AES-256-CBC解密(IV 取自密钥前 16 字节,PKCS#7 手工去填充)//// 编译:// g++ -std=c++17 -O2 aes_decrypt.cpp -o aes_decrypt \\// -I$(brew –prefix openssl@3)/include -L$(brew –prefix openssl@3)/lib -lcrypto//// macOS Apple Silicon 若 Homebrew 的 OpenSSL 只有 x86_64 版本,需补 -arch x86_64,// 并在运行时设置 DYLD_LIBRARY_PATH=$(brew –prefix openssl@3)/lib#include <cstring>#include <iostream>#include <stdexcept>#include <string>#include <vector>// AES_set_decrypt_key / AES_cbc_encrypt 在 OpenSSL 3.x 已标记 deprecated,// 此处局部静默相关告警。迁移建议见文末。#ifdef __clang__#pragma clang diagnostic push#pragma clang diagnostic ignored "-Wdeprecated-declarations"#elif defined(__GNUC__)#pragma GCC diagnostic push#pragma GCC diagnostic ignored "-Wdeprecated-declarations"#endif#include <openssl/aes.h>#include <openssl/bio.h>#include <openssl/buffer.h>#include <openssl/crypto.h>#include <openssl/evp.h>namespace aescbc {constexpr size_t kAesKeySize = 32; // AES-256constexpr size_t kBlockSize = 16; // AES_BLOCK_SIZE// 为 true 时接受零填充(ZeroPadding)密文,即末字节为 0x00 时不剥离任何字节。// 仅在对端使用 ZeroPadding 时开启;标准 PKCS#7 请保持 false。bool g_allow_zero_padding = false;class DecryptError : public std::runtime_error { public: explicit DecryptError(const std::string& msg) : std::runtime_error(msg) {}};// RAII 包装 BIO 链,确保异常路径也能释放。class BioChain { public: explicit BioChain(BIO* bio) : bio_(bio) {} ~BioChain() { if (bio_ != nullptr) BIO_free_all(bio_); } BioChain(const BioChain&) = delete; BioChain& operator=(const BioChain&) = delete; BIO* get() const { return bio_; } private: BIO* bio_;};// 为标准 base64 字符串补齐 '=' padding。std::string PadBase64(const std::string& s) { const size_t remainder = s.size() % 4; if (remainder == 0) return s; return s + std::string(4 – remainder, '=');}// base64 解码,返回二进制字节。//// 注意:结果必须用 std::string(ptr, len) 构造,且 len 取自 BIO_read 的返回值。// 密钥与密文是任意二进制,含 0x00 是常态;若从 char* 隐式构造会在首个 0x00 处截断。std::string Base64Decode(const std::string& encoded, const char* what) { if (encoded.empty()) return std::string(); BIO* b64 = BIO_new(BIO_f_base64()); if (b64 == nullptr) throw DecryptError("BIO_new(base64) 失败"); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO* bmem = BIO_new_mem_buf(encoded.data(), static_cast<int>(encoded.size())); if (bmem == nullptr) { BIO_free_all(b64); throw DecryptError("BIO_new_mem_buf 失败"); } BioChain chain(BIO_push(b64, bmem)); // base64 解码后长度必定 <= 输入长度 std::vector<unsigned char> buffer(encoded.size()); const int decoded_len = BIO_read(chain.get(), buffer.data(), static_cast<int>(buffer.size())); if (decoded_len < 0) { throw DecryptError(std::string(what) + " base64 解码失败"); } return std::string(reinterpret_cast<char*>(buffer.data()), static_cast<size_t>(decoded_len));}// 校验并剥离 PKCS#7 填充。//// 注意:padding 可达255,必须先校验范围。否则 data.end() – padding// 会构成迭代器越界(未定义行为)。std::string UnpadPkcs7(const std::vector<unsigned char>& data) { const size_t n = data.size(); if (n == 0 || n % kBlockSize != 0) { throw DecryptError("PKCS#7 填充无效: 数据长度 " + std::to_string(n)); } const size_t padding = static_cast<size_t>(data[n – 1]); if (padding == 0 && g_allow_zero_padding) { return std::string(reinterpret_cast<const char*>(data.data()), n); } if (padding < 1 || padding > kBlockSize || padding > n) { throw DecryptError("PKCS#7 填充无效: 填充长度 " + std::to_string(padding)); } // 全部填充字节都要校验;CRYPTO_memcmp 为常量时间比较,缓解 padding oracle std::vector<unsigned char> expected(padding, static_cast<unsigned char>(padding)); if (CRYPTO_memcmp(data.data() + (n – padding), expected.data(), padding) != 0) { throw DecryptError("PKCS#7 填充无效"); } return std::string(reinterpret_cast<const char*>(data.data()), n – padding);}std::string AesDecrypt(const std::string& encrypted_text, const std::string& key) { const std::string decoded_key = Base64Decode(PadBase64(key), "密钥"); if (decoded_key.size() != kAesKeySize) { throw DecryptError("AES 密钥 base64 解码后必须为 32 字节 (AES-256), 实际 " + std::to_string(decoded_key.size()) + " 字节"); } unsigned char aes_key[kAesKeySize]; std::memcpy(aes_key, decoded_key.data(), kAesKeySize); unsigned char iv[kBlockSize]; std::memcpy(iv, aes_key, kBlockSize); const std::string decoded_text = Base64Decode(encrypted_text, "密文"); if (decoded_text.empty()) { throw DecryptError("密文为空"); } if (decoded_text.size() % kBlockSize != 0) { throw DecryptError("密文长度不是 AES 块大小(16)的整数倍, 实际 " + std::to_string(decoded_text.size()) + " 字节"); } AES_KEY aes_decrypt_key; if (AES_set_decrypt_key(aes_key, 256, &aes_decrypt_key) < 0) { OPENSSL_cleanse(aes_key, sizeof(aes_key)); throw DecryptError("AES_set_decrypt_key 失败"); } // AES_cbc_encrypt 会就地修改 iv,故传入副本 std::vector<unsigned char> decrypted_bytes(decoded_text.size()); unsigned char iv_copy[kBlockSize]; std::memcpy(iv_copy, iv, kBlockSize); AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(decoded_text.data()), decrypted_bytes.data(), decoded_text.size(), &aes_decrypt_key, iv_copy, AES_DECRYPT); std::string result; try { result = UnpadPkcs7(decrypted_bytes); } catch (…) { OPENSSL_cleanse(aes_key, sizeof(aes_key)); OPENSSL_cleanse(&aes_decrypt_key, sizeof(aes_decrypt_key)); if (!decrypted_bytes.empty()) OPENSSL_cleanse(decrypted_bytes.data(), decrypted_bytes.size()); throw; } // 敏感数据及时清零(OPENSSL_cleanse 不会被编译器优化掉) OPENSSL_cleanse(aes_key, sizeof(aes_key)); OPENSSL_cleanse(&aes_decrypt_key, sizeof(aes_decrypt_key)); if (!decrypted_bytes.empty()) OPENSSL_cleanse(decrypted_bytes.data(), decrypted_bytes.size()); return result;}} // namespace aescbc#ifdef __clang__#pragma clang diagnostic pop#elif defined(__GNUC__)#pragma GCC diagnostic pop#endif#ifndef AES_DECRYPT_NO_MAINint main() { const std::string encrypted_text = "your base64 encoded encrypted text"; const std::string key = "your base64 encoded key"; try { const std::string decrypted_text = aescbc::AesDecrypt(encrypted_text, key); std::cout << "Decrypted Text: " << decrypted_text << std::endl; } catch (const aescbc::DecryptError& e) { std::cerr << "解密失败: " << e.what() << std::endl; return 1; } return 0;}#endif
<?php/** * AES-256-CBC 解密(IV 取自密钥前 16 字节)。 * * 注意:openssl_decrypt 配合 OPENSSL_RAW_DATA 会自动校验并剥离 PKCS#7 填充, * 因此 aes_decrypt() 无需手工处理填充,填充非法时直接返回 false。 * 若需要与手工剥离填充的实现(Go/Java/C++/Python)严格对齐, * 或需兼容零填充(ZeroPadding)密文,请改用 aes_decrypt_manual_unpad()。 */declare(strict_types=1);const AES_BLOCK_SIZE = 16;const AES_KEY_SIZE = 32; // AES-256const AES_CIPHER = 'AES-256-CBC';/** 解密相关异常基类 */class DecryptException extends RuntimeException {}class InvalidKeyLengthException extends DecryptException {}/** 为标准 base64 字符串补齐 '=' padding。 */function pad_base64(string $s): string{ $remainder = strlen($s) % 4; if ($remainder !== 0) { $s .= str_repeat('=', 4 – $remainder); } return $s;}/** 严格模式 base64 解码,非法字符会报错而非被静默丢弃。 */function b64decode_strict(string $s, string $what): string{ $decoded = base64_decode($s, true); if ($decoded === false) { throw new DecryptException("{$what} base64 解码失败(非法 base64 字符或 padding 错误)"); } return $decoded;}/** * 解密 base64 编码的密文,PKCS#7 填充由 openssl 自动处理。 * * @throws DecryptException */function aes_decrypt(string $encrypted_text, string $key): string{ $decoded_key = b64decode_strict(pad_base64($key), '密钥'); if (strlen($decoded_key) !== AES_KEY_SIZE) { throw new InvalidKeyLengthException( sprintf( 'AES 密钥 base64 解码后必须为 %d 字节 (AES-256), 实际 %d 字节', AES_KEY_SIZE, strlen($decoded_key) ) ); } $aes_key = $decoded_key; $iv = substr($aes_key, 0, AES_BLOCK_SIZE); $decoded_text = b64decode_strict($encrypted_text, '密文'); if ($decoded_text === '') { throw new DecryptException('密文为空'); } if (strlen($decoded_text) % AES_BLOCK_SIZE !== 0) { throw new DecryptException( sprintf( '密文长度不是 AES 块大小(%d)的整数倍, 实际 %d 字节', AES_BLOCK_SIZE, strlen($decoded_text) ) ); } $decrypted_text = openssl_decrypt( $decoded_text, AES_CIPHER, $aes_key, OPENSSL_RAW_DATA, $iv ); // openssl_decrypt 失败返回 false,必须检查,否则会静默得到空字符串 if ($decrypted_text === false) { $errors = []; while (($err = openssl_error_string()) !== false) { $errors[] = $err; } throw new DecryptException( '解密失败(通常意味着密钥错误、密文被篡改或填充非法)' . ($errors !== [] ? ': ' . implode('; ', $errors) : '') ); } return $decrypted_text;}/** * 解密并手工剥离 PKCS#7 填充,行为与 Go/Java/C++/Python 实现一致。 * * @param bool $allow_zero_padding 为 true 时接受零填充密文(末字节 0x00 时不剥离) * @throws DecryptException */function aes_decrypt_manual_unpad( string $encrypted_text, string $key, bool $allow_zero_padding = false): string { $decoded_key = b64decode_strict(pad_base64($key), '密钥'); if (strlen($decoded_key) !== AES_KEY_SIZE) { throw new InvalidKeyLengthException( sprintf('AES 密钥必须为 %d 字节, 实际 %d', AES_KEY_SIZE, strlen($decoded_key)) ); } $iv = substr($decoded_key, 0, AES_BLOCK_SIZE); $decoded_text = b64decode_strict($encrypted_text, '密文'); if ($decoded_text === '') { throw new DecryptException('密文为空'); } if (strlen($decoded_text) % AES_BLOCK_SIZE !== 0) { throw new DecryptException('密文长度不是 16 的整数倍'); } // OPENSSL_ZERO_PADDING 表示"不自动处理填充",而非"使用零填充" $raw = openssl_decrypt( $decoded_text, AES_CIPHER, $decoded_key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv ); if ($raw === false) { throw new DecryptException('openssl_decrypt 失败'); } return unpad_pkcs7($raw, $allow_zero_padding);}/** * 校验并剥离 PKCS#7 填充。 * * @throws DecryptException */function unpad_pkcs7(string $data, bool $allow_zero_padding = false): string{ $n = strlen($data); if ($n === 0 || $n % AES_BLOCK_SIZE !== 0) { throw new DecryptException("PKCS#7 填充无效: 数据长度 {$n}"); } $padding = ord($data[$n – 1]); // ord() 返回 0..255,无符号 if ($padding === 0 && $allow_zero_padding) { return $data; } if ($padding < 1 || $padding > AES_BLOCK_SIZE || $padding > $n) { throw new DecryptException("PKCS#7 填充无效: 填充长度 {$padding}"); } // 全部填充字节都要校验;hash_equals 为常量时间比较,缓解 padding oracle $actual = substr($data, $n – $padding); $expected = str_repeat(chr($padding), $padding); if (!hash_equals($expected, $actual)) { throw new DecryptException('PKCS#7 填充无效'); } return substr($data, 0, $n – $padding);}// ===================== 示例入口 =====================if (PHP_SAPI === 'cli' && realpath($argv[0] ?? '') === realpath(__FILE__)) { $encrypted_text = 'your base64 encoded encrypted text'; $key = 'your base64 encoded key'; try { $decrypted_text = aes_decrypt($encrypted_text, $key); echo 'Decrypted Text: ' . $decrypted_text . PHP_EOL; } catch (DecryptException $e) { fwrite(STDERR, '解密失败: ' . $e->getMessage() . PHP_EOL); exit(1); }}
"""AES-256-CBC 解密。- 密钥经 base64 解码后作为 AES-256 key- IV 取自密钥前 16 字节- PKCS#7 填充由本模块手工剥离依赖:pycryptodome (pip install pycryptodome)"""from __future__ import annotationsimport base64import binasciiimport hmacfrom Crypto.Cipher import AESBLOCK_SIZE = 16AES_KEY_SIZE = 32 # AES-256# 为 True 时接受零填充(ZeroPadding)密文,即末字节为 0x00 时不剥离任何字节。# 仅在对端使用 ZeroPadding 时开启;标准 PKCS#7 请保持 False。ALLOW_ZERO_PADDING = Falseclass DecryptError(ValueError): """解密相关错误的统一基类。"""class InvalidKeyLengthError(DecryptError): passclass BadPaddingError(DecryptError): passdef _pad_base64(s: str) -> str: """为标准 base64 字符串补齐 '=' padding。""" remainder = len(s) % 4 if remainder: s += "=" * (4 – remainder) return sdef _b64decode(s: str | bytes, what: str) -> bytes: """严格模式 base64 解码,非法字符会报错而非被静默忽略。""" try: return base64.b64decode(s, validate=True) except (binascii.Error, ValueError) as exc: raise DecryptError(f"{what} base64 解码失败: {exc}") from excdef unpad_pkcs7(data: bytes, block_size: int = BLOCK_SIZE) -> bytes: """校验并剥离 PKCS#7 填充。 注意:不可写成 data[:-padding]。Python 中 x[:-0] 等价于 x[:0], 当 padding 为 0 时会静默返回空串;padding > len(data) 时同样如此。 因此必须先校验 padding 取值范围。 """ n = len(data) if n == 0 or n % block_size != 0: raise BadPaddingError(f"PKCS#7 填充无效: 数据长度 {n}") padding = data[-1] # 索引 bytes 得到 int,无符号 if padding == 0 and ALLOW_ZERO_PADDING: return data if padding < 1 or padding > block_size or padding > n: raise BadPaddingError(f"PKCS#7 填充无效: 填充长度 {padding}") # 全部填充字节都要校验;compare_digest 为常量时间比较, # 缓解 padding oracle 时序侧信道 if not hmac.compare_digest(data[-padding:], bytes([padding]) * padding): raise BadPaddingError("PKCS#7 填充无效") return data[:-padding]def aes_decrypt(encrypted_text: str, key: str) -> str: """解密 base64 编码的 AES-256-CBC 密文,返回 UTF-8 字符串。""" decoded_key = _b64decode(_pad_base64(key), "密钥") if len(decoded_key) != AES_KEY_SIZE: raise InvalidKeyLengthError( f"AES 密钥 base64 解码后必须为 {AES_KEY_SIZE} 字节 (AES-256), " f"实际 {len(decoded_key)} 字节" ) aes_key = decoded_key iv = aes_key[:BLOCK_SIZE] decoded_text = _b64decode(encrypted_text, "密文") if not decoded_text: raise DecryptError("密文为空") if len(decoded_text) % BLOCK_SIZE != 0: raise DecryptError( f"密文长度不是 AES 块大小({BLOCK_SIZE})的整数倍, " f"实际 {len(decoded_text)} 字节" ) cipher = AES.new(aes_key, AES.MODE_CBC, iv) plaintext = unpad_pkcs7(cipher.decrypt(decoded_text)) try: return plaintext.decode("utf-8") except UnicodeDecodeError as exc: raise DecryptError( f"解密结果不是合法 UTF-8(通常意味着密钥错误或密文被篡改): {exc}" ) from excdef main() -> int: encrypted_text = "your base64 encoded encrypted text" key = "your base64 encoded key" try: decrypted_text = aes_decrypt(encrypted_text, key) except DecryptError as exc: print(f"解密失败: {exc}") return 1 print("Decrypted Text:", decrypted_text) return 0if __name__ == "__main__": raise SystemExit(main())