Простое небезопасное двустороннее «запутывание» для C # - PullRequest
415 голосов
/ 03 октября 2008

Я ищу очень простую функцию обфускации (например, шифрование и дешифрование, но не обязательно безопасную) для некоторых данных. Это не важно для миссии. Мне нужно что-то, чтобы честные люди были честными, но что-то немного сильнее, чем ROT13 или Base64 .

Я бы предпочел что-то, что уже включено в .NET framework 2.0, поэтому мне не нужно беспокоиться о каких-либо внешних зависимостях.

Я действительно не хочу возиться с открытыми / закрытыми ключами и т. Д. Я мало знаю о шифровании, но знаю достаточно, чтобы знать, что все, что я написал, будет бесполезным ... На самом деле, я бы, наверное, облажался с математикой и сделал бы ее тривиальной.

Ответы [ 17 ]

470 голосов
/ 17 октября 2008

Другие ответы здесь работают нормально, но AES - более безопасный и современный алгоритм шифрования. Это класс, который я приобрел несколько лет назад для выполнения шифрования AES и который со временем изменил, чтобы сделать его более удобным для веб-приложений (например, я создал методы Encrypt / Decrypt, которые работают со строкой, удобной для URL). У этого также есть методы, которые работают с байтовыми массивами.

ПРИМЕЧАНИЕ: вы должны использовать разные значения в массивах Key (32 байта) и Vector (16 байтов)! Вы не хотели бы, чтобы кто-то выяснил ваши ключи, просто предполагая, что вы использовали этот код как есть! Все, что вам нужно сделать, это изменить некоторые числа (должно быть <= 255) в массивах Key и Vector (я оставил одно недопустимое значение в массиве Vector, чтобы убедиться, что вы делаете это ...). Вы можете использовать <a href="https://www.random.org/bytes/" rel="noreferrer">https://www.random.org/bytes/ для простого создания нового набора:

Использовать его легко: просто создайте экземпляр класса, а затем вызовите (обычно) EncryptToString (string StringToEncrypt) и DecryptString (string StringToDecrypt) в качестве методов. Это не может быть проще (или более безопасно), если у вас есть этот класс.


using System;
using System.Data;
using System.Security.Cryptography;
using System.IO;


public class SimpleAES
{
    // Change these keys
    private byte[] Key = __Replace_Me__({ 123, 217, 19, 11, 24, 26, 85, 45, 114, 184, 27, 162, 37, 112, 222, 209, 241, 24, 175, 144, 173, 53, 196, 29, 24, 26, 17, 218, 131, 236, 53, 209 });

    // a hardcoded IV should not be used for production AES-CBC code
    // IVs should be unpredictable per ciphertext
    private byte[] Vector = __Replace_Me__({ 146, 64, 191, 111, 23, 3, 113, 119, 231, 121, 2521, 112, 79, 32, 114, 156 });


    private ICryptoTransform EncryptorTransform, DecryptorTransform;
    private System.Text.UTF8Encoding UTFEncoder;

    public SimpleAES()
    {
        //This is our encryption method
        RijndaelManaged rm = new RijndaelManaged();

        //Create an encryptor and a decryptor using our encryption method, key, and vector.
        EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
        DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);

        //Used to translate bytes to text and vice versa
        UTFEncoder = new System.Text.UTF8Encoding();
    }

    /// -------------- Two Utility Methods (not used but may be useful) -----------
    /// Generates an encryption key.
    static public byte[] GenerateEncryptionKey()
    {
        //Generate a Key.
        RijndaelManaged rm = new RijndaelManaged();
        rm.GenerateKey();
        return rm.Key;
    }

    /// Generates a unique encryption vector
    static public byte[] GenerateEncryptionVector()
    {
        //Generate a Vector
        RijndaelManaged rm = new RijndaelManaged();
        rm.GenerateIV();
        return rm.IV;
    }


    /// ----------- The commonly used methods ------------------------------    
    /// Encrypt some text and return a string suitable for passing in a URL.
    public string EncryptToString(string TextValue)
    {
        return ByteArrToString(Encrypt(TextValue));
    }

    /// Encrypt some text and return an encrypted byte array.
    public byte[] Encrypt(string TextValue)
    {
        //Translates our text value into a byte array.
        Byte[] bytes = UTFEncoder.GetBytes(TextValue);

        //Used to stream the data in and out of the CryptoStream.
        MemoryStream memoryStream = new MemoryStream();

        /*
         * We will have to write the unencrypted bytes to the stream,
         * then read the encrypted result back from the stream.
         */
        #region Write the decrypted value to the encryption stream
        CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write);
        cs.Write(bytes, 0, bytes.Length);
        cs.FlushFinalBlock();
        #endregion

        #region Read encrypted value back out of the stream
        memoryStream.Position = 0;
        byte[] encrypted = new byte[memoryStream.Length];
        memoryStream.Read(encrypted, 0, encrypted.Length);
        #endregion

        //Clean up.
        cs.Close();
        memoryStream.Close();

        return encrypted;
    }

    /// The other side: Decryption methods
    public string DecryptString(string EncryptedString)
    {
        return Decrypt(StrToByteArray(EncryptedString));
    }

    /// Decryption when working with byte arrays.    
    public string Decrypt(byte[] EncryptedValue)
    {
        #region Write the encrypted value to the decryption stream
        MemoryStream encryptedStream = new MemoryStream();
        CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write);
        decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length);
        decryptStream.FlushFinalBlock();
        #endregion

        #region Read the decrypted value from the stream.
        encryptedStream.Position = 0;
        Byte[] decryptedBytes = new Byte[encryptedStream.Length];
        encryptedStream.Read(decryptedBytes, 0, decryptedBytes.Length);
        encryptedStream.Close();
        #endregion
        return UTFEncoder.GetString(decryptedBytes);
    }

    /// Convert a string to a byte array.  NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so).
    //      System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
    //      return encoding.GetBytes(str);
    // However, this results in character values that cannot be passed in a URL.  So, instead, I just
    // lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100).
    public byte[] StrToByteArray(string str)
    {
        if (str.Length == 0)
            throw new Exception("Invalid string value in StrToByteArray");

        byte val;
        byte[] byteArr = new byte[str.Length / 3];
        int i = 0;
        int j = 0;
        do
        {
            val = byte.Parse(str.Substring(i, 3));
            byteArr[j++] = val;
            i += 3;
        }
        while (i < str.Length);
        return byteArr;
    }

    // Same comment as above.  Normally the conversion would use an ASCII encoding in the other direction:
    //      System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
    //      return enc.GetString(byteArr);    
    public string ByteArrToString(byte[] byteArr)
    {
        byte val;
        string tempStr = "";
        for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
        {
            val = byteArr[i];
            if (val < (byte)10)
                tempStr += "00" + val.ToString();
            else if (val < (byte)100)
                tempStr += "0" + val.ToString();
            else
                tempStr += val.ToString();
        }
        return tempStr;
    }
}
175 голосов
/ 01 апреля 2011

Я убрал SimpleAES (выше) для моего использования. Исправлены запутанные методы шифрования / дешифрования; отдельные методы для кодирования байтовых буферов, строк и строк, удобных для URL; использовал существующие библиотеки для кодирования URL.

Код меньше, проще, быстрее, а вывод более лаконичен. Например, johnsmith@gmail.com производит:

SimpleAES: "096114178117140150104121138042115022037019164188092040214235183167012211175176167001017163166152"
SimplerAES: "YHKydYyWaHmKKnMWJROkvFwo1uu3pwzTr7CnARGjppg%3d"

Код:

public class SimplerAES
{
    private static byte[] key = __Replace_Me__({ 123, 217, 19, 11, 24, 26, 85, 45, 114, 184, 27, 162, 37, 112, 222, 209, 241, 24, 175, 144, 173, 53, 196, 29, 24, 26, 17, 218, 131, 236, 53, 209 });

    // a hardcoded IV should not be used for production AES-CBC code
    // IVs should be unpredictable per ciphertext
    private static byte[] vector = __Replace_Me_({ 146, 64, 191, 111, 23, 3, 113, 119, 231, 121, 221, 112, 79, 32, 114, 156 });

    private ICryptoTransform encryptor, decryptor;
    private UTF8Encoding encoder;

    public SimplerAES()
    {
        RijndaelManaged rm = new RijndaelManaged();
        encryptor = rm.CreateEncryptor(key, vector);
        decryptor = rm.CreateDecryptor(key, vector);
        encoder = new UTF8Encoding();
    }

    public string Encrypt(string unencrypted)
    {
        return Convert.ToBase64String(Encrypt(encoder.GetBytes(unencrypted)));
    }

    public string Decrypt(string encrypted)
    {
        return encoder.GetString(Decrypt(Convert.FromBase64String(encrypted)));
    }

    public byte[] Encrypt(byte[] buffer)
    {
        return Transform(buffer, encryptor);
    }

    public byte[] Decrypt(byte[] buffer)
    {
        return Transform(buffer, decryptor);
    }

    protected byte[] Transform(byte[] buffer, ICryptoTransform transform)
    {
        MemoryStream stream = new MemoryStream();
        using (CryptoStream cs = new CryptoStream(stream, transform, CryptoStreamMode.Write))
        {
            cs.Write(buffer, 0, buffer.Length);
        }
        return stream.ToArray();
    }
}
36 голосов
/ 03 октября 2008

Да, добавьте сборку System.Security, импортируйте пространство имен System.Security.Cryptography. Вот простой пример симметричного (DES) алгоритма шифрования:

DESCryptoServiceProvider des = new DESCryptoServiceProvider();
des.GenerateKey();
byte[] key = des.Key; // save this!

ICryptoTransform encryptor = des.CreateEncryptor();
// encrypt
byte[] enc = encryptor.TransformFinalBlock(new byte[] { 1, 2, 3, 4 }, 0, 4);

ICryptoTransform decryptor = des.CreateDecryptor();

// decrypt
byte[] originalAgain = decryptor.TransformFinalBlock(enc, 0, enc.Length);
Debug.Assert(originalAgain[0] == 1);
26 голосов
/ 03 октября 2014

Просто подумал, что добавлю, что улучшил SimplerAES Mud, добавив случайный IV, который передается обратно в зашифрованную строку. Это улучшает шифрование, поскольку шифрование одной и той же строки каждый раз приводит к разным выводам.

public class StringEncryption
{
    private readonly Random random;
    private readonly byte[] key;
    private readonly RijndaelManaged rm;
    private readonly UTF8Encoding encoder;

    public StringEncryption()
    {
        this.random = new Random();
        this.rm = new RijndaelManaged();
        this.encoder = new UTF8Encoding();
        this.key = Convert.FromBase64String("Your+Secret+Static+Encryption+Key+Goes+Here=");
    }

    public string Encrypt(string unencrypted)
    {
        var vector = new byte[16];
        this.random.NextBytes(vector);
        var cryptogram = vector.Concat(this.Encrypt(this.encoder.GetBytes(unencrypted), vector));
        return Convert.ToBase64String(cryptogram.ToArray());
    }

    public string Decrypt(string encrypted)
    {
        var cryptogram = Convert.FromBase64String(encrypted);
        if (cryptogram.Length < 17)
        {
            throw new ArgumentException("Not a valid encrypted string", "encrypted");
        }

        var vector = cryptogram.Take(16).ToArray();
        var buffer = cryptogram.Skip(16).ToArray();
        return this.encoder.GetString(this.Decrypt(buffer, vector));
    }

    private byte[] Encrypt(byte[] buffer, byte[] vector)
    {
        var encryptor = this.rm.CreateEncryptor(this.key, vector);
        return this.Transform(buffer, encryptor);
    }

    private byte[] Decrypt(byte[] buffer, byte[] vector)
    {
        var decryptor = this.rm.CreateDecryptor(this.key, vector);
        return this.Transform(buffer, decryptor);
    }

    private byte[] Transform(byte[] buffer, ICryptoTransform transform)
    {
        var stream = new MemoryStream();
        using (var cs = new CryptoStream(stream, transform, CryptoStreamMode.Write))
        {
            cs.Write(buffer, 0, buffer.Length);
        }

        return stream.ToArray();
    }
}

И бонусный юнит-тест

[Test]
public void EncryptDecrypt()
{
    // Arrange
    var subject = new StringEncryption();
    var originalString = "Testing123!£$";

    // Act
    var encryptedString1 = subject.Encrypt(originalString);
    var encryptedString2 = subject.Encrypt(originalString);
    var decryptedString1 = subject.Decrypt(encryptedString1);
    var decryptedString2 = subject.Decrypt(encryptedString2);

    // Assert
    Assert.AreEqual(originalString, decryptedString1, "Decrypted string should match original string");
    Assert.AreEqual(originalString, decryptedString2, "Decrypted string should match original string");
    Assert.AreNotEqual(originalString, encryptedString1, "Encrypted string should not match original string");
    Assert.AreNotEqual(encryptedString1, encryptedString2, "String should never be encrypted the same twice");
}
12 голосов
/ 06 сентября 2011

Вариант оценки (отлично), ответ

  • Добавить "используя" s
  • Сделать класс IDisposable
  • Удалите кодировку URL, чтобы упростить пример.
  • Добавить простое тестовое устройство, чтобы продемонстрировать использование

Надеюсь, это поможет

[TestFixture]
public class RijndaelHelperTests
{
    [Test]
    public void UseCase()
    {
        //These two values should not be hard coded in your code.
        byte[] key = {251, 9, 67, 117, 237, 158, 138, 150, 255, 97, 103, 128, 183, 65, 76, 161, 7, 79, 244, 225, 146, 180, 51, 123, 118, 167, 45, 10, 184, 181, 202, 190};
        byte[] vector = {214, 11, 221, 108, 210, 71, 14, 15, 151, 57, 241, 174, 177, 142, 115, 137};

        using (var rijndaelHelper = new RijndaelHelper(key, vector))
        {
            var encrypt = rijndaelHelper.Encrypt("StringToEncrypt");
            var decrypt = rijndaelHelper.Decrypt(encrypt);
            Assert.AreEqual("StringToEncrypt", decrypt);
        }
    }
}

public class RijndaelHelper : IDisposable
{
    Rijndael rijndael;
    UTF8Encoding encoding;

    public RijndaelHelper(byte[] key, byte[] vector)
    {
        encoding = new UTF8Encoding();
        rijndael = Rijndael.Create();
        rijndael.Key = key;
        rijndael.IV = vector;
    }

    public byte[] Encrypt(string valueToEncrypt)
    {
        var bytes = encoding.GetBytes(valueToEncrypt);
        using (var encryptor = rijndael.CreateEncryptor())
        using (var stream = new MemoryStream())
        using (var crypto = new CryptoStream(stream, encryptor, CryptoStreamMode.Write))
        {
            crypto.Write(bytes, 0, bytes.Length);
            crypto.FlushFinalBlock();
            stream.Position = 0;
            var encrypted = new byte[stream.Length];
            stream.Read(encrypted, 0, encrypted.Length);
            return encrypted;
        }
    }

    public string Decrypt(byte[] encryptedValue)
    {
        using (var decryptor = rijndael.CreateDecryptor())
        using (var stream = new MemoryStream())
        using (var crypto = new CryptoStream(stream, decryptor, CryptoStreamMode.Write))
        {
            crypto.Write(encryptedValue, 0, encryptedValue.Length);
            crypto.FlushFinalBlock();
            stream.Position = 0;
            var decryptedBytes = new Byte[stream.Length];
            stream.Read(decryptedBytes, 0, decryptedBytes.Length);
            return encoding.GetString(decryptedBytes);
        }
    }

    public void Dispose()
    {
        if (rijndael != null)
        {
            rijndael.Dispose();
        }
    }
}
8 голосов
/ 17 октября 2008

[РЕДАКТИРОВАТЬ] Годы спустя я вернулся, чтобы сказать: не делайте этого! См. Что не так с шифрованием XOR? для получения подробной информации.

Очень простым, легким двусторонним шифрованием является шифрование XOR.

  1. Придумай пароль. Пусть это будет mypass.
  2. Преобразование пароля в двоичный файл (согласно ASCII). Пароль становится 01101101 01111001 01110000 01100001 01110011 01110011.
  3. Возьмите сообщение, которое вы хотите закодировать. Преобразуйте это также в двоичный файл.
  4. Посмотрите на длину сообщения. Если длина сообщения составляет 400 байтов, превратите пароль в строку длиной 400 байтов, повторяя ее снова и снова. Он станет 01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011 ... (или mypassmypassmypass...)
  5. XOR сообщение с длинным паролем.
  6. Отправьте результат.
  7. В другой раз XOR зашифрованное сообщение с тем же паролем (mypassmypassmypass...).
  8. Вот ваше сообщение!
7 голосов
/ 23 октября 2014

Я объединил то, что нашел лучшее из нескольких ответов и комментариев.

  • Случайный вектор инициализации, добавленный к криптографическому тексту (@jbtule)
  • Использовать TransformFinalBlock () вместо MemoryStream (@RenniePet)
  • Нет предварительно заполненных ключей, чтобы никто не копировал и не вставал бедствие
  • Правильная утилизация и использование шаблонов

Код:

/// <summary>
/// Simple encryption/decryption using a random initialization vector
/// and prepending it to the crypto text.
/// </summary>
/// <remarks>Based on multiple answers in /115256/prostoe-nebezopasnoe-dvustoronnee-zaputyvanie-dlya-c-# </remarks>
public class SimpleAes : IDisposable
{
    /// <summary>
    ///     Initialization vector length in bytes.
    /// </summary>
    private const int IvBytes = 16;

    /// <summary>
    ///     Must be exactly 16, 24 or 32 bytes long.
    /// </summary>
    private static readonly byte[] Key = Convert.FromBase64String("FILL ME WITH 24 (2 pad chars), 32 OR 44 (1 pad char) RANDOM CHARS"); // Base64 has a blowup of four-thirds (33%)

    private readonly UTF8Encoding _encoder;
    private readonly ICryptoTransform _encryptor;
    private readonly RijndaelManaged _rijndael;

    public SimpleAes()
    {
        _rijndael = new RijndaelManaged {Key = Key};
        _rijndael.GenerateIV();
        _encryptor = _rijndael.CreateEncryptor();
        _encoder = new UTF8Encoding();
    }

    public string Decrypt(string encrypted)
    {
        return _encoder.GetString(Decrypt(Convert.FromBase64String(encrypted)));
    }

    public void Dispose()
    {
        _rijndael.Dispose();
        _encryptor.Dispose();
    }

    public string Encrypt(string unencrypted)
    {
        return Convert.ToBase64String(Encrypt(_encoder.GetBytes(unencrypted)));
    }

    private byte[] Decrypt(byte[] buffer)
    {
        // IV is prepended to cryptotext
        byte[] iv = buffer.Take(IvBytes).ToArray();
        using (ICryptoTransform decryptor = _rijndael.CreateDecryptor(_rijndael.Key, iv))
        {
            return decryptor.TransformFinalBlock(buffer, IvBytes, buffer.Length - IvBytes);
        }
    }

    private byte[] Encrypt(byte[] buffer)
    {
        // Prepend cryptotext with IV
        byte [] inputBuffer = _encryptor.TransformFinalBlock(buffer, 0, buffer.Length); 
        return _rijndael.IV.Concat(inputBuffer).ToArray();
    }
}

Обновление 2015-07-18: исправлена ​​ошибка в приватном методе Encrypt () по комментариям @bpsilver и @Evereq. IV был случайно зашифрован, теперь добавляется в виде обычного текста, как и ожидалось Decrypt ().

6 голосов
/ 03 октября 2008

Если вы просто хотите простое шифрование (то есть, возможно, что определенный взломщик может сломаться, но блокирует большинство случайных пользователей), просто выберите две парольные фразы одинаковой длины, скажем:

deoxyribonucleicacid
while (x>0) { x-- };

и зашифруйте свои данные с ними обоими (зацикливая пароли при необходимости) (a) . Например:

1111-2222-3333-4444-5555-6666-7777
deoxyribonucleicaciddeoxyribonucle
while (x>0) { x-- };while (x>0) { 

Кто-то ищет ваш двоичный файл, может подумать, что строка ДНК является ключом, но вряд ли он подумает, что код C - это что-то кроме неинициализированной памяти, сохраненной вместе с вашим двоичным файлом.


(a) Имейте в виду, что это очень простое шифрование и, по некоторым определениям, вообще не может считаться шифрованием (поскольку целью шифрования является предотвратить несанкционированный доступ, а не просто сделать его более сложным). Хотя, конечно, даже самое надежное шифрование небезопасно, когда кто-то стоит над держателями ключей со стальной трубой.

Как указывалось в первом предложении, это означает, что случайному злоумышленнику будет достаточно сложно двигаться дальше. Это похоже на предотвращение краж со взломом в вашем доме - вам не нужно делать его неуязвимым, вам просто нужно сделать его менее беременным, чем дом по соседству: -)

5 голосов
/ 03 октября 2008

Шифрование легко: как указывали другие, в пространстве имен System.Security.Cryptography есть классы, которые делают всю работу за вас. Используйте их, а не любые домашние решения.

Но расшифровка тоже проста. У вас проблема не в алгоритме шифрования, а в защите доступа к ключу, используемому для расшифровки.

Я бы использовал одно из следующих решений:

  • DPAPI с использованием класса ProtectedData с областью действия CurrentUser. Это легко, так как вам не нужно беспокоиться о ключе. Данные могут быть расшифрованы только одним и тем же пользователем, поэтому не подходит для обмена данными между пользователями или компьютерами.

  • DPAPI с использованием класса ProtectedData с областью LocalMachine. Хорошо, например, для защита данных конфигурации на одном безопасном сервере. Но любой, кто может войти в систему, может зашифровать ее, так что ничего страшного, если сервер не защищен.

  • Любой симметричный алгоритм. Обычно я использую статический метод SymmetricAlgorithm.Create (), если мне все равно, какой алгоритм используется (на самом деле это Rijndael по умолчанию). В этом случае вам нужно как-то защитить свой ключ. Например. Вы можете каким-то образом запутать его и спрятать в своем коде. Но имейте в виду, что любой, кто достаточно умен, чтобы декомпилировать ваш код, вероятно, сможет найти ключ.

5 голосов
/ 17 ноября 2015

Я хотел опубликовать свое решение, поскольку ни одно из вышеперечисленных решений не было так просто, как мое. Дайте мне знать, что вы думаете:

 // This will return an encrypted string based on the unencrypted parameter
 public static string Encrypt(this string DecryptedValue)
 {
      HttpServerUtility.UrlTokenEncode(MachineKey.Protect(Encoding.UTF8.GetBytes(DecryptedValue.Trim())));
 }

 // This will return an unencrypted string based on the parameter
 public static string Decrypt(this string EncryptedValue)
 {
      Encoding.UTF8.GetString(MachineKey.Unprotect(HttpServerUtility.UrlTokenDecode(EncryptedValue)));
 }

Дополнительно

Предполагается, что MachineKey сервера, используемого для шифрования значения, совпадает с тем, который использовался для дешифрования значения. При желании вы можете указать статический MachineKey в файле Web.config, чтобы ваше приложение могло расшифровывать / шифровать данные независимо от того, где они выполняются (например, разработка или производственный сервер). Вы можете создать статический машинный ключ, следуя этим инструкциям .

...