在 Dart 中解密 AES/CBC/PKCS5Padding 加密
Decrypt AES/CBC/PKCS5Padding Encryption in Dart
我已经在 java 中有了加密代码。现在我想从我的服务器使用 API。即使尝试了各种教程和示例代码,我也无法成功解密哈希。
我知道固定盐,完全不推荐静脉注射。但为了简单起见和理解问题,我将 salt 和 IV 保留为“00000000000000000000000000000000”;
从 Java = "XjxCg0KK0ZDWa4XMFhykIw==" 加密后的哈希值;
使用的私钥 = "Mayur12354673645"
谁能帮我用 dart 解密上面的字符串。
JAVA代码
public String encrypt(String salt, String iv, String passphrase,
String plaintext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext
.getBytes("UTF-8"));
return base64(encrypted);
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
public String decrypt(String salt, String iv, String passphrase,
String ciphertext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv,
base64(ciphertext));
return new String(decrypted, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt),
iterationCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return key;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv,
byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
return cipher.doFinal(bytes);
} catch (Exception e) {
e.printStackTrace();
throw fail(e);
}
}
我的飞镖代码
import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/block/modes/cbc.dart';
import 'package:pointycastle/digests/sha1.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/macs/hmac.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'dart:developer';
import 'package:pointycastle/random/fortuna_random.dart';
const KEY_SIZE = 16;
const ITERATION_COUNT = 5;
class EncryptionHandler {
static const CBC_MODE = 'CBC';
static Uint8List deriveKey(dynamic password,
{String salt = '0000000000000000',
int iterationCount = ITERATION_COUNT,
int derivedKeyLength = KEY_SIZE}) {
if (password == null || password.isEmpty) {
throw new ArgumentError('password must not be empty');
}
if (password is String) {
password = createUint8ListFromString(password);
}
Uint8List saltBytes = createUint8ListFromString(salt);
String hexSalt = formatBytesAsHexString(saltBytes);
KeyDerivator keyDerivator =
new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
Pbkdf2Parameters params =
new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
keyDerivator.init(params);
return keyDerivator.process(password);
}
Uint8List createUint8ListFromHexString(String hex) {
var result = new Uint8List(hex.length ~/ 2);
for (var i = 0; i < hex.length; i += 2) {
var num = hex.substring(i, i + 2);
var byte = int.parse(num, radix: 16);
result[i ~/ 2] = byte;
}
return result;
}
static String formatBytesAsHexString(Uint8List bytes) {
var result = new StringBuffer();
for (var i = 0; i < bytes.lengthInBytes; i++) {
var part = bytes[i];
result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
}
return result.toString();
}
static Uint8List pad(Uint8List src, int blockSize) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = blockSize - (src.length % blockSize);
var out = new Uint8List(src.length + padLength)..setAll(0, src);
pad.addPadding(out, src.length);
return out;
}
static Uint8List unpad(Uint8List src) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = pad.padCount(src);
int len = src.length - padLength;
return new Uint8List(len)..setRange(0, len, src);
}
static String encrypt(String password, String plaintext,
{String mode = CBC_MODE}) {
Uint8List derivedKey = deriveKey(password);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var rnd = FortunaRandom();
rnd.seed(keyParam);
Uint8List iv = createUint8ListFromString("0000000000000000");
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
cipher = new CBCBlockCipher(aes);
cipher.init(true, params);
Uint8List textBytes = createUint8ListFromString(plaintext);
Uint8List paddedText = pad(textBytes, aes.blockSize);
Uint8List cipherBytes = _processBlocks(cipher, paddedText);
Uint8List cipherIvBytes = new Uint8List(cipherBytes.length + iv.length)
..setAll(0, iv)
..setAll(iv.length, cipherBytes);
return base64.encode(cipherIvBytes);
}
static String decrypt(String password, String ciphertext) {
log('Password: $password');
Uint8List derivedKey = deriveKey(password);
log('derivedKey: $derivedKey');
KeyParameter keyParam = new KeyParameter(derivedKey);
log('keyParam: $keyParam');
BlockCipher aes = new AESFastEngine();
Uint8List cipherIvBytes = base64.decode(ciphertext);
log('cipherIvBytes: $cipherIvBytes');
Uint8List iv = createUint8ListFromString("0000000000000000");
// Uint8List iv = new Uint8List(aes.blockSize)
// ..setRange(0, aes.blockSize, cipherIvBytes);
log('iv: $iv');
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
log('params: $params');
cipher = new CBCBlockCipher(aes);
log('cipher: $cipher');
cipher.init(false, params);
int cipherLen = cipherIvBytes.length - aes.blockSize;
Uint8List cipherBytes = new Uint8List(cipherLen)
..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
Uint8List paddedText = _processBlocks(cipher, cipherBytes);
log('cipher: $paddedText');
Uint8List textBytes = paddedText;
// Uint8List textBytes = unpad(paddedText);
return new String.fromCharCodes(textBytes);
}
static Uint8List createUint8ListFromString(String s) {
var ret = new Uint8List(s.length);
for (var i = 0; i < s.length; i++) {
ret[i] = s.codeUnitAt(i);
}
return ret;
}
static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
var out = new Uint8List(inp.lengthInBytes);
for (var offset = 0; offset < inp.lengthInBytes;) {
var len = cipher.processBlock(inp, offset, out, offset);
offset += len;
}
return out;
}
}
可以通过使用现有的 Dart 库将 二进制转换为十六进制 来简化代码,反之亦然。 PointyCastle 还支持 (PKCS7) 填充,因此不需要自定义实现,这也减少了代码。在 Internet 上,您可以找到 AES/CBC/PKCS7Padding 的几个飞镖实现与使用 PointyCastle 的 PBKDF2 相结合,例如here and here.
使用 pointycastle and convert 包进行解密的可能的 Dart 实现例如是(为简单起见,没有异常处理):
import 'dart:typed_data';
import "package:pointycastle/export.dart";
import 'package:convert/convert.dart';
import 'dart:convert';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
CBCBlockCipher cipher = new CBCBlockCipher(new AESFastEngine());
ParametersWithIV<KeyParameter> params = new ParametersWithIV<KeyParameter>(new KeyParameter(key), iv);
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = new PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(params, null);
PaddedBlockCipherImpl paddingCipher = new PaddedBlockCipherImpl(new PKCS7Padding(), cipher);
paddingCipher.init(false, paddingParams);
return paddingCipher.process(ciphertext);
}
static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
KeyDerivator derivator = new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
Pbkdf2Parameters params = new Pbkdf2Parameters(salt, 5, 16);
derivator.init(params);
return derivator.process(passphrase);
}
贴上测试数据:
String saltHex = '00000000000000000000000000000000';
String ivHex = '00000000000000000000000000000000';
String passphraseUtf8 = 'Mayur12354673645';
String ciphertextBase64 = "XjxCg0KK0ZDWa4XMFhykIw==";
Uint8List salt = hex.decode(saltHex);
Uint8List passphrase = utf8.encode(passphraseUtf8);
Uint8List key = generateKey(salt, passphrase);
Uint8List ciphertext = base64.decode(ciphertextBase64);
Uint8List iv = hex.decode(ivHex);
Uint8List decrypted = decrypt(ciphertext, key, iv);
print(utf8.decode(decrypted)); // This is working
密文可以解密为:有效。
PointyCastle 的替代品是 cryptography 包,它在当前情况下允许更紧凑的实现:
import 'package:cryptography/cryptography.dart';
import 'package:convert/convert.dart';
import 'dart:convert';
import 'dart:typed_data';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
SecretKey secretKey = new SecretKey(key);
Nonce nonce = new Nonce(iv);
Uint8List decrypted = aesCbc.decryptSync(ciphertext, secretKey: secretKey, nonce: nonce);
return decrypted;
}
static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
Pbkdf2 pbkdf2 = Pbkdf2(macAlgorithm: new Hmac(sha1), iterations: 5, bits: 128);
return pbkdf2.deriveBitsSync(passphrase, nonce: Nonce(salt));
}
请注意,在实践中,必须为每次加密随机生成 IV 和盐(您已经在问题中提到过)。除此之外,5 的迭代次数通常太低了。
我已经在 java 中有了加密代码。现在我想从我的服务器使用 API。即使尝试了各种教程和示例代码,我也无法成功解密哈希。
我知道固定盐,完全不推荐静脉注射。但为了简单起见和理解问题,我将 salt 和 IV 保留为“00000000000000000000000000000000”;
从 Java = "XjxCg0KK0ZDWa4XMFhykIw==" 加密后的哈希值; 使用的私钥 = "Mayur12354673645"
谁能帮我用 dart 解密上面的字符串。
JAVA代码
public String encrypt(String salt, String iv, String passphrase,
String plaintext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext
.getBytes("UTF-8"));
return base64(encrypted);
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
public String decrypt(String salt, String iv, String passphrase,
String ciphertext) {
try {
SecretKey key = generateKey(salt, passphrase);
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv,
base64(ciphertext));
return new String(decrypted, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory
.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), hex(salt),
iterationCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return key;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv,
byte[] bytes) {
try {
cipher.init(encryptMode, key, new IvParameterSpec(hex(iv)));
return cipher.doFinal(bytes);
} catch (Exception e) {
e.printStackTrace();
throw fail(e);
}
}
我的飞镖代码
import 'package:pointycastle/block/aes_fast.dart';
import 'package:pointycastle/block/modes/cbc.dart';
import 'package:pointycastle/digests/sha1.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/macs/hmac.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';
import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'dart:developer';
import 'package:pointycastle/random/fortuna_random.dart';
const KEY_SIZE = 16;
const ITERATION_COUNT = 5;
class EncryptionHandler {
static const CBC_MODE = 'CBC';
static Uint8List deriveKey(dynamic password,
{String salt = '0000000000000000',
int iterationCount = ITERATION_COUNT,
int derivedKeyLength = KEY_SIZE}) {
if (password == null || password.isEmpty) {
throw new ArgumentError('password must not be empty');
}
if (password is String) {
password = createUint8ListFromString(password);
}
Uint8List saltBytes = createUint8ListFromString(salt);
String hexSalt = formatBytesAsHexString(saltBytes);
KeyDerivator keyDerivator =
new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
Pbkdf2Parameters params =
new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
keyDerivator.init(params);
return keyDerivator.process(password);
}
Uint8List createUint8ListFromHexString(String hex) {
var result = new Uint8List(hex.length ~/ 2);
for (var i = 0; i < hex.length; i += 2) {
var num = hex.substring(i, i + 2);
var byte = int.parse(num, radix: 16);
result[i ~/ 2] = byte;
}
return result;
}
static String formatBytesAsHexString(Uint8List bytes) {
var result = new StringBuffer();
for (var i = 0; i < bytes.lengthInBytes; i++) {
var part = bytes[i];
result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
}
return result.toString();
}
static Uint8List pad(Uint8List src, int blockSize) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = blockSize - (src.length % blockSize);
var out = new Uint8List(src.length + padLength)..setAll(0, src);
pad.addPadding(out, src.length);
return out;
}
static Uint8List unpad(Uint8List src) {
var pad = new PKCS7Padding();
pad.init(null);
int padLength = pad.padCount(src);
int len = src.length - padLength;
return new Uint8List(len)..setRange(0, len, src);
}
static String encrypt(String password, String plaintext,
{String mode = CBC_MODE}) {
Uint8List derivedKey = deriveKey(password);
KeyParameter keyParam = new KeyParameter(derivedKey);
BlockCipher aes = new AESFastEngine();
var rnd = FortunaRandom();
rnd.seed(keyParam);
Uint8List iv = createUint8ListFromString("0000000000000000");
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
cipher = new CBCBlockCipher(aes);
cipher.init(true, params);
Uint8List textBytes = createUint8ListFromString(plaintext);
Uint8List paddedText = pad(textBytes, aes.blockSize);
Uint8List cipherBytes = _processBlocks(cipher, paddedText);
Uint8List cipherIvBytes = new Uint8List(cipherBytes.length + iv.length)
..setAll(0, iv)
..setAll(iv.length, cipherBytes);
return base64.encode(cipherIvBytes);
}
static String decrypt(String password, String ciphertext) {
log('Password: $password');
Uint8List derivedKey = deriveKey(password);
log('derivedKey: $derivedKey');
KeyParameter keyParam = new KeyParameter(derivedKey);
log('keyParam: $keyParam');
BlockCipher aes = new AESFastEngine();
Uint8List cipherIvBytes = base64.decode(ciphertext);
log('cipherIvBytes: $cipherIvBytes');
Uint8List iv = createUint8ListFromString("0000000000000000");
// Uint8List iv = new Uint8List(aes.blockSize)
// ..setRange(0, aes.blockSize, cipherIvBytes);
log('iv: $iv');
BlockCipher cipher;
ParametersWithIV params = new ParametersWithIV(keyParam, iv);
log('params: $params');
cipher = new CBCBlockCipher(aes);
log('cipher: $cipher');
cipher.init(false, params);
int cipherLen = cipherIvBytes.length - aes.blockSize;
Uint8List cipherBytes = new Uint8List(cipherLen)
..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
Uint8List paddedText = _processBlocks(cipher, cipherBytes);
log('cipher: $paddedText');
Uint8List textBytes = paddedText;
// Uint8List textBytes = unpad(paddedText);
return new String.fromCharCodes(textBytes);
}
static Uint8List createUint8ListFromString(String s) {
var ret = new Uint8List(s.length);
for (var i = 0; i < s.length; i++) {
ret[i] = s.codeUnitAt(i);
}
return ret;
}
static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
var out = new Uint8List(inp.lengthInBytes);
for (var offset = 0; offset < inp.lengthInBytes;) {
var len = cipher.processBlock(inp, offset, out, offset);
offset += len;
}
return out;
}
}
可以通过使用现有的 Dart 库将 二进制转换为十六进制 来简化代码,反之亦然。 PointyCastle 还支持 (PKCS7) 填充,因此不需要自定义实现,这也减少了代码。在 Internet 上,您可以找到 AES/CBC/PKCS7Padding 的几个飞镖实现与使用 PointyCastle 的 PBKDF2 相结合,例如here and here.
使用 pointycastle and convert 包进行解密的可能的 Dart 实现例如是(为简单起见,没有异常处理):
import 'dart:typed_data';
import "package:pointycastle/export.dart";
import 'package:convert/convert.dart';
import 'dart:convert';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
CBCBlockCipher cipher = new CBCBlockCipher(new AESFastEngine());
ParametersWithIV<KeyParameter> params = new ParametersWithIV<KeyParameter>(new KeyParameter(key), iv);
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = new PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(params, null);
PaddedBlockCipherImpl paddingCipher = new PaddedBlockCipherImpl(new PKCS7Padding(), cipher);
paddingCipher.init(false, paddingParams);
return paddingCipher.process(ciphertext);
}
static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
KeyDerivator derivator = new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
Pbkdf2Parameters params = new Pbkdf2Parameters(salt, 5, 16);
derivator.init(params);
return derivator.process(passphrase);
}
贴上测试数据:
String saltHex = '00000000000000000000000000000000';
String ivHex = '00000000000000000000000000000000';
String passphraseUtf8 = 'Mayur12354673645';
String ciphertextBase64 = "XjxCg0KK0ZDWa4XMFhykIw==";
Uint8List salt = hex.decode(saltHex);
Uint8List passphrase = utf8.encode(passphraseUtf8);
Uint8List key = generateKey(salt, passphrase);
Uint8List ciphertext = base64.decode(ciphertextBase64);
Uint8List iv = hex.decode(ivHex);
Uint8List decrypted = decrypt(ciphertext, key, iv);
print(utf8.decode(decrypted)); // This is working
密文可以解密为:有效。
PointyCastle 的替代品是 cryptography 包,它在当前情况下允许更紧凑的实现:
import 'package:cryptography/cryptography.dart';
import 'package:convert/convert.dart';
import 'dart:convert';
import 'dart:typed_data';
...
static Uint8List decrypt(Uint8List ciphertext, Uint8List key, Uint8List iv) {
SecretKey secretKey = new SecretKey(key);
Nonce nonce = new Nonce(iv);
Uint8List decrypted = aesCbc.decryptSync(ciphertext, secretKey: secretKey, nonce: nonce);
return decrypted;
}
static Uint8List generateKey(Uint8List salt, Uint8List passphrase){
Pbkdf2 pbkdf2 = Pbkdf2(macAlgorithm: new Hmac(sha1), iterations: 5, bits: 128);
return pbkdf2.deriveBitsSync(passphrase, nonce: Nonce(salt));
}
请注意,在实践中,必须为每次加密随机生成 IV 和盐(您已经在问题中提到过)。除此之外,5 的迭代次数通常太低了。