/* Copyright 2001/2002 Markus Hahn All rights reserved. */ using System; using System.Text; using System.Security.Cryptography; namespace Blowfish_NET { /// /// simple and easy to use string encryption and /// decryption using the Blowfish algorithm /// /// /// As a simple solution for developers who want nothing /// more than protect simple strings with a simple key this /// class provides such a functionality. /// The password is hashed using the build-in SHA-1 class /// of the .NET framework. Additionally the random number /// generation for the CBC initialization vector (IV) and /// the BASE64 encoding and decoding is also taken from /// the system security services. /// public class BlowfishSimple { BlowfishCBC m_bfc; UTF8Encoding m_ue; RNGCryptoServiceProvider m_rng; String m_sKeyChecksum; /// /// secure checksum of the key used /// /// /// Store this checksum somewhere to be able to check later on /// by calling the VerifyKey() method to see if a key matches /// for decryption or not. /// public String KeyChecksum { get { return m_sKeyChecksum; } } static byte[] TransformKey (String sKey) { UTF8Encoding ue = new UTF8Encoding(); return ue.GetBytes(sKey); } static byte[] CalcKeyChecksum (byte[] salt, byte[] key) { byte[] keyCombo = new byte[20 + key.Length]; Array.Copy(salt, 0, keyCombo, 0, 20); Array.Copy(key, 0, keyCombo, 20, key.Length); HashAlgorithm sha = new SHA1CryptoServiceProvider(); byte[] result = sha.ComputeHash(keyCombo); sha = null; Array.Clear(keyCombo, 0, keyCombo.Length); return result; } /// /// to verify a key before it is used for decryption /// /// /// By passing the current key available and a key checksum /// retrieved during the former encryption process you can /// check (with a propability of the SHA-1 collision safety) /// that this key will decrypt the data correctly. /// /// /// the key to verify /// /// /// the original key checksum /// /// /// true: key seems to be the right one / false: no match /// public static bool VerifyKey (String sKey, String sKeyChecksum) { // decode what we got byte[] checksumCombo = Convert.FromBase64String(sKeyChecksum); // correct size? if (40 != checksumCombo.Length) return false; // calculate the new checksum byte[] keyRaw = TransformKey(sKey); byte[] checksum = CalcKeyChecksum(checksumCombo, keyRaw); // is it equal to the existing one? int nI = 0; while (nI < checksum.Length) { if (checksum[nI] != checksumCombo[checksum.Length + nI]) break; nI++; } return (nI == checksum.Length); } /// /// standard constructor /// /// /// the string which is used as the key material, internally /// a UTF8 representation is used, hashed with SHA-1, thus /// we use a 160bit key (which does not make weak keys safe!) /// public BlowfishSimple (String sKey) { m_ue = new UTF8Encoding(); byte[] keyRaw = TransformKey(sKey); HashAlgorithm sha = new SHA1CryptoServiceProvider(); byte[] key = sha.ComputeHash(keyRaw); sha = null; m_rng = new RNGCryptoServiceProvider(); byte[] checksumSalt = new byte[20]; m_rng.GetBytes(checksumSalt); byte[] checksum = CalcKeyChecksum(checksumSalt, keyRaw); byte[] checksumCombo = new byte[checksumSalt.Length + checksum.Length]; Array.Copy(checksumSalt, 0, checksumCombo, 0, checksumSalt.Length); Array.Copy(checksum, 0, checksumCombo, checksumSalt.Length, checksum.Length); m_sKeyChecksum = Convert.ToBase64String(checksumCombo); // (start with a dummy IV) byte[] iv = new byte[Blowfish.BLOCKSIZE]; m_bfc = new BlowfishCBC(key, iv); Array.Clear(keyRaw, 0, keyRaw.Length); Array.Clear(key, 0, key.Length); Array.Clear(iv, 0, iv.Length); } /// /// encrypts a string /// /// /// for efficiency the given string will be UTF8 encoded /// and padded to the next block border, the CBC IV plus /// the encrypted data will then be BASE64 encoded and /// returned as an string /// /// /// the string to encrypt /// /// /// the encrypted string /// public String Encrypt (String sPlainText) { int nI; // convert string byte[] ueData = m_ue.GetBytes(sPlainText); // prepare the input and the output buffer int nOrigLen = ueData.Length; int nLen = nOrigLen; // (we need aligned and paddable buffers) int nMod = nLen % Blowfish.BLOCKSIZE; nLen = (nLen - nMod) + Blowfish.BLOCKSIZE; // allocate the input buffer byte[] inBuf = new byte[nLen]; // copy the data and do the (RFC 2630) padding Array.Copy(ueData, 0, inBuf, 0, nOrigLen); nI = nLen - (Blowfish.BLOCKSIZE - nMod); while (nI < nLen) inBuf[nI++] = (byte)nMod; // allocate the output buffer byte[] outBuf = new byte[inBuf.Length + Blowfish.BLOCKSIZE]; // create and set a new IV byte[] iv = new byte[Blowfish.BLOCKSIZE]; m_rng.GetBytes(iv); m_bfc.Iv = iv; // do the encryption m_bfc.Encrypt(inBuf, outBuf, 0, Blowfish.BLOCKSIZE, inBuf.Length); // copy the IV Array.Copy(iv, 0, outBuf, 0, Blowfish.BLOCKSIZE); // BASE64 encode the whole thing String sResult = Convert.ToBase64String(outBuf); // finally clear the plaintext buffer Array.Clear(inBuf, 0, inBuf.Length); return sResult; } /// /// decrypts a string /// /// /// The string has to be decrypted with the same key, /// otherwise the result will be just garbage. If you /// want to check if the key is the right one use the /// VerifyKey() method. /// /// /// the string to decrypt /// /// /// the decrypted (original) string (null on error) /// public String Decrypt (String sCipherText) { byte[] cdata = Convert.FromBase64String(sCipherText); if (cdata.Length < Blowfish.BLOCKSIZE) return null; // set the cbc IV m_bfc.Iv = cdata; // decrypt the data byte[] outBuf = new byte[cdata.Length]; int nDataAbs = cdata.Length - Blowfish.BLOCKSIZE; nDataAbs /= Blowfish.BLOCKSIZE; nDataAbs *= Blowfish.BLOCKSIZE; m_bfc.Decrypt(cdata, outBuf, Blowfish.BLOCKSIZE, 0, nDataAbs); // calculate the original size int nOrigSize = nDataAbs - Blowfish.BLOCKSIZE + outBuf[nDataAbs - 1]; // UTF8 decode the result and pass it back return m_ue.GetString(outBuf, 0, nOrigSize); } /// /// securely invalidates this instance /// /// /// Removes all sensitive data, after this call it is /// not possible to encrypt or data anymore properly! /// /// /// self reference /// public BlowfishSimple Burn() { m_bfc.Burn(); return this; } } } // (end of namespace)