术语说明
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 的加密过程
msg_encrypt = Base64_Encode( AES_Encrypt[msg + $key] )。AES 加密的 buf 由明文 msg 和 $key 组成。
注意:
开发者无需自行加密,加密由会议业务内部实现。
加密方案对应的解密方案
取出返回的 JSON 中的 data 字段。
对密文 BASE64 解码:
aes_msg=Base64_Decode(data)。使用 AESKey 做 AES 解密:
msg=AES_Decrypt(aes_msg)。解密示例代码
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 要求输入非空且为块大小整数倍,否则 panicif 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_64
Runtime 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 oraclestd::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) % 4if 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 dataif 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_keyiv = 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 1print("Decrypted Text:", decrypted_text)return 0if __name__ == "__main__":raise SystemExit(main())