How to decode and unzip text back? - c#

I want to create big text, squeeze it, encode and save into file. Then I want to decipher and extract that text and save it into the simple text file.
I haven't problem with first step (create encrypted and squeezed file) but I have it with second ones - to get info back (read the comment in my code, please). What I did wrong?
using System;
using System.IO;
using System.Text;
using System.IO.Compression;
using System.Security.Cryptography;
static class Program
{
static void Main()
{
Console.WriteLine("Create encrypted archive...");
var fileName = #"data.xgzip";
byte[] key = null;
byte[] iv = null;
// I want to create big text, squeeze it, encode and save into file:
// 1. Create big text.
// 2. Squeeze text.
// 3. Encode squeezed text.
// 4. Write result to file.
using (var stream = new FileStream(fileName, FileMode.Create,
FileAccess.Write, FileShare.None, 0x1000, FileOptions.None))
{
using (var rijn = Rijndael.Create())
{
key = rijn.Key;
iv = rijn.IV;
var encryptor = rijn.CreateEncryptor(key, iv);
using (var encStream = new CryptoStream(stream, encryptor,
CryptoStreamMode.Write))
{
using (var zip = new DeflateStream(encStream,
CompressionLevel.Optimal))
{
using (var writer = new StreamWriter(zip, new UTF8Encoding(
encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true), 0x1000, true))
{
var text = "One, two, three, four, five...";
for (int n = 0; n < 1000; n++)
{
writer.WriteLine(text);
}
}
Console.WriteLine("zip.Length = {0}", stream.Length);
}
}
}
}
var fi = new FileInfo(fileName);
Console.WriteLine("File size: {0}", fi.Length);
// Now I want to decipher and extract my file into simple text file.
// 1. Decode squeezed data.
// 2. Unpack decoded data.
// 4. Write result to text file.
Console.WriteLine("Extract encrypted archive...");
var fileName2 = #"data.txt";
using (var stream = new FileStream(fileName, FileMode.Open,
FileAccess.Read, FileShare.None, 0x1000, FileOptions.None))
{
using (var rijn = Rijndael.Create())
{
var encryptor = rijn.CreateEncryptor(key, iv);
using (var cryptoStream = new CryptoStream(stream, encryptor,
CryptoStreamMode.Read))
{
using (var zip = new DeflateStream(cryptoStream,
CompressionMode.Decompress))
{
using (var reader = new StreamReader(zip,
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true)))
{
// System.IO.InvalidDataException:
// "The archive entry was compressed using an unsupported
// compression method."
var text = reader.ReadToEnd();
// Write the result into the simple text file.
using (var stream2 = new FileStream(fileName2,
FileMode.Create, FileAccess.Write, FileShare.None,
0x1000, FileOptions.None))
{
using (var writer = new StreamWriter(stream2,
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false,
throwOnInvalidBytes: true), 0x1000, true))
{
writer.Write(text);
}
Console.WriteLine("stream.Length = {0}", stream.Length);
}
}
}
}
}
}
var fi2 = new FileInfo(fileName2);
Console.WriteLine("File size: {0}", fi2.Length);
Console.WriteLine("Press any key for exit...");
Console.ReadKey();
}
}

In the decode part you are still using the encryptor to decrypt the content, the correct way should be using a decryptor.
update the below lines in the decode part
var encryptor = rijn.CreateEncryptor(key, iv);
using (var cryptoStream = new CryptoStream(stream, encryptor,
CryptoStreamMode.Read))
to
var decryptor = rijn.CreateDecryptor(key, iv);
using (var cryptoStream = new CryptoStream(stream, decryptor,
CryptoStreamMode.Read))
Then you code should work

Related

Why do I get an empty result when encrypting a string, even though I flushed the input stream?

I want to encrypt some string through RijndaelManaged and get encrypted result. I want to do it with MemoryStream. But I get the empty string. I get the same problem if I use Rijndael class instead of RijndaelManaged. What I did wrong?
static string EncodeString(string text, byte[] key, byte[] iv) {
RijndaelManaged alg = new RijndaelManaged();
var encryptor = alg.CreateEncryptor(key, iv);
string encodedString = null;
using (var s = new MemoryStream())
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
using (var sw = new StreamWriter(cs)) {
// encrypt the string
sw.Write(text);
sw.Flush();
cs.Flush();
s.Flush();
Console.WriteLine($"Stream position: {s.Position}"); // Oops... It is 0 still. Why?
// get encrypted string
var sr = new StreamReader(s);
s.Position = 0;
encodedString = sr.ReadToEnd(); // I get empty string here
}
return encodedString;
}
Then I use this method:
RijndaelManaged alg = new RijndaelManaged();
alg.GenerateKey();
alg.GenerateIV();
var encodedString = EncodeString("Hello, Dev!", alg.Key, alg.IV); // I get empty string. Why?
You have two issues here:
Problem 1: You try to read the result before it is ready. You need to close the StreamWriter first:
using (var s = new MemoryStream())
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
using (var sw = new StreamWriter(cs)) {
// encrypt the string
sw.Write(text);
Console.WriteLine(s.ToArray().Length); // prints 0
sw.Close();
Console.WriteLine(s.ToArray().Length); // prints 16
...
}
But why do I need this? Didn't you see all those Flush statements in my code? Yes, but Rijndael is a block cypher. It can only encrypt a block once it has read the full block (or you have told it that this was the final partial block). Flush allows further data to be written to the stream, so the encryptor cannot be sure that the block is complete.
You can solve this by explicitly telling the crypto stream that you are done sending input. The reference implementation does this by closing the StreamWriter (and, thus the CryptoStream) with a nested using statement. As soon as the CryptoStream is closed, it flushes the final block.
using (var s = new MemoryStream())
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
{
using (var sw = new StreamWriter(cs))
{
// encrypt the string
sw.Write(text);
}
Console.WriteLine(s.ToArray().Length); // prints 16
...
}
Alternatively, as mentioned by Jimi in the comments, you can call FlushFinalBlock explicitly. In addition, you can skip the StreamWriter by explicitly converting your base string to a byte array:
using (var s = new MemoryStream())
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
{
cs.Write(Encoding.UTF8.GetBytes(text));
cs.FlushFinalBlock();
Console.WriteLine(s.ToArray().Length); // prints 16
...
}
Or, as mentioned by V.Lorz in the comments, you can just dispose the CryptoStream to call FlushFinalBlock implicitly:
using (var s = new MemoryStream())
{
using (var cs = new CryptoStream(s, encryptor, CryptoStreamMode.Write))
{
cs.Write(Encoding.UTF8.GetBytes(text));
}
Console.WriteLine(s.ToArray().Length); // prints 16
...
}
Problem 2: You tried to read the result as a string. Encryption does not work on strings, it works on byte arrays. Thus, trying to read the result as an UTF-8 string will result in garbage.
Instead, you could, for example, use a Base64 representation of the resulting byte array:
return Convert.ToBase64String(s.ToArray());
Here are working fiddles of your code with all those fixes applied:
With StreamWriter: https://dotnetfiddle.net/8kGI4N
Without StreamWriter: https://dotnetfiddle.net/Nno0DF

Serializing object at specific byte index

In VS2015, I would like to serialize a data table to a file, beginning at byte 16, since the file is to be encrypted and the IV uses the bytes 0-15. I have not yet found a serialization method taking an offset parameter, so should I convert the table to a byte array? There must be a cleaner approach. Here is one of the functions:
internal static void EncryptData(DataTable dTable, string userName, string password, string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
{
using (Aes aes = SetAes(userName, password)) // simply performs aes initialization
{
fs.Write(aes.IV, 0, 16); // writing the IV
using (CryptoStream cs = new CryptoStream(fs, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
dTable.WriteXml(cs, XmlWriteMode.WriteSchema); // This is overwriting the bytes 0-15 :(
}
}
}
}
EDIT: Adding deserialization function, which throws the exception "Length of the data to decrypt is invalid"
internal static void DecryptData(DataTable dTable, string userName, string password, string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Open))
{
byte[] IV = new byte[16];
fs.Read(IV, 0, 16);
using (Aes aes = SetAes(userName, password, IV)) // simply setting aes
{
using (CryptoStream cs = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read))
{
dTable.ReadXml(cs);
}
}
}
}
FINAL EDIT: The solution: Add IV bytes with File.WriteAllBytes and use FileStream filemode.Append in the serializing method(EncryptData):
using (Aes aes = SetAes(userName, password))
{
File.WriteAllBytes(fileName, aes.IV);
using (FileStream fs = new FileStream(fileName, FileMode.Append, FileAccess.Write, FileShare.None))
{
using (CryptoStream cs = new CryptoStream(fs, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
dTable.WriteXml(cs, XmlWriteMode.WriteSchema);
}
}
}

Encrypt large file and add to ZIP

my goal is to encrypt large (cca 10 GB) input file and append it to an existing System.IO.Packaging Package. I can use .NET Framework 3.5 only and no third-party libraries.
I tried maybee ten methods with no success. I tried to read the input to Stream, encrypt it and save to PackagePart. I tried to read the input file byte after byte, then encrypt byte read and append it to Stream from PackagePart too. Everytime I found a new issue (e.g. CryptoStream does not supports seeking and so on).
Could you show me the right way, please?
//method to create zip file (just a sample)
public static void AppendToZip(SomeType encryptedData)
{
using (Package zip = Package.Open(#"C:\myarchive.zip", FileMode.OpenOrCreate))
{
Uri uri = PackUriHelper.CreatePartUri(new Uri("/files/test.enc", UriKind.Relative));
try
{
part = zip.GetPart(uri);
}
catch
{
}
if (part == null)
{
part = zip.CreatePart(uri, "", CompressionOption.Maximum);
}
using (Stream dest = part.GetStream())
{
//how to write encryptedData to destination stream?
}
}
}
//sample method for encrypting a file
private static void Encrypt(string inputFile, string cryptFile, byte[] passwordBytes, byte[] saltBytes)
{
FileStream fsCrypt = new FileStream(cryptFile, FileMode.Create);
RijndaelManaged AES = new RijndaelManaged();
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Padding = PaddingMode.Zeros;
AES.Mode = CipherMode.CBC;
CryptoStream cs = new CryptoStream(fsCrypt, AES.CreateEncryptor(), CryptoStreamMode.Write);
FileStream fsIn = new FileStream(inputFile, FileMode.Open);
int data;
while ((data = fsIn.ReadByte()) != -1)
{
cs.WriteByte((byte)data);
}
fsIn.Close();
cs.Close();
fsCrypt.Close();
}
Try this out - play around with block size for performance. I did this with a 3.5 GB ISO successfully. However the zip file is much larger compressing encrypted content, so as the other guy said you're better compressing the file FIRST and then encrypting it. But I don't know your requirements, so here's this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Packaging;
namespace ZipTest
{
class Program
{
static void Main(string[] args)
{
// Block size we apply to all reads / writes
const int BLOCK_SIZE = 65536;
// The zip file we're using
var zipFileName = #"C:\temp\ZipSO\MyZip.zip";
// Password for encryption
var password = "ThisIsMyPassword";
// Name of temp file where we'll encrypt the file first
var intermediateFile = #"C:\temp\ZipSO\Intermediate_" + Guid.NewGuid().ToString();
// File we're encrypting / adding to archive
var inputFile = #"C:\temp\ZipSO\InputFile.txt";
// Salt for encryption
var salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
// For the new / existing package part
PackagePart part = null;
// Open the archive
using (var zip = Package.Open(zipFileName, System.IO.FileMode.OpenOrCreate))
{
// Uri for the part
var uri = PackUriHelper.CreatePartUri(new Uri("/files/test.enc", UriKind.Relative));
// Get existing part if found, or create new
if (zip.PartExists(uri))
part = zip.GetPart(uri);
else
part = zip.CreatePart(uri, "", CompressionOption.Maximum);
// Encrypt the file first
var passBytes = System.Text.Encoding.ASCII.GetBytes(password);
using (var fs = new System.IO.FileStream(intermediateFile, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
{
var key = new System.Security.Cryptography.Rfc2898DeriveBytes(passBytes, salt, 1000);
var keySize = 256;
var blockSize = 128;
var aes = new System.Security.Cryptography.RijndaelManaged()
{
KeySize = keySize,
BlockSize = blockSize,
Key = key.GetBytes(keySize / 8),
IV = key.GetBytes(blockSize / 8),
Padding = System.Security.Cryptography.PaddingMode.Zeros,
Mode = System.Security.Cryptography.CipherMode.CBC
};
using (var fsSource = new System.IO.FileStream(inputFile, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
using (var cs = new System.Security.Cryptography.CryptoStream(fs, aes.CreateEncryptor(), System.Security.Cryptography.CryptoStreamMode.Write))
{
var readBytes = new byte[BLOCK_SIZE];
int read;
while ((read = fsSource.Read(readBytes, 0, BLOCK_SIZE)) != 0)
{
cs.Write(readBytes, 0, read);
}
cs.Close();
}
fsSource.Close();
}
fs.Close();
}
// Now add it to the archive
using (var p = part.GetStream(System.IO.FileMode.OpenOrCreate))
{
using (var source = new System.IO.FileStream(intermediateFile, System.IO.FileMode.Open, System.IO.FileAccess.Read))
using (var bw = new System.IO.BinaryWriter(p))
{
var readBytes = new byte[BLOCK_SIZE];
int read;
while ((read = source.Read(readBytes, 0, BLOCK_SIZE)) != 0)
{
bw.Write(readBytes.Take(read).ToArray());
}
}
}
// Clean up the intermediate
System.IO.File.Delete(intermediateFile);
}
}
}
}

Encrypt RijndaelManaged 128 in unity c# and Decrypt in Node.JS

I'm looking for a way to encrypt a byte array in unity c# and decrypt on a node.js server.
I'm open to any implementation of either but I have currently gone with the below code which encrypts/decrypts fine in unity but I receive the error:
TypeError: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
When decrypting a file encrypted in unity using RijndaelManaged 128
Find the encrypting and decrypting code below:
Unity C# Encrypt
private void GenerateEncryptionKey(string userID)
{
//Generate the Salt, with any custom logic and using the user's ID
StringBuilder salt = new StringBuilder();
for (int i = 0; i < 8; i++)
{
salt.Append("," + userID.Length.ToString());
}
Rfc2898DeriveBytes pwdGen = new Rfc2898DeriveBytes (Encoding.UTF8.GetBytes(userID), Encoding.UTF8.GetBytes(salt.ToString()), 100);
m_cryptoKey = pwdGen.GetBytes(KEY_SIZE / 8);
m_cryptoIV = pwdGen.GetBytes(KEY_SIZE / 8);
}
public void Save(string path)
{
string json = MiniJSON.Json.Serialize(m_saveData);
using (RijndaelManaged crypto = new RijndaelManaged())
{
crypto.BlockSize = KEY_SIZE;
crypto.Padding = PaddingMode.PKCS7;
crypto.Key = m_cryptoKey;
crypto.IV = m_cryptoIV;
crypto.Mode = CipherMode.CBC;
ICryptoTransform encryptor = crypto.CreateEncryptor(crypto.Key, crypto.IV);
byte[] compressed = null;
using (MemoryStream compMemStream = new MemoryStream())
{
using (StreamWriter writer = new StreamWriter(compMemStream, Encoding.UTF8))
{
writer.Write(json);
writer.Close();
compressed = compMemStream.ToArray();
}
}
if (compressed != null)
{
using (MemoryStream encMemStream = new MemoryStream(compressed))
{
using (CryptoStream cryptoStream = new CryptoStream(encMemStream, encryptor, CryptoStreamMode.Write))
{
using (FileStream fs = File.Create(GetSavePath(path)))
{
byte[] encrypted = encMemStream.ToArray();
fs.Write(encrypted, 0, encrypted.Length);
fs.Close();
}
}
}
}
}
}
ignore the compressed bit, I'll eventually be compressing the data for encryption but I have removed it in this example.
Node.JS Decrypt
var sUserID = "hello-me";
var sSalt = "";
for (var i = 0; i < 8; i++)
{
sSalt += "," + sUserID.length;
}
var KEY_SIZE = 128;
crypto.pbkdf2(sUserID, sSalt, 100, KEY_SIZE / 4, function(cErr, cBuffer){
var cKey = cBuffer.slice(0, cBuffer.length / 2);
var cIV = cBuffer.slice(cBuffer.length / 2, cBuffer.length);
fs.readFile("save.sav", function (cErr, cData){
try
{
var cDecipher = crypto.createDecipheriv("AES-128-CBC", cKey, cIV);
var sDecoded = cDecipher.update(cData, null, "utf8");
sDecoded += cDecipher.final("utf8");
console.log(sDecoded);
}
catch(e)
{
console.log(e.message);
console.log(e.stack);
}
});
});
I believe the problem is something to do with padding! I am not using:
cryptoStream.FlushFinalBlock();
when saving the file in c# land because for some reason after doing that c# can't decrypt it anymore and it doesn't really have an effect on the ability of node to decrypt it either, but maybe I'm just missing something in the decryption of it with padding?
Any help is appreciated
One problem is that you're using PasswordDeriveBytes which according to this article is for PBKDF1, whereas Rfc2898DeriveBytes is for PBKDF2. You're using PBKDF2 in your node script.
Then you should check that your cKey and cIV values match between C# and node.
Okay well it seems that order of operation is very important when encrypting and decryption using RijndaelManaged.
Below is the code to encrypt and decrypt in Unity and works with the node.js code posted in the question.
public void Save(string path)
{
string json = MiniJSON.Json.Serialize(m_saveData);
using (RijndaelManaged crypto = new RijndaelManaged())
{
crypto.BlockSize = KEY_SIZE;
crypto.Padding = PaddingMode.PKCS7;
crypto.Key = m_cryptoKey;
crypto.IV = m_cryptoIV;
crypto.Mode = CipherMode.CBC;
ICryptoTransform encryptor = crypto.CreateEncryptor(crypto.Key, crypto.IV);
byte[] compressed = null;
using (MemoryStream compMemStream = new MemoryStream())
{
using (StreamWriter writer = new StreamWriter(compMemStream, Encoding.UTF8))
{
writer.Write(json);
writer.Close();
//compressed = CLZF2.Compress(compMemStream.ToArray());
compressed = compMemStream.ToArray();
}
}
if (compressed != null)
{
using (MemoryStream encMemStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(encMemStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(compressed, 0, compressed.Length);
cryptoStream.FlushFinalBlock();
using (FileStream fs = File.Create(GetSavePath(path)))
{
encMemStream.WriteTo(fs);
}
}
}
}
}
}
public void Load(string path)
{
path = GetSavePath(path);
try
{
byte[] decrypted = null;
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
{
using (RijndaelManaged crypto = new RijndaelManaged())
{
crypto.BlockSize = KEY_SIZE;
crypto.Padding = PaddingMode.PKCS7;
crypto.Key = m_cryptoKey;
crypto.IV = m_cryptoIV;
crypto.Mode = CipherMode.CBC;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = crypto.CreateDecryptor(crypto.Key, crypto.IV);
using (CryptoStream cryptoStream = new CryptoStream(fs, decryptor, CryptoStreamMode.Read))
{
using (MemoryStream decMemStream = new MemoryStream())
{
var buffer = new byte[512];
var bytesRead = 0;
while ((bytesRead = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
{
decMemStream.Write(buffer, 0, bytesRead);
}
//decrypted = CLZF2.Decompress(decMemStream.ToArray());
decrypted = decMemStream.ToArray();
}
}
}
}
if (decrypted != null)
{
using (MemoryStream jsonMemoryStream = new MemoryStream(decrypted))
{
using (StreamReader reader = new StreamReader(jsonMemoryStream))
{
string json = reader.ReadToEnd();
Dictionary<string, object> saveData = MiniJSON.Json.Deserialize(json) as Dictionary<string, object>;
if (saveData != null)
{
m_saveData = saveData;
}
else
{
Debug.LogWarning("Trying to load invalid JSON file at path: " + path);
}
}
}
}
}
catch (FileNotFoundException e)
{
Debug.LogWarning("No save file found at path: " + path);
}
}

Decompress byte array to string via BinaryReader yields empty string

I am trying to decompress a byte array and get it into a string using a binary reader. When the following code executes, the inStream position changes from 0 to the length of the array, but str is always an empty string.
BinaryReader br = null;
string str = String.Empty;
using (MemoryStream inStream = new MemoryStream(pByteArray))
{
GZipStream zipStream = new GZipStream(inStream, CompressionMode.Decompress);
BinaryReader br = new BinaryReader(zipStream);
str = br.ReadString();
inStream.Close();
br.Close();
}
You haven't shown how is the data being compressed, but here's a full example of compressing and decompressing a buffer:
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
class Program
{
static void Main()
{
var test = "foo bar baz";
var compressed = Compress(Encoding.UTF8.GetBytes(test));
var decompressed = Decompress(compressed);
Console.WriteLine(Encoding.UTF8.GetString(decompressed));
}
static byte[] Compress(byte[] data)
{
using (var compressedStream = new MemoryStream())
using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress))
{
zipStream.Write(data, 0, data.Length);
zipStream.Close();
return compressedStream.ToArray();
}
}
static byte[] Decompress(byte[] data)
{
using (var compressedStream = new MemoryStream(data))
using (var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var resultStream = new MemoryStream())
{
zipStream.CopyTo(resultStream);
return resultStream.ToArray();
}
}
}

Categories

Resources