我参与了两个系统通信项目的开发。另一端基于Java的系统要求传输的内容使用AES进行加密。我们的系统目前基于.NET Framework 7 (NET7)。我正在尝试使用 C# 解密对方的内容。我尝试使用“BouncyCastle.Cryptography”框架来实现该算法,试图模仿Java编码风格,但不幸的是,我没有成功。我已经为这个问题苦苦挣扎了几天,并决定在这里寻求帮助。
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class AESUtil {
private static final String KEY_ALGORITHM_AES = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final String CHARSET = "UTF-8";
public static String aesEncrypt(String content, String encryptPass) {
try {
byte[] iv = new byte[12];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
byte[] contentBytes = content.getBytes(CHARSET);
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
GCMParameterSpec params = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(encryptPass), params);
byte[] encryptData = cipher.doFinal(contentBytes);
assert encryptData.length == contentBytes.length + 16;
byte[] message = new byte[12 + contentBytes.length + 16];
System.arraycopy(iv, 0, message, 0, 12);
System.arraycopy(encryptData, 0, message, 12, encryptData.length);
return Base64.getEncoder().encodeToString(message);
} catch (Exception e) {
}
return null;
}
public static String aesDecrypt(String base64Content, String encryptPass) {
try {
byte[] content = Base64.getDecoder().decode(base64Content);
if (content.length < 12 + 16) {
throw new IllegalArgumentException();
}
GCMParameterSpec params = new GCMParameterSpec(128, content, 0, 12);
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(encryptPass), params);
byte[] decryptData = cipher.doFinal(content, 12, content.length - 12);
return new String(decryptData, CHARSET);
} catch (Exception e) {
System.out.println("error:" + e.getMessage());
}
return null;
}
private static SecretKeySpec getSecretKey(String encryptPass) throws NoSuchAlgorithmException {
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM_AES);
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(encryptPass.getBytes());
kg.init(128, secureRandom);
SecretKey secretKey = kg.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM_AES);
}
}
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System.Text;
static string Encrypt(string content, string encryptPass)
{
var size = 128;
var plainTextData = Encoding.UTF8.GetBytes(content);
CipherKeyGenerator keyGen = new CipherKeyGenerator();
var sr = SecureRandom.GetInstance("SHA1PRNG");
sr.SetSeed(Encoding.UTF8.GetBytes(encryptPass));
keyGen.Init(new KeyGenerationParameters(sr, size));
KeyParameter keyParam = keyGen.GenerateKeyParameter();
var cipher = CipherUtilities.GetCipher("AES/GCM/NoPadding");
byte[] iv = new byte[12];
var secureRandom = new SecureRandom();
secureRandom.NextBytes(iv);
AeadParameters keyParamAead = new AeadParameters(keyParam, size, iv);
cipher.Init(true, keyParamAead);
int outputSize = cipher.GetOutputSize(plainTextData.Length);
byte[] cipherTextData = new byte[outputSize];
int result = cipher.ProcessBytes(plainTextData, 0, plainTextData.Length, cipherTextData, 0);
cipher.DoFinal(cipherTextData, result);
if (cipherTextData.Length != plainTextData.Length + 16)
{
Console.WriteLine("error!");
return string.Empty;
}
else
{
cipher.Init(false, keyParamAead);
outputSize = cipher.GetOutputSize(cipherTextData.Length);
var decryptData = new byte[outputSize];
int decryptResult = cipher.ProcessBytes(cipherTextData, 0, cipherTextData.Length, decryptData, 0);
cipher.DoFinal(decryptData, decryptResult);
if (Encoding.UTF8.GetString(decryptData) != content)
{
Console.WriteLine($"error :{Encoding.UTF8.GetString(decryptData)}");
return string.Empty;
}
}
byte[] message = new byte[12 + plainTextData.Length + 16];
Buffer.BlockCopy(iv, 0, message, 0, 12);
Buffer.BlockCopy(cipherTextData, 0, message, 12, cipherTextData.Length);
return Convert.ToBase64String(message);
}
static string Decrypt(string encryptString, string encryptPass)
{
var encryptedData = Encoding.UTF8.GetBytes(encryptString);
CipherKeyGenerator keyGen = new CipherKeyGenerator();
var sr = SecureRandom.GetInstance("SHA1PRNG");
sr.SetSeed(Encoding.UTF8.GetBytes(encryptPass));
keyGen.Init(new KeyGenerationParameters(sr, 128));
KeyParameter keyParam = keyGen.GenerateKeyParameter();
var cipher = CipherUtilities.GetCipher("AES/GCM/NoPadding");
var iv = new byte[12];
Buffer.BlockCopy(encryptedData, 0, iv, 0, 12);
AeadParameters keyParamAead = new AeadParameters(keyParam, 128, iv);
cipher.Init(true, keyParamAead);
var realEncryptedData = new byte[encryptedData.Length - 12];
var outputSize = cipher.GetOutputSize(realEncryptedData.Length);
var decryptData = new byte[outputSize];
int decryptResult = cipher.ProcessBytes(realEncryptedData, 0, realEncryptedData.Length, decryptData, 0);
cipher.DoFinal(decryptData, decryptResult);
return Encoding.UTF8.GetString(decryptData);
}
esp4tgsltzA2fml2
coYavtca/pvUNgU1tYiNzeEOOnazFVsfce/ApmHz0gaAsq
我写的C#代码无法正常解密。我希望我写的C#代码能够与Java版本实现相互加解密
PRNG 不应该用作 KDF,因为不同平台甚至版本之间的实现可能有所不同,因此即使使用相同的种子,也不能保证生成相同的字节序列。这使得 PRNG 不适合作为 KDF。
您使用的 BouncyCastle 实现默认生成随机启动种子,并且仅添加使用
SetSeed()
指定的附加种子。因此,默认值会生成随机字节序列,因此不适合您的目的。SecureRandom.GetInstance("SHA1PRNG", false)
禁用起始种子的生成。不幸的是,尽管种子相同,但生成的字节序列与 Java 实现的值并不对应。造成这种情况的原因是上述 SHA1PRNG 算法的非标准化实现。
为了避免此类兼容性问题,SHA1PRNG 本身的 Java 实现可以移植到 C#。以下 C# 轻量级实现仅移植为给定种子生成任意长度的字节序列所需的部分。特别是,省略了此处不需要的起始种子生成的移植。用于移植的 Java 源代码是 JDK 22:
using System.Security.Cryptography;
...
class Sha1PrngLW
{
private static int DIGEST_SIZE = 20;
private byte[] state;
private byte[] remainder;
private int remCount;
public Sha1PrngLW(byte[] seed)
{
state = SHA1.HashData(seed);
}
public byte[] GetNextBytes(int number)
{
byte[] result = new byte[number];
int index = 0;
int todo;
byte[] output = remainder;
int r = remCount;
if (r > 0)
{
todo = Math.Min(result.Length - index, DIGEST_SIZE - r);
for (int i = 0; i < todo; i++)
{
result[i] = output[r];
output[r++] = 0;
}
remCount += todo;
index += todo;
}
while (index < result.Length)
{
output = SHA1.HashData(state);
updateState(state, output);
todo = Math.Min((result.Length - index), DIGEST_SIZE);
for (int i = 0; i < todo; i++)
{
result[index++] = output[i];
output[i] = 0;
}
remCount += todo;
}
remainder = output;
remCount %= DIGEST_SIZE;
return result;
}
private static void updateState(byte[] state, byte[] output)
{
int last = 1;
int v;
byte t;
bool zf = false;
for (int i = 0; i < state.Length; i++)
{
v = (sbyte)state[i] + (sbyte)output[i] + last;
t = (byte)v;
zf = zf | (state[i] != t);
state[i] = t;
last = v >> 8;
}
if (!zf)
{
state[0]++;
}
}
}
可以在这里找到类似的端口。上述端口在某些方面有所不同,因为它基于较新的 Java 版本并适用于 .NET 7。
关于 AES/GCM 加密,.NET 7 上无需使用 BouncyCastle,因为内置
AesGcm
类支持此算法:
Sha1PrngLW sha1PrngLW = new Sha1PrngLW(Encoding.UTF8.GetBytes("esp4tgsltzA2fml2"));
byte[] key = sha1PrngLW.GetNextBytes(16);
byte[] data = Convert.FromBase64String("cOYavtcA/pvUNgU1tYiNzEOOnazFVsfce/ApmHz0gAsq");
AesGcm aesGcm = new AesGcm(key);
byte[] decrypted = new byte[data.Length - 12 - 16];
aesGcm.Decrypt(data[0..12], data[12..^16], data[^16..], decrypted);
Console.WriteLine(Encoding.UTF8.GetString(decrypted)); // 12345
至此,解密成功并返回
12345
作为解密数据。