update version
This commit is contained in:
@@ -1,107 +1,235 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:pointycastle/export.dart';
|
||||
|
||||
class AesDecrypt {
|
||||
static const String _cn = 'i0.a';
|
||||
|
||||
static const List<int> _obf = <int>[
|
||||
0xED,
|
||||
0x7A,
|
||||
0x7F,
|
||||
0x3F,
|
||||
0x9E,
|
||||
0x6E,
|
||||
0xE7,
|
||||
0xF9,
|
||||
0x06,
|
||||
0xA0,
|
||||
0xA8,
|
||||
0xE4,
|
||||
0x16,
|
||||
0xE5,
|
||||
0x69,
|
||||
0x70,
|
||||
];
|
||||
|
||||
/// Java String.hashCode() 等价实现(32-bit signed)
|
||||
static int javaStringHashCode(String str) {
|
||||
int h = 0;
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
h = _toSigned32(h * 31 + str.codeUnitAt(i));
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static int _toSigned32(int value) {
|
||||
value &= 0xFFFFFFFF;
|
||||
if ((value & 0x80000000) != 0) {
|
||||
return value - 0x100000000;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int _unsignedRightShift32(int value, int shift) {
|
||||
return (value & 0xFFFFFFFF) >> shift;
|
||||
}
|
||||
|
||||
static Uint8List deriveKeyBytes16() {
|
||||
final int seed = _toSigned32(javaStringHashCode(_cn) ^ 0x5f3759df);
|
||||
|
||||
final Uint8List keyBytes = Uint8List(16);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
final int shift = (i & 3) * 8;
|
||||
final int shifted = _unsignedRightShift32(seed, shift);
|
||||
final int mask = (((shifted & 0xff) ^ ((i * 17 + 31) & 0xff)) & 0xff);
|
||||
keyBytes[i] = (_obf[i] ^ mask) & 0xff;
|
||||
}
|
||||
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
static Uint8List _aesEcbPkcs7(bool forEncryption, Uint8List input) {
|
||||
final key = deriveKeyBytes16();
|
||||
|
||||
final cipher = PaddedBlockCipherImpl(
|
||||
PKCS7Padding(),
|
||||
ECBBlockCipher(AESEngine()),
|
||||
);
|
||||
|
||||
cipher.init(
|
||||
forEncryption,
|
||||
PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
|
||||
KeyParameter(key),
|
||||
null,
|
||||
),
|
||||
);
|
||||
|
||||
return cipher.process(input);
|
||||
}
|
||||
|
||||
/// 对应 JS / Java encrypt(String) -> Base64.NO_WRAP
|
||||
static String encrypt(String plainText) {
|
||||
try {
|
||||
final Uint8List input = Uint8List.fromList(utf8.encode(plainText));
|
||||
final Uint8List encrypted = _aesEcbPkcs7(true, input);
|
||||
return base64.encode(encrypted);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 对应 JS / Java decrypt(String base64Encrypted) -> UTF-8
|
||||
static String? decrypt(String base64Encrypted) {
|
||||
try {
|
||||
final Uint8List encrypted = Uint8List.fromList(
|
||||
base64.decode(base64Encrypted),
|
||||
);
|
||||
final Uint8List decrypted = _aesEcbPkcs7(false, encrypted);
|
||||
final String text = utf8.decode(decrypted, allowMalformed: false);
|
||||
return text.isNotEmpty ? text : null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:pointycastle/export.dart';
|
||||
|
||||
class AesDecrypt {
|
||||
static const String _cn = 'i0.a';
|
||||
|
||||
static const List<int> _obf = <int>[
|
||||
0xED,
|
||||
0x7A,
|
||||
0x7F,
|
||||
0x3F,
|
||||
0x9E,
|
||||
0x6E,
|
||||
0xE7,
|
||||
0xF9,
|
||||
0x06,
|
||||
0xA0,
|
||||
0xA8,
|
||||
0xE4,
|
||||
0x16,
|
||||
0xE5,
|
||||
0x69,
|
||||
0x70,
|
||||
];
|
||||
|
||||
/// Java String.hashCode() 等价实现(32-bit signed)
|
||||
static int javaStringHashCode(String str) {
|
||||
int h = 0;
|
||||
for (int i = 0; i < str.length; i++) {
|
||||
h = _toSigned32(h * 31 + str.codeUnitAt(i));
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static int _toSigned32(int value) {
|
||||
value &= 0xFFFFFFFF;
|
||||
if ((value & 0x80000000) != 0) {
|
||||
return value - 0x100000000;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int _unsignedRightShift32(int value, int shift) {
|
||||
return (value & 0xFFFFFFFF) >> shift;
|
||||
}
|
||||
|
||||
static Uint8List deriveKeyBytes16() {
|
||||
final int seed = _toSigned32(javaStringHashCode(_cn) ^ 0x5f3759df);
|
||||
|
||||
final Uint8List keyBytes = Uint8List(16);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
final int shift = (i & 3) * 8;
|
||||
final int shifted = _unsignedRightShift32(seed, shift);
|
||||
final int mask = (((shifted & 0xff) ^ ((i * 17 + 31) & 0xff)) & 0xff);
|
||||
keyBytes[i] = (_obf[i] ^ mask) & 0xff;
|
||||
}
|
||||
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
static Uint8List _aesEcbPkcs7(bool forEncryption, Uint8List input) {
|
||||
final key = deriveKeyBytes16();
|
||||
|
||||
final cipher = PaddedBlockCipherImpl(
|
||||
PKCS7Padding(),
|
||||
ECBBlockCipher(AESEngine()),
|
||||
);
|
||||
|
||||
cipher.init(
|
||||
forEncryption,
|
||||
PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
|
||||
KeyParameter(key),
|
||||
null,
|
||||
),
|
||||
);
|
||||
|
||||
return cipher.process(input);
|
||||
}
|
||||
|
||||
/// 对应 JS / Java encrypt(String) -> Base64.NO_WRAP
|
||||
static String encrypt(String plainText) {
|
||||
try {
|
||||
final Uint8List input = Uint8List.fromList(utf8.encode(plainText));
|
||||
final Uint8List encrypted = _aesEcbPkcs7(true, input);
|
||||
return base64.encode(encrypted);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 对应 JS / Java decrypt(String base64Encrypted) -> UTF-8
|
||||
static String? decrypt(String base64Encrypted) {
|
||||
try {
|
||||
final Uint8List encrypted = Uint8List.fromList(
|
||||
base64.decode(base64Encrypted),
|
||||
);
|
||||
final Uint8List decrypted = _aesEcbPkcs7(false, encrypted);
|
||||
final String text = utf8.decode(decrypted, allowMalformed: false);
|
||||
return text.isNotEmpty ? text : null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 新增:支持自定义 key / iv
|
||||
// AES/CBC/PKCS7
|
||||
// =========================
|
||||
|
||||
static Uint8List _normalizeKey(String key) {
|
||||
final Uint8List keyBytes = Uint8List.fromList(utf8.encode(key));
|
||||
|
||||
if (keyBytes.length != 16 &&
|
||||
keyBytes.length != 24 &&
|
||||
keyBytes.length != 32) {
|
||||
throw ArgumentError('AES key length must be 16/24/32 bytes.');
|
||||
}
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
static Uint8List _normalizeIv(String iv) {
|
||||
final Uint8List ivBytes = Uint8List.fromList(utf8.encode(iv));
|
||||
if (ivBytes.length != 16) {
|
||||
throw ArgumentError('AES CBC iv length must be 16 bytes.');
|
||||
}
|
||||
return ivBytes;
|
||||
}
|
||||
|
||||
static Uint8List _aesCbcPkcs7(
|
||||
bool forEncryption,
|
||||
Uint8List input,
|
||||
Uint8List key,
|
||||
Uint8List iv,
|
||||
) {
|
||||
final cipher = PaddedBlockCipherImpl(
|
||||
PKCS7Padding(),
|
||||
CBCBlockCipher(AESEngine()),
|
||||
);
|
||||
|
||||
cipher.init(
|
||||
forEncryption,
|
||||
PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
|
||||
ParametersWithIV<KeyParameter>(KeyParameter(key), iv),
|
||||
null,
|
||||
),
|
||||
);
|
||||
|
||||
return cipher.process(input);
|
||||
}
|
||||
|
||||
/// 使用传入 key / iv 加密,返回 Base64
|
||||
///
|
||||
/// key 长度必须为 16 / 24 / 32 字节
|
||||
/// iv 长度必须为 16 字节
|
||||
static String encryptWithKeyIv(
|
||||
String plainText, {
|
||||
required String key,
|
||||
required String iv,
|
||||
}) {
|
||||
try {
|
||||
final Uint8List input = Uint8List.fromList(utf8.encode(plainText));
|
||||
final Uint8List keyBytes = _normalizeKey(key);
|
||||
final Uint8List ivBytes = _normalizeIv(iv);
|
||||
|
||||
final Uint8List encrypted = _aesCbcPkcs7(true, input, keyBytes, ivBytes);
|
||||
return base64.encode(encrypted);
|
||||
} catch (_) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/// 使用传入 key / iv 解密 Base64 密文,返回 UTF-8 字符串
|
||||
///
|
||||
/// key 长度必须为 16 / 24 / 32 字节
|
||||
/// iv 长度必须为 16 字节
|
||||
static String? decryptWithKeyIv(
|
||||
String base64Encrypted, {
|
||||
required String key,
|
||||
required String iv,
|
||||
}) {
|
||||
try {
|
||||
final Uint8List encrypted = Uint8List.fromList(
|
||||
base64.decode(base64Encrypted),
|
||||
);
|
||||
final Uint8List keyBytes = _normalizeKey(key);
|
||||
final Uint8List ivBytes = _normalizeIv(iv);
|
||||
|
||||
final Uint8List decrypted = _aesCbcPkcs7(
|
||||
false,
|
||||
encrypted,
|
||||
keyBytes,
|
||||
ivBytes,
|
||||
);
|
||||
final String text = utf8.decode(decrypted, allowMalformed: false);
|
||||
return text.isNotEmpty ? text : null;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================
|
||||
// 可选新增:支持直接传 Uint8List
|
||||
// =========================
|
||||
|
||||
static Uint8List encryptBytesWithKeyIv(
|
||||
Uint8List plainBytes, {
|
||||
required Uint8List key,
|
||||
required Uint8List iv,
|
||||
}) {
|
||||
if (key.length != 16 && key.length != 24 && key.length != 32) {
|
||||
throw ArgumentError('AES key length must be 16/24/32 bytes.');
|
||||
}
|
||||
if (iv.length != 16) {
|
||||
throw ArgumentError('AES CBC iv length must be 16 bytes.');
|
||||
}
|
||||
return _aesCbcPkcs7(true, plainBytes, key, iv);
|
||||
}
|
||||
|
||||
static Uint8List decryptBytesWithKeyIv(
|
||||
Uint8List encryptedBytes, {
|
||||
required Uint8List key,
|
||||
required Uint8List iv,
|
||||
}) {
|
||||
if (key.length != 16 && key.length != 24 && key.length != 32) {
|
||||
throw ArgumentError('AES key length must be 16/24/32 bytes.');
|
||||
}
|
||||
if (iv.length != 16) {
|
||||
throw ArgumentError('AES CBC iv length must be 16 bytes.');
|
||||
}
|
||||
return _aesCbcPkcs7(false, encryptedBytes, key, iv);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user