Out of personal interest and as a learning exercise I've written a C# class (.NET 4) to perform encryption/decryption of a file along with some compression upon encryption. Most of my understanding of this has been based on web research so I'd like some feedback on coding style, optimizations and, if any cryptography experts out there, the security of this approach.
using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
namespace FileCrypt {
class Cypto {
private const int SALTSIZE = 16;
private const int HASHSIZE = 20;
private const int EXTENSIONSIZE = 32;
private const int BITSPERBYTE = 8;
private const int ITERCOUNT = 32767;
/// <summary>
/// compress and encrypt file into target with password protection
/// </summary>
/// <param name="inFile"></param>
/// <param name="outFile"></param>
/// <param name="password"></param>
/// <returns></returns>
public static void Encrypt( FileInfo inFile, FileInfo outFile, string password ) {
try{
Rfc2898DeriveBytes keyGenerator = new Rfc2898DeriveBytes(password, SALTSIZE, ITERCOUNT);
Rijndael rijndael = Rijndael.Create();
rijndael.Padding = PaddingMode.PKCS7;
rijndael.Mode = CipherMode.CBC;
rijndael.IV = keyGenerator.GetBytes(rijndael.BlockSize / BITSPERBYTE);
rijndael.Key = keyGenerator.GetBytes(rijndael.KeySize / BITSPERBYTE);
using(var fileStream = outFile.Create()) {
byte[] salt = keyGenerator.Salt;
byte[] fileExtension = Encoding.ASCII.GetBytes(Convert.ToBase64String(Encoding.ASCII.GetBytes(inFile.Extension)));
byte[] paddedExtension = new byte[EXTENSIONSIZE];
if(fileExtension.Length > EXTENSIONSIZE) {
throw new Exception("File extension is too long for allocated memory. " +
"Consider compressing the file to a zip archive prior to encryption.");
}
Array.Copy(fileExtension, paddedExtension, fileExtension.Length);
// write random salt, hash and file extension to output file
fileStream.Write(salt, 0, SALTSIZE);
fileStream.Write(Hash(salt, password), 0, HASHSIZE);
fileStream.Write(paddedExtension, 0, EXTENSIONSIZE);
using(CryptoStream cryptoStream = new CryptoStream(fileStream, rijndael.CreateEncryptor(), CryptoStreamMode.Write)) {
using(GZipStream gzStream = new GZipStream(cryptoStream, CompressionMode.Compress)) {
using(FileStream sourceStream = inFile.OpenRead()) {
sourceStream.CopyTo(gzStream);
}
}
}
}
} catch {
throw;
}
}
/// <summary>
/// decompress and decrypt file with password validation
/// </summary>
/// <param name="sourceFile"></param>
/// <param name="outDir"></param>
/// <param name="password"></param>
/// <returns></returns>
public static void Decrypt( FileInfo sourceFile, DirectoryInfo outDir, string password ) {
byte[] salt = new byte[SALTSIZE];
byte[] hash = new byte[HASHSIZE];
byte[] fileextension = new byte[EXTENSIONSIZE];
try {
using(FileStream sourceStream = sourceFile.OpenRead()) {
// read salt, hash and file extension from source file
sourceStream.Read(salt, 0, SALTSIZE);
sourceStream.Read(hash, 0, HASHSIZE);
sourceStream.Read(fileextension, 0, EXTENSIONSIZE);
// check hashed password
if(!ByteCompare(Hash(salt, password), hash)) {
throw new Exception("Incorrect password entered.");
}
// build output file path and create FileInfo object for it
fileextension = Convert.FromBase64String(Encoding.ASCII.GetString(fileextension).TrimEnd('\0'));
string filepath = Path.Combine(outDir.FullName,
Path.GetFileNameWithoutExtension(sourceFile.FullName) +
Encoding.ASCII.GetString(fileextension).Trim());
FileInfo outFile = new FileInfo(filepath);
// generate derived key
Rfc2898DeriveBytes keyGenerator = new Rfc2898DeriveBytes(password, salt, ITERCOUNT);
Rijndael rijndael = Rijndael.Create();
rijndael.Padding = PaddingMode.PKCS7;
rijndael.Mode = CipherMode.CBC;
rijndael.IV = keyGenerator.GetBytes(rijndael.BlockSize / BITSPERBYTE);
rijndael.Key = keyGenerator.GetBytes(rijndael.KeySize / BITSPERBYTE);
using(FileStream outStream = new FileStream(outFile.FullName, FileMode.OpenOrCreate, FileAccess.Write)) {
using(CryptoStream cryptoStream = new CryptoStream(sourceStream, rijndael.CreateDecryptor(), CryptoStreamMode.Read)) {
using(GZipStream gzipStream = new GZipStream(cryptoStream, CompressionMode.Decompress)) {
gzipStream.CopyTo(outStream);
}
}
}
}
} catch {
throw;
}
}
/// <summary>
/// compute hash from salt and password
/// </summary>
/// <param name="salt"></param>
/// <param name="password"></param>
/// <returns></returns>
private static byte[] Hash( byte[] salt, string password ) {
byte[] pass = Encoding.ASCII.GetBytes(password);
byte[] saltedpass = new byte[salt.Length + pass.Length];
Array.Copy(salt, saltedpass, salt.Length);
Array.Copy(pass, 0, saltedpass, salt.Length, pass.Length);
return new SHA1CryptoServiceProvider().ComputeHash(saltedpass);
}
/// <summary>
/// compare byte arrays for equality
/// </summary>
/// <param name="b1"></param>
/// <param name="b2"></param>
/// <returns></returns>
public static bool ByteCompare( byte[] b1, byte[] b2 ) {
return Encoding.ASCII.GetString(b1) == Encoding.ASCII.GetString(b2);
}
}
}