I am trying to convert the following Java encryption snippet to C#
private final String encryptToken(String str) {
byte[] generateIV = generateIV(16);
Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
instance.init(1, new SecretKeySpec(new byte[]{55, 66, 53, 68, 55, 66, 67, 50, 52, 66, 53, 67, 52, 69, 51, 65, 56, 48, 70, 66, 66, 67, 50, 65, 49, 49, 53, 54, 69, 52, 51, 55}, "AES/CBC/PKCS5Padding"), new IvParameterSpec(generateIV));
Charset charset = Charsets.UTF_8;
if (str != null) {
byte[] bytes = str.getBytes(charset);
Intrinsics.checkExpressionValueIsNotNull(bytes, "(this as java.lang.String).getBytes(charset)");
byte[] doFinal = instance.doFinal(bytes);
StringBuilder sb = new StringBuilder();
sb.append(new String(generateIV, Charsets.UTF_8));
String encodeToString = Base64.encodeToString(doFinal, 0);
Intrinsics.checkExpressionValueIsNotNull(encodeToString, "Base64.encodeToString(cipherText, Base64.DEFAULT)");
if (encodeToString != null) {
sb.append(StringsKt.trim((CharSequence) encodeToString).toString());
return sb.toString();
}
throw new TypeCastException("null cannot be cast to non-null type kotlin.CharSequence");
}
throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
}
Here is my C# code:
public static string Encrypt(string token)
{
byte[] IV;
byte[] encrypted;
byte[] key = new byte[] { 55, 66, 53, 68, 55, 66, 67, 50, 52, 66, 53, 67, 52, 69, 51, 65, 56, 48, 70, 66, 66, 67, 50, 65, 49, 49, 53, 54, 69, 52, 51, 55 };
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = key;
aesAlg.IV = GenerateIv(16);
IV = aesAlg.IV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(Encoding.UTF8.GetBytes(token));
}
encrypted = msEncrypt.ToArray();
StringBuilder sb = new StringBuilder();
sb.Append(Encoding.UTF8.GetString(IV));
string encodedString = Convert.ToBase64String(encrypted);
sb.Append(encodedString.ToString());
return sb.ToString();
}
}
}
return "";
}
I am using the result of the encryption routine to authorize against an API that I do not have control over. Using my C# code, the API returns "unauthorized" which is a result of my encryption routine not being quite correct.
I note that the Java method uses PKCS5 padding, but the only option available in .NET is PKCS7. However reading this previous article, I understand they are the same, and therefore it shouldn't matter.
If I use a temporary IV of h62PmLFO5Yq4SxQw
and a token
of 4a97adfc-d25c-485b-84af-86c93ff28b20
, the Java code returns: h62PmLFO5Yq4SxQwgoOqOLrjcZmnYbKxEDhl1hsDeCGUmEv8kwwP337JfYyvjuXgaDdND9vqSeAd9NpH
and my C# code returns: h62PmLFO5Yq4SxQwfUXW396ss2Pzopk0FHC/7A==
Is anyone able to advise in any obvious differences between the Java code and mine?
CodePudding user response:
The reason is you are using StreamWriter
, which is intended to write text into the stream, but then you write raw bytes there:
swEncrypt.Write(Encoding.UTF8.GetBytes(token));
Overload used here is Write(object)
with description "Writes text representation of the object to the stream, by calling ToString() method on that object". So basically you are encrypting "System.Byte[]" string, not the token.
You can just remove that text writer and write bytes directly into crypto stream (note also leaveOpen: true
):
public static string Encrypt(string token)
{
byte[] IV;
byte[] encrypted;
byte[] key = new byte[] { 55, 66, 53, 68, 55, 66, 67, 50, 52, 66, 53, 67, 52, 69, 51, 65, 56, 48, 70, 66, 66, 67, 50, 65, 49, 49, 53, 54, 69, 52, 51, 55 };
using (var aesAlg = new RijndaelManaged())
{
aesAlg.Key = key;
var iv = Encoding.UTF8.GetBytes("h62PmLFO5Yq4SxQw");
aesAlg.IV = iv;
IV = aesAlg.IV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write, leaveOpen: true))
{
// just write to stream directly
csEncrypt.Write(Encoding.UTF8.GetBytes(token));
}
// read here, so that we are sure writing is finished
encrypted = msEncrypt.ToArray();
StringBuilder sb = new StringBuilder();
sb.Append(Encoding.UTF8.GetString(IV));
string encodedString = Convert.ToBase64String(encrypted);
sb.Append(encodedString.ToString());
return sb.ToString();
}
}
return "";
}
Or you can continue using StreamWriter
but then do it right:
// tell it you need UTF8 without BOM
using (var swEncrypt = new StreamWriter(csEncrypt, new UTF8Encoding(false)))
{
// then write string there, not bytes
swEncrypt.Write(token);
}