c#加密方法到Java
本文关键字:Java 方法 加密 | 更新日期: 2023-09-27 18:05:14
我有一个方法用于将字符串转换为c#中带有键和盐的编码字符串,我正试图在Java中创建一个等效的方法。
c#的方法如下: public static string Encrypt<T>(string Value, string Key, string Salt) where T : SymmetricAlgorithm, new()
{
DeriveBytes deriveBytes = new Rfc2898DeriveBytes(Key, Encoding.Unicode.GetBytes(Salt));
SymmetricAlgorithm algorithm = new T();
byte[] keyBytes = deriveBytes.GetBytes(algorithm.KeySize >> 3);
byte[] ivBytes = deriveBytes.GetBytes(algorithm.BlockSize >> 3);
ICryptoTransform transform = algorithm.CreateEncryptor(keyBytes, ivBytes);
using (MemoryStream buffer = new MemoryStream())
{
using (CryptoStream stream = new CryptoStream(buffer, transform, CryptoStreamMode.Write))
{
using (StreamWriter writer = new StreamWriter(stream, Encoding.Unicode))
{
writer.Write(Value);
}
}
return Convert.ToBase64String(buffer.ToArray());
}
}
我在整个SO和整个网络上尝试了许多不同的解决方案,但无济于事。我甚至有一个样本:
value("YourId|YourFacId")
,key("6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA=")
, salt("FPSJxiSMpAavjKqyGvVe1A==")
这些都被发送到上面的方法,并返回字符串"Y5w4A3pDZwTcq+FGyqUMO/mZSr6hSst8qiac9zDbfso9FQQbdTDsKnkKDT7SHl4y"
.
我还没有找到任何符合我的问题,所以我在这里寻求帮助。任何线索将不胜感激。谢谢。
试图链接到另一个问题没有显示我没有看到的东西。在我的示例中不需要处理密码。以下是我多次失败的尝试之一:
private String encrypt(String user) throws Exception
{
Cipher deCipher;
Cipher enCipher;
SecretKeySpec key;
IvParameterSpec ivSpec;
String plainKey = "6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA=";
String salt = "FPSJxiSMpAavjKqyGvVe1A==";
String result = "";
ivSpec = new IvParameterSpec(salt.getBytes());
key = new SecretKeySpec(plainkey.getBytes(), "AES");
enCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] input = convertToByteArray(user);
enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return new String(enCipher.doFinal(input).toString());
}
多亏了Stefan和一些摆弄,我发现c#代码在编码消息值时包含字节顺序标记,但在编码salt值时没有。等效的Java代码如下所示:
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
class SO39257791
{
private static final int KEY_LEN = 256 / 8, BLOCK_LEN = 16, ITERATIONS = 1000;
public static void main(String... argv)
throws Exception
{
String value = "YourId|YourFacId";
String key = "6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA=";
String salt = "FPSJxiSMpAavjKqyGvVe1A==";
String good = "Y5w4A3pDZwTcq+FGyqUMO/mZSr6hSst8qiac9zDbfso9FQQbdTDsKnkKDT7SHl4y";
String output = encrypt(value, key, salt);
if (output.equals(good))
System.out.println("strings are equal");
else
System.out.println("strings are NOT equal!");
}
static final String encrypt(String value, String key, String salt)
throws GeneralSecurityException, UnsupportedEncodingException
{
/* Derive the key, given password and salt. */
byte[] s = salt.getBytes(StandardCharsets.UTF_16LE);
int dkLen = (KEY_LEN + BLOCK_LEN) * 8;
KeySpec spec = new PBEKeySpec(key.toCharArray(), s, ITERATIONS, dkLen);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] dk = factory.generateSecret(spec).getEncoded();
SecretKey secret = new SecretKeySpec(Arrays.copyOfRange(dk, 0, KEY_LEN), "AES");
byte[] iv = Arrays.copyOfRange(dk, KEY_LEN, KEY_LEN + BLOCK_LEN);
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] plaintext = value.getBytes("UnicodeLittle"); /* Use Byte Order Mark */
byte[] ciphertext = cipher.doFinal(plaintext);
return Base64.getEncoder().encodeToString(ciphertext);
}
}
我只能给你一些部分的信息来帮助你:
使用的算法是256bit AesManaged。模式为CBC
,填充为PKCS7
, keysize = 256。
给出的样例值可以用这个小测试程序来证明是正确的
static void Main(string[] args)
{
string key = "6JxI1HOSg7KQj4fJ1Xb3L1T6AVdLZLBAPFSqOjh2UoA=";
string salt = "FPSJxiSMpAavjKqyGvVe1A==";
string value = "YourId|YourFacId";
string result = Encrypt<AesManaged>(value, key, salt);
Console.WriteLine(result);
string expected = "Y5w4A3pDZwTcq+FGyqUMO/mZSr6hSst8qiac9zDbfso9FQQbdTDsKnkKDT7SHl4y";
if (expected.Equals(result))
{
Console.WriteLine("strings are equal");
}
else
{
Console.WriteLine("strings are NOT equal!");
}
}
key
和salt
是普通字符串(不是base-64编码)。要获取字节,在Java中必须使用
byte[] saltBytes = salt.getBytes("UnicodeLittleUnmarked");
byte[] keyBytes = key.getBytes("UTF-8");
PBKDF2使用SHA1摘要和1000次迭代(我用BouncyCastle的PKCS5S2ParametersGenerator
检查了这一点)
final int iterations = 1000;
PKCS5S2ParametersGenerator pbkdf = new PKCS5S2ParametersGenerator(new SHA1Digest());
pbkdf.init(keyBytes, saltBytes, iterations);
final int keySize = 32 * 8;
final int ivSize = 16 * 8;
CipherParameters cp = pbkdf.generateDerivedParameters(keySize, ivSize);
这给了我以下ivBytes和keyBytes
// Java ivBytes
// [-33, 102, -108, 66, -46, 89, 122, 102, -63, -15, -92, 66, -88, -29, 67, -59]
// Java keyBytes:
// [-127, 125, -40, -123, 60, -70, 16, -6, -15, -116, 127, 93, 46, 80, 26, 31, -36, 47, -120, -37, 57, 21, -94, 44, 98, -119, -109, 48, -71, 15, -36, 80]
这些是我在c#代码中得到的带符号的等价物:
// C# ivBytes:
// [223, 102, 148, 66, 210, 89, 122, 102, 193, 241, 164, 66, 168, 227, 67, 197]
// C# keyBytes:
// [129, 125, 216, 133, 60, 186, 16, 250, 241, 140, 127, 93, 46, 80, 26, 31, 220, 47, 136, 219, 57, 21, 162, 44, 98, 137, 147, 48, 185, 15, 220, 80]
当我阅读c#代码时,值字节也必须作为UTF-16小端
检索。byte[] valueBytes = value.getBytes("UnicodeLittleUnmarked");
从这里开始,我不能再往前走了。我可以告诉您的最后一件事是加密的字节数组(base64编码之前的最后一步)是什么样子的:
// C# crypted:
// [99, 156, 56, 3, 122, 67, 103, 4, 220, 171, 225, 70, 202, 165, 12, 59, 249, 153, 74, 190, 161, 74, 203, 124, 170, 38, 156, 247, 48, 219, 126, 202, 61, 21, 4, 27, 117, 48, 236, 42, 121, 10, 13, 62, 210, 30, 94, 50]
// or signed:
// [99, -100, 56, 3, 122, 67, 103, 4, -36, -85, -31, 70, -54, -91, 12, 59, -7, -103, 74, -66, -95, 74, -53, 124, -86, 38, -100, -9, 48, -37, 126, -54, 61, 21, 4, 27, 117, 48, -20, 42, 121, 10, 13, 62, -46, 30, 94, 50]