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;
}
}
}
}