using System; using System.Collections.Generic; using System.Text; using System.Security.Cryptography; using System.IO; namespace FastReport.Utils { /// /// Contains methods used to crypt/decrypt a data. /// public static class Crypter { private static string FDefaultPassword = typeof(Crypter).FullName; /// /// Sets the password that is used to crypt connection strings stored in a report. /// /// /// See the property for more details. /// public static string DefaultPassword { set { FDefaultPassword = value; } } /// /// Crypts a stream using specified password. /// /// The destination stream that will receive the crypted data. /// The password. /// The stream that you need to write to. /// /// Pass the stream you need to write to, to the dest parameter. Write your data to the /// stream that this method returns. When you close this stream, the dest stream will be /// closed too and contains the crypted data. /// public static Stream Encrypt(Stream dest, string password) { ICryptoTransform encryptor = null; #if DOTNET_4 using (PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, Encoding.UTF8.GetBytes("Salt"))) #else PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, Encoding.UTF8.GetBytes("Salt")); #endif { RijndaelManaged rm = new RijndaelManaged(); rm.Padding = PaddingMode.ISO10126; encryptor = rm.CreateEncryptor(pdb.GetBytes(16), pdb.GetBytes(16)); } // write "rij" signature dest.Write(new byte[] { 114, 105, 106 }, 0, 3); return new CryptoStream(dest, encryptor, CryptoStreamMode.Write); } /// /// Decrypts a stream using specified password. /// /// Stream that contains crypted data. /// The password. /// The stream that contains decrypted data. /// /// You should read from the stream that this method returns. /// public static Stream Decrypt(Stream source, string password) { ICryptoTransform decryptor = null; #if DOTNET_4 using (PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, Encoding.UTF8.GetBytes("Salt"))) #else PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, Encoding.UTF8.GetBytes("Salt")); #endif { RijndaelManaged rm = new RijndaelManaged(); rm.Padding = PaddingMode.ISO10126; decryptor = rm.CreateDecryptor(pdb.GetBytes(16), pdb.GetBytes(16)); } var encrypted = IsStreamEncryptedPrivate(source); if (encrypted) return new CryptoStream(source, decryptor, CryptoStreamMode.Read); source.Position -= 3; return null; } /// /// Checks if the stream contains a crypt signature. /// /// Stream to check. /// true if stream is crypted. public static bool IsStreamEncrypted(Stream stream) { var result = IsStreamEncryptedPrivate(stream); stream.Position -= 3; return result; } private static bool IsStreamEncryptedPrivate(Stream stream) { // check "rij" signature int byte1 = stream.ReadByte(); int byte2 = stream.ReadByte(); int byte3 = stream.ReadByte(); return byte1 == 114 && byte2 == 105 && byte3 == 106; } /// /// Encrypts the string using the default password. /// /// String to encrypt. /// The encrypted string. /// /// The password used to encrypt a string can be set via property. /// You also may use the method if you want to /// specify another password. /// public static string EncryptString(string data) { return EncryptString(data, FDefaultPassword); } /// /// Encrypts the string using specified password. /// /// String to encrypt. /// The password. /// The encrypted string. public static string EncryptString(string data, string password) { if (String.IsNullOrEmpty(data) || String.IsNullOrEmpty(password)) return data; using (MemoryStream stream = new MemoryStream()) { using (Stream cryptedStream = Encrypt(stream, password)) { byte[] bytes = Encoding.UTF8.GetBytes(data); cryptedStream.Write(bytes, 0, bytes.Length); } return "rij" + Convert.ToBase64String(stream.ToArray()); } } /// /// Decrypts the string using the default password. /// /// String to decrypt. /// The decrypted string. /// /// The password used to decrypt a string can be set via property. /// You also may use the method if you want to /// specify another password. /// public static string DecryptString(string data) { return DecryptString(data, FDefaultPassword); } /// /// Decrypts the string using specified password. /// /// String to decrypt. /// The password. /// The decrypted string. public static string DecryptString(string data, string password) { if (String.IsNullOrEmpty(data) || String.IsNullOrEmpty(password) || !data.StartsWith("rij")) return data; data = data.Substring(3); using (Stream stream = Converter.FromString(typeof(Stream), data) as Stream) { using (Stream decryptedStream = Decrypt(stream, password)) { byte[] bytes = new byte[data.Length]; int bytesRead = 0; while (bytesRead < bytes.Length) { int n = decryptedStream.Read(bytes, bytesRead, bytes.Length - bytesRead); if (n == 0) break; bytesRead += n; } return Encoding.UTF8.GetString(bytes, 0, bytesRead); } } } /// /// Computes hash of specified stream. Initial position in stream will be saved. /// /// Initial stream /// public static string ComputeHash(Stream input) { byte[] buff = new byte[input.Length]; input.Read(buff, 0, buff.Length); return ComputeHash(buff); } /// /// Computes hash of specified array. /// /// Initial array /// public static string ComputeHash(byte[] input) { byte[] hash = new Murmur3().ComputeHash(input); return BitConverter.ToString(hash).Replace("-", String.Empty); } /// /// Computes hash of specified array. /// /// Initial array /// public static string ComputeHash(string input) { return ComputeHash(Encoding.UTF8.GetBytes(input)); } } /// /// MurmurHash is a non-cryptographic hash function suitable for general hash-based lookup. /// It was created by Austin Appleby in 2008 and is currently hosted on Github along with its test suite named 'SMHasher'. /// It also exists in a number of variants, all of which have been released into the public domain. /// The name comes from two basic operations, multiply (MU) and rotate (R), used in its inner loop. /// https://en.wikipedia.org/wiki/MurmurHash /// Implementation of Murmur3 Hash by Adam Horvath /// http://blog.teamleadnet.com/2012/08/murmurhash3-ultra-fast-hash-algorithm.html /// [CLSCompliantAttribute(true)] public class Murmur3 { // 128 bit output, 64 bit platform version /// /// READ_SIZE /// [CLSCompliantAttribute(false)] public static ulong READ_SIZE = 16; private const ulong C1 = 0x87c37b91114253d5L; private const ulong C2 = 0x4cf5ad432745937fL; private ulong length; private readonly uint seed = 0; // if want to start with a seed, create a constructor ulong h1; ulong h2; private void MixBody(ulong k1, ulong k2) { unchecked { h1 ^= MixKey1(k1); h1 = (h1 << 27) | (h1 >> 37); h1 += h2; h1 = h1* 5 + 0x52dce729; h2 ^= MixKey2(k2); h2 = (h2 << 31) | (h2 >> 33); h2 += h1; h2 = h2* 5 + 0x38495ab5; } } private static ulong MixKey1(ulong k1) { unchecked { k1 *= C1; k1 = (k1 << 31) | (k1 >> 33); k1 *= C2; } return k1; } private static ulong MixKey2(ulong k2) { unchecked { k2 *= C2; k2 = (k2 << 33) | (k2 >> 31); k2 *= C1; } return k2; } private static ulong MixFinal(ulong k) { unchecked { // avalanche bits k ^= k >> 33; k *= 0xff51afd7ed558ccdL; k ^= k >> 33; k *= 0xc4ceb9fe1a85ec53L; k ^= k >> 33; } return k; } /// /// ComputeHash function /// /// /// public byte[] ComputeHash(byte[] bb) { ProcessBytes(bb); return Hash; } private void ProcessBytes(byte[] bb) { h1 = seed; this.length = 0L; int pos = 0; int npos; ulong remaining = (ulong)bb.Length; // read 128 bits, 16 bytes, 2 longs in eacy cycle while (remaining >= READ_SIZE) unchecked { npos = pos; ulong k1 = (uint)(bb[npos++] | bb[npos++] << 8 | bb[npos++] << 16 | bb[npos++] << 24); pos += 8; npos = pos; ulong k2 = (uint)(bb[npos++] | bb[npos++] << 8 | bb[npos++] << 16 | bb[npos++] << 24); pos += 8; length += READ_SIZE; remaining -= READ_SIZE; MixBody(k1, k2); } // if the input MOD 16 != 0 if (remaining > 0) ProcessBytesRemaining(bb, remaining, pos); } private void ProcessBytesRemaining(byte[] bb, ulong remaining, int pos) { ulong k1 = 0; ulong k2 = 0; length += remaining; // little endian (x86) processing unchecked { switch (remaining) { case 15: k2 ^= (ulong)bb[pos + 14] << 48; // fall through goto case 14; case 14: k2 ^= (ulong)bb[pos + 13] << 40; // fall through goto case 13; case 13: k2 ^= (ulong)bb[pos + 12] << 32; // fall through goto case 12; case 12: k2 ^= (ulong)bb[pos + 11] << 24; // fall through goto case 11; case 11: k2 ^= (ulong)bb[pos + 10] << 16; // fall through goto case 10; case 10: k2 ^= (ulong)bb[pos + 9] << 8; // fall through goto case 9; case 9: k2 ^= (ulong)bb[pos + 8]; // fall through goto case 8; case 8: int npos = pos; k1 ^= (uint)(bb[npos++] | bb[npos++] << 8 | bb[npos++] << 16 | bb[npos++] << 24); break; case 7: k1 ^= (ulong)bb[pos + 6] << 48; // fall through goto case 6; case 6: k1 ^= (ulong)bb[pos + 5] << 40; // fall through goto case 5; case 5: k1 ^= (ulong)bb[pos + 4] << 32; // fall through goto case 4; case 4: k1 ^= (ulong)bb[pos + 3] << 24; // fall through goto case 3; case 3: k1 ^= (ulong)bb[pos + 2] << 16; // fall through goto case 2; case 2: k1 ^= (ulong)bb[pos + 1] << 8; // fall through goto case 1; case 1: k1 ^= (ulong)bb[pos]; // fall through break; default: throw new Exception("Something went wrong with remaining bytes calculation."); } h1 ^= MixKey1(k1); h2 ^= MixKey2(k2); } } /// /// Gets the Hash /// public byte[] Hash { get { unchecked { h1 ^= length; h2 ^= length; h1 += h2; h2 += h1; h1 = Murmur3.MixFinal(h1); h2 = Murmur3.MixFinal(h2); h1 += h2; h2 += h1; } byte[] hash = new byte[Murmur3.READ_SIZE]; Array.Copy(BitConverter.GetBytes(h1), 0, hash, 0, 8); Array.Copy(BitConverter.GetBytes(h2), 0, hash, 8, 8); return hash; } } } }