Простая небезопасная двусторонняя «обфускация» данных?


426

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

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

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


3
Привет Марк - нет проблем. Я чувствовал себя плохо, что мне пришлось принять ответ от richdiet, так как я действительно использовал его решение, и оно работало просто отлично. Тем не менее, я продолжал возвращаться сюда, чтобы прочитать другие ответы, и ваш действительно лучше. Нет причин говорить людям о том, что, хотя это работает, на самом деле это не лучший способ сделать что-то, когда есть лучший ответ.
Мэтт Доуди

3
Сэкономьте время и используйте HttpServerUtility.UrlTokenEn / Decode для преобразования туда и обратно из байтовых массивов в строку, удобную для URL.
Praesagus

32
+1 за то, что не пытался накатить свой умный дизайн. Возможно, вы мало что знаете о шифровании, но тот факт, что вы знаете, что ставит вас на несколько лет впереди большинства знакомых мне разработчиков, которые мало что знают о шифровании, но думают, что в любом случае могут создать свое собственное решение.
Дина

6
Внимание: многие ответы на этот вопрос являются только шифрованием без аутентификации. Это означает, что злоумышленник может изменить данные без уведомления приложения . Это также приводит к другим серьезным уязвимостям (например, дешифрование без ключа из-за отступа оракула). TL; DR: не используйте код в ответах, если вы не согласны с этим или не понимаете, что я только что сказал.
USR

36
Ни один ответ на этот вопрос не описывает безопасное шифрование. Используйте ответ jbtule в Encrypt и расшифруйте строку вместо этого.
CodesInChaos

Ответы:


471

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

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

Использовать его легко: просто создайте экземпляр класса и затем вызовите (обычно) EncryptToString (строка StringToEncrypt) и DecryptString (строка 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;
    }
}

53
@AndyMcKenna - Это сделано специально, чтобы вы изменили значения в массивах, как отмечает Марк во втором абзаце.
Паук

42
Вы не должны использовать IV, как это. Для данных двух сообщений они не должны были быть зашифрованы одним и тем же ключом и одним и тем же IV. IV должен быть случайным для каждого сообщения, добавляться в криптопоток и считываться перед расшифровкой. crypto.stackexchange.com/a/82/1934
jbtule

30
Использование случайного IV для каждого сообщения не является экзотическим или новым, просто важно и является частью дизайна алгоритма. Использование предсказуемого IV для каждого сообщения является распространенной крипто-ошибкой, которую не нужно увековечивать.
Jbtule

14
Обратите также внимание на то, что следствием использования CBC в качестве режима является то, что вы, вероятно, будете уязвимы для атак оракула . Используйте аутентифицированное шифрование и, по возможности, не применяйте криптографию самостоятельно .
Стивен Таусет

57
Предупреждение о безопасности: не используйте этот код Несмотря на то, что он является общепринятым ответом, в приведенных выше комментариях упоминаются серьезные проблемы безопасности, которые автор продолжает игнорировать в течение 8 лет.
jbtule

176

Я очистил 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();
    }
}

2
При декодировании мне пришлось заменить пробел на +, чтобы он работал с QueryString в Chrome: (new SimplerAES ()). Decrypt (Request.QueryString ["myParam"]. Replace ('', '+'));
живи-люби

20
Никогда не используйте постоянный вектор инициализации, для получения дополнительной информации о причинах см.: Crypto.stackexchange.com/questions/66/… Вместо этого сгенерируйте новый IV для каждого шифрования и добавьте его в криптекст, намного лучше и не сложнее.
Том Херд

2
Имейте в виду, что выходные данные метода EncryptToUrl в этом решении (или любое использование строки UrlEncoded base 64 в целом) не будут работать по умолчанию в IIS 7 при использовании в качестве части пути URL (не строки запроса), как в маршрут ASP.NET MVC из-за настройки безопасности IIS 7. Для получения дополнительной информации см .: stackoverflow.com/a/2014121/12484
Джон Шнайдер

5
@ TomHeard Как можно поступить так с приведенным выше кодом?
MKII

26
Предупреждение о безопасности: не используйте этот код См. Комментарий @TomHeard
jbtule

36

Да, добавить 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);

5
Это хорошее, компактное двустороннее шифрование. Единственное предостережение заключается в том, что DES больше не считается современной системой безопасности. Этот заголовок теперь относится к алгоритму AES, который я обсуждаю ниже.
Марк Бриттингем

@richdiet. Извините, я не принял ваш ответ. Другой ответ с 37+ голосами, потому что это более актуально. Спасибо за ваш ответ, так как он все еще хороший.
Мэтт Доуди

14
@MarkBrittingham: любой блочный шифр без функции сцепления блоков, вектора инициализации и правильного заполнения небезопасен. Использование DES - наименее важная проблема с этой схемой.
Хьюберт Карио

2
Так где же используется ключ?
Алекс

22
Предупреждение безопасности: не используйте этот код См. Комментарий @HubertKario
jbtule

28

Просто подумал, что добавлю, что я улучшил 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");
}

11
1) Не используйте в System.Randomкачестве RNG. 2) Это полностью сломано против атак с использованием выбранного шифротекста (в частности, оракулов
отступов

21
Предупреждение о безопасности: не используйте этот код, см. Выше комментарий @CodesInChaos
jbtule

@jbtule, пожалуйста, не вводите в заблуждение каждого, кто не хочет, чтобы осложнения просто шифровались, а также тех, кто не настороженно относится к атаке, - пожалуйста, не заказывайте, если хотите дать предложение.
Virbhadrasinh

@Virbhadrasinh, с моей стороны нет никаких заблуждений, на самом деле все наоборот. Если вы собираетесь использовать AES, очень важно правильно его использовать, неправильно его использовать и говорить, что все в порядке, я не использую его для чего-то важного, ошибочно.
jbtule

1
@Corey Не кричал, и следовал лучшей практике для решения проблем безопасности в ответах переполнения стека. Если вы хотите ссылку, она была размещена в комментариях к вопросу. Но я также
выложу

12

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

  • Добавить "использование" с
  • Сделай класс 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();
        }
    }
}

Хороший ответ. Одна вещь в методе Dispose: вам нужно будет привести rijndael к IDisposable, иначе вы получите ошибку уровня защиты, вызвав Dispose
John ClearZ

8
Никогда не используйте постоянный вектор инициализации, для получения дополнительной информации о причинах см.: Crypto.stackexchange.com/questions/66/… Вместо этого сгенерируйте новый IV для каждого шифрования и добавьте его в криптекст, намного лучше и не сложнее.
Том Херд

5
@Chalky При шифровании вы используете класс Rijndael для генерации случайного IV для вас ( msdn.microsoft.com/en-us/library/… ), выполняете шифрование, а затем извлекаете IV из экземпляра Rijndael с помощью свойства IV , Затем вы добавляете (или добавляете, либо работаете, пока ваш дешифровщик захватывает его с той же стороны), либо к своему криптографическому тексту. При дешифровании вы извлекаете IV из полученных данных (размер свойства IV совпадает со свойством BlockSize, разделенным на 8), а затем передаете его в свой экземпляр расшифровки перед дешифрованием.
Том Херд

2
@Chalky Обратите внимание, что IV не должен быть секретным, он просто должен быть уникальным для каждого отправленного сообщения.
Том Херд

20
Предупреждение безопасности: не используйте этот код. См. Выше Комментарии @TomHeard
jbtule,

8

[РЕДАКТИРОВАТЬ] Годы спустя, я вернулся, чтобы сказать: не делай этого! Посмотрите, что не так с шифрованием 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. Там ваше сообщение!

10
@Ryan Не каждая ситуация требует криптографически защищенных хэшей или шифров Rijndael. «Простое двухстороннее шифрование» на самом деле может означать простое , что предполагает xor или даже ROT13.

1
@Ryan: AES со статическим ключом шифрования, без вектора инициализации и без функции объединения блоков - это просто причудливое название для шифрования XOR, вы просто используете действительно причудливый KDF ...
Hubert Kario

17
Предупреждение безопасности: не используйте этот код XOR Шифрование с повторяющимся ключом тривиально взломано.
jbtule

7

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

  • Случайный вектор инициализации, добавленный к криптографическому тексту (@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 http://stackoverflow.com/questions/165808/simple-two-way-encryption-for-c-sharp </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 ().


Вы должны зашифровать весь inputBuffer с добавленным IV, в противном случае первые 16 символов строки для шифрования будут потеряны. Таким образом, ваш код должен читать:return _encryptor.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
bpsilver

2
В таком случае:byte [] inputBuffer = _encryptor.TransformFinalBlock(buffer, 0, buffer.Length); return _rijndael.IV.Concat(inputBuffer).ToArray();
bpsilver

1
Это сделало бы то же самое, что и текущая реализация, не так ли?
angularsen

1
"ЗАПОЛНИТЕ МЕНЯ 16, 24 ИЛИ 32 СИМВОЛАМИ" ну, нет, не раньше декодирования base 64 И ключ должен быть случайным. Действительно случайно.
Maarten Bodewes

1
Я заметил, что @bpsilver прав, и предоставленный код не будет работать без его исправления: метод encrypt возвращает зашифрованные данные без IV (сначала он добавляет IV в буфер ввода, но затем шифрует и возвращает данные без него). Поэтому, если возможно, просто обновите ответ своим кодом. (Примечание: я тестирую только методы с параметрами byte [], а не строки). Спасибо!
Evereq

6

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

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

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

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

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


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

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


3
Интересная идея. Я не уверен, что «поверил бы» исходному коду в двоичном виде - но как насчет адаптации идеи использовать сообщение об ошибке в качестве ключевой фразы?
Джон Скит

1
Я предпочитаю использовать md5-хэш некоторой строки открытого текста, которая уже существует в приложении (сообщение об ошибке или около того).
Треб

2
Почему они должны быть одинаковой длины? Это на самом деле кажется лучше, если они разной длины. Таким образом, длина вашего эффективного операнда XOR равна LCM (length1, length2), а не просто length1 (= length2). Что, конечно, становится length1 * length2, если длины относительно простые.
Фантиус

15
Предупреждение о безопасности: не используйте этот код Повторяющий ключ XOR легко взломать, просто имея общие знания о зашифрованных данных.
jbtule

3
@jbtule, если бы вы прочитали вопрос, вы бы поняли, что более безопасное шифрование ни в коем случае не требуется. В частности, ссылка на «простое шифрование», «не критично» и просто «честность честных людей». Вам также следует прочитать мой первый абзац, в котором явно указывается, что он не будет блокировать решительных злоумышленников.
paxdiablo

5

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

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

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

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

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

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


5

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

 // 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, чтобы ваше приложение могло расшифровывать / шифровать данные независимо от того, где они выполняются (например, разработка или производственный сервер). Вы можете создать статический машинный ключ, следуя этим инструкциям .


Обратите внимание, что этот подход может быть использован только для приложения ASP.NET.
Феру

2

Пространство имен System.Security.Cryptographyсодержит TripleDESCryptoServiceProviderиRijndaelManaged классы

Не забудьте добавить ссылку на System.Securityсборку.


8
Не то чтобы я проголосовал против, но почему возраст вопроса имеет значение при голосовании?
user247702

2

Использование TripleDESCryptoServiceProvider в System.Security.Cryptography :

public static class CryptoHelper
{
    private const string Key = "MyHashString";
    private static TripleDESCryptoServiceProvider GetCryproProvider()
    {
        var md5 = new MD5CryptoServiceProvider();
        var key = md5.ComputeHash(Encoding.UTF8.GetBytes(Key));
        return new TripleDESCryptoServiceProvider() { Key = key, Mode = CipherMode.ECB, Padding = PaddingMode.PKCS7 };
    }

    public static string Encrypt(string plainString)
    {
        var data = Encoding.UTF8.GetBytes(plainString);
        var tripleDes = GetCryproProvider();
        var transform = tripleDes.CreateEncryptor();
        var resultsByteArray = transform.TransformFinalBlock(data, 0, data.Length);
        return Convert.ToBase64String(resultsByteArray);
    }

    public static string Decrypt(string encryptedString)
    {
        var data = Convert.FromBase64String(encryptedString);
        var tripleDes = GetCryproProvider();
        var transform = tripleDes.CreateDecryptor();
        var resultsByteArray = transform.TransformFinalBlock(data, 0, data.Length);
        return Encoding.UTF8.GetString(resultsByteArray);
    }
}

1

Я изменил это :

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

к этому:

    public string ByteArrToString(byte[] byteArr)
    {
        string temp = "";
        foreach (byte b in byteArr)
            temp += b.ToString().PadLeft(3, '0');
        return temp;
    }

1

Используя встроенную библиотеку криптографии .Net, этот пример показывает, как использовать Advanced Encryption Standard (AES).

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

namespace Aes_Example
{
    class AesExample
    {
        public static void Main()
        {
            try
            {

                string original = "Here is some data to encrypt!";

                // Create a new instance of the Aes
                // class.  This generates a new key and initialization 
                // vector (IV).
                using (Aes myAes = Aes.Create())
                {

                    // Encrypt the string to an array of bytes.
                    byte[] encrypted = EncryptStringToBytes_Aes(original, myAes.Key, myAes.IV);

                    // Decrypt the bytes to a string.
                    string roundtrip = DecryptStringFromBytes_Aes(encrypted, myAes.Key, myAes.IV);

                    //Display the original data and the decrypted data.
                    Console.WriteLine("Original:   {0}", original);
                    Console.WriteLine("Round Trip: {0}", roundtrip);
                }

            }
            catch (Exception e)
            {
                Console.WriteLine("Error: {0}", e.Message);
            }
        }
        static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key,byte[] IV)
        {
            // Check arguments.
            if (plainText == null || plainText.Length <= 0)
                throw new ArgumentNullException("plainText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");
            byte[] encrypted;
            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption.
                using (MemoryStream msEncrypt = new MemoryStream())
                {
                    using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                        {

                            //Write all data to the stream.
                            swEncrypt.Write(plainText);
                        }
                        encrypted = msEncrypt.ToArray();
                    }
                }
            }


            // Return the encrypted bytes from the memory stream.
            return encrypted;

        }

        static string DecryptStringFromBytes_Aes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments.
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");

            // Declare the string used to hold
            // the decrypted text.
            string plaintext = null;

            // Create an Aes object
            // with the specified key and IV.
            using (Aes aesAlg = Aes.Create())
            {
                aesAlg.Key = Key;
                aesAlg.IV = IV;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for decryption.
                using (MemoryStream msDecrypt = new MemoryStream(cipherText))
                {
                    using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                        {

                            // Read the decrypted bytes from the decrypting stream
                            // and place them in a string.
                            plaintext = srDecrypt.ReadToEnd();
                        }
                    }
                }

            }

            return plaintext;

        }
    }
}

0

Я знаю, что вы сказали, что вас не волнует, насколько это безопасно, но если вы выбрали DES, вы могли бы также использовать AES, это более современный метод шифрования.


0

Я использовал принятый ответ Марка Бриттингема, и он мне очень помог. Недавно мне пришлось отправить зашифрованный текст в другую организацию, и здесь возникли некоторые проблемы. ОП не требует этих опций, но так как это популярный вопрос, я публикую свою модификацию ( Encryptи Decryptфункции заимствованы здесь ):

  1. Различный IV для каждого сообщения - объединяет IV байта с байтами шифра перед получением гекса. Конечно, это соглашение, которое необходимо передать сторонам, получающим зашифрованный текст.
  2. Позволяет два конструктора - один по умолчанию RijndaelManaged значений и значений свойств (на основе взаимного соглашения между сторонами, шифрующими и дешифрующими)

Вот класс (тестовый образец в конце):

/// <summary>
/// Based on https://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndaelmanaged(v=vs.110).aspx
/// Uses UTF8 Encoding
///  http://security.stackexchange.com/a/90850
/// </summary>
public class AnotherAES : IDisposable
{
    private RijndaelManaged rijn;

    /// <summary>
    /// Initialize algo with key, block size, key size, padding mode and cipher mode to be known.
    /// </summary>
    /// <param name="key">ASCII key to be used for encryption or decryption</param>
    /// <param name="blockSize">block size to use for AES algorithm. 128, 192 or 256 bits</param>
    /// <param name="keySize">key length to use for AES algorithm. 128, 192, or 256 bits</param>
    /// <param name="paddingMode"></param>
    /// <param name="cipherMode"></param>
    public AnotherAES(string key, int blockSize, int keySize, PaddingMode paddingMode, CipherMode cipherMode)
    {
        rijn = new RijndaelManaged();
        rijn.Key = Encoding.UTF8.GetBytes(key);
        rijn.BlockSize = blockSize;
        rijn.KeySize = keySize;
        rijn.Padding = paddingMode;
        rijn.Mode = cipherMode;
    }

    /// <summary>
    /// Initialize algo just with key
    /// Defaults for RijndaelManaged class: 
    /// Block Size: 256 bits (32 bytes)
    /// Key Size: 128 bits (16 bytes)
    /// Padding Mode: PKCS7
    /// Cipher Mode: CBC
    /// </summary>
    /// <param name="key"></param>
    public AnotherAES(string key)
    {
        rijn = new RijndaelManaged();
        byte[] keyArray = Encoding.UTF8.GetBytes(key);
        rijn.Key = keyArray;
    }

    /// <summary>
    /// Based on https://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndaelmanaged(v=vs.110).aspx
    /// Encrypt a string using RijndaelManaged encryptor.
    /// </summary>
    /// <param name="plainText">string to be encrypted</param>
    /// <param name="IV">initialization vector to be used by crypto algorithm</param>
    /// <returns></returns>
    public byte[] Encrypt(string plainText, byte[] IV)
    {
        if (rijn == null)
            throw new ArgumentNullException("Provider not initialized");

        // Check arguments.
        if (plainText == null || plainText.Length <= 0)
            throw new ArgumentNullException("plainText cannot be null or empty");
        if (IV == null || IV.Length <= 0)
            throw new ArgumentNullException("IV cannot be null or empty");
        byte[] encrypted;

        // Create a decrytor to perform the stream transform.
        using (ICryptoTransform encryptor = rijn.CreateEncryptor(rijn.Key, IV))
        {
            // Create the streams used for encryption.
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        //Write all data to the stream.
                        swEncrypt.Write(plainText);
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        }
        // Return the encrypted bytes from the memory stream.
        return encrypted;
    }//end EncryptStringToBytes

    /// <summary>
    /// Based on https://msdn.microsoft.com/en-us/library/system.security.cryptography.rijndaelmanaged(v=vs.110).aspx
    /// </summary>
    /// <param name="cipherText">bytes to be decrypted back to plaintext</param>
    /// <param name="IV">initialization vector used to encrypt the bytes</param>
    /// <returns></returns>
    public string Decrypt(byte[] cipherText, byte[] IV)
    {
        if (rijn == null)
            throw new ArgumentNullException("Provider not initialized");

        // Check arguments.
        if (cipherText == null || cipherText.Length <= 0)
            throw new ArgumentNullException("cipherText cannot be null or empty");
        if (IV == null || IV.Length <= 0)
            throw new ArgumentNullException("IV cannot be null or empty");

        // Declare the string used to hold the decrypted text.
        string plaintext = null;

        // Create a decrytor to perform the stream transform.
        using (ICryptoTransform decryptor = rijn.CreateDecryptor(rijn.Key, IV))
        {
            // Create the streams used for decryption.
            using (MemoryStream msDecrypt = new MemoryStream(cipherText))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        // Read the decrypted bytes from the decrypting stream and place them in a string.
                        plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
        }

        return plaintext;
    }//end DecryptStringFromBytes

    /// <summary>
    /// Generates a unique encryption vector using RijndaelManaged.GenerateIV() method
    /// </summary>
    /// <returns></returns>
    public byte[] GenerateEncryptionVector()
    {
        if (rijn == null)
            throw new ArgumentNullException("Provider not initialized");

        //Generate a Vector
        rijn.GenerateIV();
        return rijn.IV;
    }//end GenerateEncryptionVector


    /// <summary>
    /// Based on https://stackoverflow.com/a/1344255
    /// Generate a unique string given number of bytes required.
    /// This string can be used as IV. IV byte size should be equal to cipher-block byte size. 
    /// Allows seeing IV in plaintext so it can be passed along a url or some message.
    /// </summary>
    /// <param name="numBytes"></param>
    /// <returns></returns>
    public static string GetUniqueString(int numBytes)
    {
        char[] chars = new char[62];
        chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
        byte[] data = new byte[1];
        using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
        {
            data = new byte[numBytes];
            crypto.GetBytes(data);
        }
        StringBuilder result = new StringBuilder(numBytes);
        foreach (byte b in data)
        {
            result.Append(chars[b % (chars.Length)]);
        }
        return result.ToString();
    }//end GetUniqueKey()

    /// <summary>
    /// Converts a string to byte array. Useful when converting back hex string which was originally formed from bytes.
    /// </summary>
    /// <param name="hex"></param>
    /// <returns></returns>
    public static byte[] StringToByteArray(String hex)
    {
        int NumberChars = hex.Length;
        byte[] bytes = new byte[NumberChars / 2];
        for (int i = 0; i < NumberChars; i += 2)
            bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
        return bytes;
    }//end StringToByteArray

    /// <summary>
    /// Dispose RijndaelManaged object initialized in the constructor
    /// </summary>
    public void Dispose()
    {
        if (rijn != null)
            rijn.Dispose();
    }//end Dispose()
}//end class

а также..

Вот тестовый образец:

class Program
{
    string key;
    static void Main(string[] args)
    {
        Program p = new Program();

        //get 16 byte key (just demo - typically you will have a predetermined key)
        p.key = AnotherAES.GetUniqueString(16);

        string plainText = "Hello World!";

        //encrypt
        string hex = p.Encrypt(plainText);

        //decrypt
        string roundTrip = p.Decrypt(hex);

        Console.WriteLine("Round Trip: {0}", roundTrip);
    }

    string Encrypt(string plainText)
    {
        Console.WriteLine("\nSending (encrypt side)...");
        Console.WriteLine("Plain Text: {0}", plainText);
        Console.WriteLine("Key: {0}", key);
        string hex = string.Empty;
        string ivString = AnotherAES.GetUniqueString(16);
        Console.WriteLine("IV: {0}", ivString);
        using (AnotherAES aes = new AnotherAES(key))
        {
            //encrypting side
            byte[] IV = Encoding.UTF8.GetBytes(ivString);

            //get encrypted bytes (IV bytes prepended to cipher bytes)
            byte[] encryptedBytes = aes.Encrypt(plainText, IV);
            byte[] encryptedBytesWithIV = IV.Concat(encryptedBytes).ToArray();

            //get hex string to send with url
            //this hex has both IV and ciphertext
            hex = BitConverter.ToString(encryptedBytesWithIV).Replace("-", "");
            Console.WriteLine("sending hex: {0}", hex);
        }

        return hex;
    }

    string Decrypt(string hex)
    {
        Console.WriteLine("\nReceiving (decrypt side)...");
        Console.WriteLine("received hex: {0}", hex);
        string roundTrip = string.Empty;
        Console.WriteLine("Key " + key);
        using (AnotherAES aes = new AnotherAES(key))
        {
            //get bytes from url
            byte[] encryptedBytesWithIV = AnotherAES.StringToByteArray(hex);

            byte[] IV = encryptedBytesWithIV.Take(16).ToArray();

            Console.WriteLine("IV: {0}", System.Text.Encoding.Default.GetString(IV));

            byte[] cipher = encryptedBytesWithIV.Skip(16).ToArray();

            roundTrip = aes.Decrypt(cipher, IV);
        }
        return roundTrip;
    }
}

введите описание изображения здесь


-2

Я думаю, что это самый простой в мире!

string encrypted = "Text".Aggregate("", (c, a) => c + (char) (a + 2));

Тестовое задание

 Console.WriteLine(("Hello").Aggregate("", (c, a) => c + (char) (a + 1)));
            //Output is Ifmmp
 Console.WriteLine(("Ifmmp").Aggregate("", (c, a) => c + (char)(a - 1)));
            //Output is Hello

ROT ... 1? В самом деле? OP даже вызвал ROT13 как пример того, что он не хотел делать.
user812786
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.