¿Simple "inseguridad" bidireccional de datos?


426

Estoy buscando una funcionalidad de ofuscación muy simple (como cifrar y descifrar, pero no necesariamente segura) para algunos datos. No es de misión crítica. Necesito algo para mantener a la gente honesta honesta, pero algo un poco más fuerte que ROT13 o Base64 .

Prefiero algo que ya está incluido en .NET Framework 2.0, por lo que no tengo que preocuparme por ninguna dependencia externa.

Realmente no quiero tener que perder el tiempo con las claves públicas / privadas, etc. No sé mucho sobre el cifrado, pero sé lo suficiente como para saber que cualquier cosa que escribí sería menos que inútil ... De hecho, Probablemente arruinaría las matemáticas y haría trivial el crack.


3
Hola Mark, no hay problema. Me sentí mal porque tuve que aceptar la respuesta de richdiet, ya que realmente usé su solución y funcionó bien. Sin embargo, seguí viniendo aquí para leer las otras respuestas, y la suya es realmente mejor. No hay razón para decirle a la gente que use algo que, si bien funciona, no es realmente una excelente manera de hacer algo cuando hay una mejor respuesta disponible.
Matt Dawdy

3
Ahorre horas y use HttpServerUtility.UrlTokenEn / Decode para convertir de un lado a otro los conjuntos de bytes en una cadena amigable para URL.
Praesagus

32
+1 por no intentar rodar tu propio diseño inteligente. Es posible que no sepa mucho sobre el cifrado, pero el hecho de que lo sabe le pone años luz por delante de la mayoría de los desarrolladores que he conocido que no saben mucho sobre cifrado pero creen que pueden crear su propia solución de todos modos.
Dinah

66
Atención: muchas de las respuestas en esta pregunta son solo encriptación no autenticada. Esto significa que el atacante puede cambiar los datos sin que la aplicación lo note . También conduce a otras vulnerabilidades graves (como descifrado sin clave debido al relleno de Oracle). TL; DR: No use el código en las respuestas dadas si no está de acuerdo con eso, o no entiende lo que acabo de decir.
Usr

36
Ni una sola respuesta a esta pregunta describe el cifrado seguro. Use la respuesta de jbtule en Cifrar y descifre una cadena en su lugar.
CodesInChaos

Respuestas:


471

Otras respuestas aquí funcionan bien, pero AES es un algoritmo de cifrado más seguro y actualizado. Esta es una clase que obtuve hace unos años para realizar el cifrado AES que he modificado con el tiempo para que sea más amigable con las aplicaciones web (por ejemplo, he creado métodos de cifrado / descifrado que funcionan con cadenas compatibles con URL). También tiene los métodos que funcionan con matrices de bytes.

NOTA: ¡debe usar diferentes valores en las matrices Key (32 bytes) y Vector (16 bytes)! ¡No querría que alguien descubriera sus claves simplemente asumiendo que utilizó este código tal como está! Todo lo que tiene que hacer es cambiar algunos de los números (deben ser <= 255) en las matrices Clave y Vector (dejé un valor no válido en la matriz Vector para asegurarme de que haga esto ...). Puede usar https://www.random.org/bytes/ para generar un nuevo conjunto fácilmente:

Usarlo es fácil: simplemente crea una instancia de la clase y luego llama (generalmente) EncryptToString (string StringToEncrypt) y DecryptString (string StringToDecrypt) como métodos. No podría ser más fácil (o más seguro) una vez que tenga esta clase en su lugar.


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: eso se hace a propósito para que cambie los valores en las matrices, como Mark señala en el segundo párrafo.
Pauk

42
No debe usar el IV de esta manera. Para dos mensajes dados, no deberían haberse cifrado con la misma Clave y el mismo IV. El IV debe ser aleatorio para cada mensaje, antepuesto al cryptostream, y leído antes del descifrado. crypto.stackexchange.com/a/82/1934
jbtule

30
Usar un IV aleatorio para cada mensaje no es exótico o nuevo, solo es importante y forma parte del diseño del algoritmo. Usar un IV predecible para cada mensaje es un error criptográfico común que no necesita perpetuarse.
jbtule

14
Tenga en cuenta también que una consecuencia del uso de CBC como modo es que es probable que sea vulnerable a los ataques de oráculo de relleno . Use cifrado autenticado y, siempre que sea posible, no implemente la criptografía usted mismo .
Stephen Touset

57
Advertencia de seguridad: no utilice este código A pesar de ser la respuesta aceptada, hay graves problemas de seguridad mencionados en los comentarios anteriores que el autor ha seguido ignorando durante 8 años.
jbtule

176

Limpié SimpleAES (arriba) para mi uso. Métodos de cifrado / descifrado contorneados fijos; métodos separados para codificar buffers de bytes, cadenas y cadenas compatibles con URL; hizo uso de las bibliotecas existentes para la codificación de URL.

El código es pequeño, más simple, más rápido y el resultado es más conciso. Por ejemplo, johnsmith@gmail.comproduce:

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

Código:

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
Al decodificar, tuve que reemplazar el espacio con + para que funcione con QueryString en Chrome: (nuevo SimplerAES ()). Descifrar (Request.QueryString ["myParam"]. Reemplazar ('', '+'));
live-love

20
No use nunca un Vector de inicialización constante, consulte: crypto.stackexchange.com/questions/66/… para obtener más información sobre por qué. En cambio, genere un nuevo IV para cada encriptación y añádalo al texto cifrado, mucho mejor y no demasiado.
Tom escuchó el

2
Tenga en cuenta que la salida del método EncryptToUrl en esta solución (o cualquier uso de una cadena base 64 codificada en Url en general) no funcionará de manera predeterminada en IIS 7 cuando se usa como parte de una ruta URL (no en una cadena de consulta), como en una ruta ASP.NET MVC, debido a una configuración de seguridad IIS 7. Para obtener más información, consulte: stackoverflow.com/a/2014121/12484
Jon Schneider

55
@TomHeard ¿Cómo se podría hacer eso con el código anterior?
MKII

26
Advertencia de seguridad: no utilice este código Ver comentario de @TomHeard
jbtule

36

Sí, agregue el System.Securityensamblado, importe el System.Security.Cryptographyespacio de nombres. Aquí hay un ejemplo simple de un cifrado de algoritmo simétrico (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);

55
Este es un cifrado bidireccional compacto y agradable. La única advertencia es que el DES ya no se considera seguridad de vanguardia. Ese título ahora va al algoritmo AES que analizo a continuación.
Mark Brittingham

@richdiet. Lo siento, no acepté tu respuesta. La otra responde con más de 37 votos porque es más actual. Gracias por su respuesta, ya que sigue siendo buena.
Matt Dawdy

14
@ MarkBrittingham: cualquier cifrado de bloque sin función de encadenamiento de bloque, vector de inicialización y relleno adecuado es inseguro. Usar DES es el problema menos importante con este esquema.
Hubert Kario el

2
Entonces, ¿dónde se usa la clave?
Alex

22
Advertencia de seguridad: no utilice este código Ver comentario de @HubertKario
jbtule

28

Solo pensé en agregar que he mejorado el SimplerAES de Mud al agregar un IV aleatorio que se devuelve dentro de la cadena encriptada. Esto mejora el cifrado ya que cifrar la misma cadena dará como resultado una salida diferente cada vez.

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

Y prueba de unidad de bonificación

[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) No usar System.Randomcomo RNG. 2) Esto está completamente roto contra los ataques de texto cifrado elegidos (en particular los oráculos de relleno)
CodesInChaos

21
Advertencia de seguridad: no utilice este código, vea el comentario anterior de @CodesInChaos
jbtule

@jbtule, por favor, no engañe a todas las personas que no desean complicaciones, solo encripten, y que tampoco desconfíen del ataque, - No ordenen si quieren dar sugerencias.
Virbhadrasinh

@Virbhadrasinh no hay equivocación de mi parte, de hecho, es todo lo contrario. Si va a usar AES, usarlo correctamente es bastante importante, usarlo incorrectamente y decir que está bien, no lo estoy usando para algo importante, está equivocado.
jbtule

1
@Corey No gritaba, y había seguido las mejores prácticas para tratar los problemas de seguridad en las respuestas de desbordamiento de pila. Si desea un enlace, se publicó en los comentarios de las preguntas. Pero también lo pondré aquí para usted stackoverflow.com/a/10366194/637783
jbtule

12

Una variante de respuesta de Marcas (excelente)

  • Agregar "usando" s
  • Hacer la clase IDisposable
  • Elimine el código de codificación de URL para simplificar el ejemplo.
  • Agregue un accesorio de prueba simple para demostrar el uso

Espero que esto ayude

[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();
        }
    }
}

Buena respuesta. Una cosa en el método Dispose necesitará convertir rijndael en IDisposable o obtendrá un error de nivel de protección llamando a Dispose
John ClearZ

8
No use nunca un Vector de inicialización constante, consulte: crypto.stackexchange.com/questions/66/… para obtener más información sobre por qué. En cambio, genere un nuevo IV para cada encriptación y añádalo al texto cifrado, mucho mejor y no demasiado.
Tom escuchó el

55
@Chalky Al cifrar, utiliza la clase Rijndael para generar un IV aleatorio para usted ( msdn.microsoft.com/en-us/library/… ), realice su cifrado y luego tome el IV de la instancia de Rijndael utilizando la propiedad IV . Luego lo antepone (o agrega, o funciona siempre que su descifrado lo tome del mismo lado) a su texto criptográfico. En el descifrado, luego extrae el IV de los datos recibidos (el tamaño de la propiedad IV es el mismo que la propiedad BlockSize dividido por 8), luego lo pasa a su instancia de descifrado antes de descifrarlo.
Tom Heard

2
@Chalky Tenga en cuenta que el IV no necesita ser secreto, solo debe ser único para cada mensaje enviado.
Tom Heard

20
Advertencia de seguridad: no use este código Vea los comentarios anteriores de @TomHeard
jbtule

8

[EDITAR] Años después, he vuelto para decir: ¡no hagas esto! Consulte ¿Qué tiene de malo el cifrado XOR?para detalles.

Un cifrado bidireccional muy simple y fácil es el cifrado XOR.

  1. Encuentra una contraseña. Hagamos que seamypass .
  2. Convierta la contraseña en binario (de acuerdo con ASCII). La contraseña se convierte en 01101101 01111001 01110000 01100001 01110011 01110011.
  3. Tome el mensaje que desea codificar. Convierta eso en binario, también.
  4. Mira la longitud del mensaje. Si la longitud del mensaje es de 400 bytes, convierta la contraseña en una cadena de 400 bytes repitiéndola una y otra vez. Se convertiría en 01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011 ... (omypassmypassmypass... )
  5. XOR el mensaje con la contraseña larga.
  6. Envía el resultado.
  7. Otra vez, XOR el mensaje cifrado con la misma contraseña (mypassmypassmypass... ).
  8. ¡Ahí está tu mensaje!

10
@Ryan No todas las situaciones requieren hashes criptográficamente seguros o cifrados Rijndael. "Cifrado simple de 2 vías" en realidad podría significar simple , lo que sugiere xor o incluso ROT13.

1
@Ryan: AES con clave de cifrado estático, sin vector de inicialización y sin función de encadenamiento de bloques es solo un nombre elegante para el cifrado XOR, solo estás usando KDF realmente elegante ...
Hubert Kario

17
Advertencia de seguridad: no utilice este código El cifrado XOR con una clave repetida está trivialmente roto.
jbtule

7

Combiné lo que encontré mejor de varias respuestas y comentarios.

  • Vector de inicialización aleatorio antepuesto a texto criptográfico (@jbtule)
  • Use TransformFinalBlock () en lugar de MemoryStream (@RenniePet)
  • Sin claves precargadas para evitar que nadie copie y pegue un desastre
  • Desechar y usar patrones adecuadamente

Código:

/// <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();
    }
}

Actualización 2015-07-18: Se corrigió el error en el método privado Encrypt () mediante comentarios de @bpsilver y @Evereq. IV fue encriptado accidentalmente, ahora se antepone en texto claro como se esperaba por Decrypt ().


Debe cifrar todo el inputBuffer con IV antepuesto, de lo contrario, se perderán los primeros 16 caracteres de la cadena para cifrar. Entonces su código debería leer:return _encryptor.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
bpsilver

2
En ese caso:byte [] inputBuffer = _encryptor.TransformFinalBlock(buffer, 0, buffer.Length); return _rijndael.IV.Concat(inputBuffer).ToArray();
bpsilver

1
Eso haría lo mismo que la implementación actual, ¿no?
angularsen

1
"LLENARME CON 16, 24 O 32 CHARS" bueno, no, no antes de la decodificación base 64. Y una clave debe ser aleatoria. Realmente al azar.
Maarten Bodewes

1
Noto que @bpsilver está bien, y el código proporcionado no funcionará sin su solución: el método de cifrado devuelve datos cifrados sin IV (primero agrega IV al buffer de entrada, pero luego cifra y devuelve datos sin él). Entonces, si es posible, solo actualice la respuesta con su código. (Nota: solo pruebo métodos con parámetros byte [], no cadenas). ¡Gracias!
Evereq

6

Si solo desea un cifrado simple (es decir, es posible que se rompa un cracker determinado, pero que bloquea a la mayoría de los usuarios ocasionales), simplemente elija dos frases de contraseña de igual longitud, por ejemplo:

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

y xor sus datos con ambos (bucle de las frases de contraseña si es necesario) (a) . Por ejemplo:

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

Alguien que busque en su binario puede pensar que la cadena de ADN es una clave, pero es poco probable que piense que el código C es algo más que memoria no inicializada guardada con su binario.


(a) Tenga en cuenta que este es un cifrado muy simple y, según algunas definiciones, puede no considerarse en absoluto cifrado (ya que la intención del cifrado es evitar acceso no autorizado en lugar de simplemente dificultarlo). Aunque, por supuesto, incluso el cifrado más fuerte es inseguro cuando alguien está parado sobre los llaveros con una tubería de acero.

Como se indicó en la primera oración, este es un medio para dificultar lo suficiente al atacante casual que seguirán adelante. Es similar a la prevención de robos en su hogar: no es necesario que sea inexpugnable, solo necesita que sea menos prevenible que la casa de al lado :-)


3
Idea interesante. No estoy seguro de "creer" el código fuente en un binario, pero ¿qué tal adaptar la idea para usar un mensaje de error como frase de contraseña?
Jon Skeet

1
Prefiero usar un hash md5 de alguna cadena de texto sin formato que ya existe en la aplicación (mensaje de error más o menos).
Treb

2
¿Por qué necesitan ser de igual longitud? En realidad parece mejor si son de diferentes longitudes. De esa manera, la longitud de su operando XOR efectivo es LCM (longitud1, longitud2), en lugar de solo longitud1 (= longitud2). Lo que, por supuesto, se convierte en length1 * length2 si las longitudes son relativamente primas.
Fantius

15
Advertencia de seguridad: no utilice este código La clave de repetición XOR se puede descifrar fácilmente con solo un conocimiento general de los datos que se cifran.
jbtule

3
@jbtule, si leyeras la pregunta, te darías cuenta de que no se requiere un cifrado más seguro. Específicamente, la referencia a 'encriptación simple', 'no crítica para la misión' y simplemente 'manteniendo a la gente honesta honesta'. También deberías leer mi primer párrafo que explícitamente señala el hecho de que no bloqueará a atacantes determinados.
paxdiablo

5

El cifrado es fácil: como otros han señalado, hay clases en el espacio de nombres System.Security.Cryptography que hacen todo el trabajo por usted. Úselos en lugar de cualquier solución local.

Pero el descifrado también es fácil. El problema que tiene no es el algoritmo de cifrado, sino proteger el acceso a la clave utilizada para el descifrado.

Usaría una de las siguientes soluciones:

  • DPAPI usando la clase ProtectedData con el alcance CurrentUser. Esto es fácil ya que no necesita preocuparse por una clave. Los datos solo pueden ser descifrados por el mismo usuario, por lo que no son buenos para compartir datos entre usuarios o máquinas.

  • DPAPI usando la clase ProtectedData con alcance LocalMachine. Bueno, por ejemplo, para proteger los datos de configuración en un único servidor seguro. Pero cualquiera que pueda iniciar sesión en la máquina puede cifrarlo, por lo que no sirve de nada a menos que el servidor sea seguro.

  • Cualquier algoritmo simétrico. Normalmente uso el método estático SymmetricAlgorithm.Create () si no me importa qué algoritmo se usa (de hecho, es Rijndael por defecto). En este caso, debe proteger su clave de alguna manera. Por ejemplo, puede ofuscarlo de alguna manera y ocultarlo en su código. Pero tenga en cuenta que cualquiera que sea lo suficientemente inteligente como para descompilar su código probablemente podrá encontrar la clave.


5

Quería publicar mi solución ya que ninguna de las soluciones anteriores es tan simple como la mía. Déjame saber lo que piensas:

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

Opcional

Esto supone que la MachineKey del servidor utilizado para cifrar el valor es la misma que la utilizada para descifrar el valor. Si lo desea, puede especificar una MachineKey estática en Web.config para que su aplicación pueda descifrar / cifrar datos independientemente de dónde se ejecute (por ejemplo, servidor de desarrollo frente a servidor de producción). Puede generar una clave de máquina estática siguiendo estas instrucciones .


tenga en cuenta que este enfoque solo se puede usar para la aplicación ASP.NET.
Feru

2

El espacio de nombres System.Security.Cryptographycontiene las clases TripleDESCryptoServiceProvideryRijndaelManaged

No olvide agregar una referencia al System.Securityensamblaje.


8
No es que haya votado mal, pero ¿por qué debería importar la edad de una pregunta al votar?
user247702

2

Usando TripleDESCryptoServiceProvider en 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

Cambié esto :

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

a esto:

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

1

Usando la biblioteca de criptografía .Net incorporada, este ejemplo muestra cómo usar el Estándar de cifrado avanzado (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

Sé que dijiste que no te importa lo seguro que es, pero si eliges DES, también podrías tomar AES , es el método de cifrado más actualizado.


0

He estado usando la respuesta aceptada por Mark Brittingham y me ha ayudado mucho. Recientemente tuve que enviar texto cifrado a una organización diferente y ahí surgieron algunos problemas. El OP no requiere estas opciones, pero como esta es una pregunta popular, estoy publicando mi modificación ( Encrypty Decryptfunciones prestadas desde aquí ):

  1. IV diferente para cada mensaje: concatena los bytes IV a los bytes de cifrado antes de obtener el hexadecimal. Por supuesto, esta es una convención que debe transmitirse a las partes que reciben el texto cifrado.
  2. Permite dos constructores: uno para los RijndaelManagedvalores predeterminados y otro para los valores de propiedad que se pueden especificar (según un acuerdo mutuo entre las partes de cifrado y descifrado)

Aquí está la clase (muestra de prueba al final):

/// <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

y..

Aquí está la muestra de prueba:

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

ingrese la descripción de la imagen aquí


-2

¡Creo que este es el más simple del mundo!

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

Prueba

 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? De Verdad? OP incluso llamó a ROT13 como un ejemplo de lo que no quería hacer.
user812786
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.