Dados ofuscados simples e inseguros, “ofuscação”?


426

Estou procurando uma funcionalidade ofuscação muito simples (como criptografar e descriptografar, mas não necessariamente segura) para alguns dados. Não é missão crítica. Preciso de algo para manter as pessoas honestas, mas algo um pouco mais forte que o ROT13 ou o Base64 .

Prefiro algo que já esteja incluído no .NET framework 2.0, para que não precise me preocupar com nenhuma dependência externa.

Eu realmente não quero ter que mexer com chaves públicas / privadas etc. Não sei muito sobre criptografia, mas sei o suficiente para saber que qualquer coisa que escrevi seria menos que inútil ... Na verdade, Eu provavelmente estragaria a matemática e tornaria trivial quebrar.


3
Oi Mark - não há problema. Eu me senti mal por ter que aceitar a resposta da richdiet, pois realmente usei a solução dele e funcionou muito bem. No entanto, voltei aqui para ler as outras respostas, e a sua realmente é melhor. Não há razão para dizer às pessoas para usar algo que, enquanto funciona, não é realmente uma ótima maneira de fazer algo quando há uma resposta melhor disponível.
precisa saber é o seguinte

3
Economize horas e use o HttpServerUtility.UrlTokenEn / Decode para converter para frente e para trás das matrizes de bytes em uma sequência amigável de URL.
Praesagus

32
+1 por não tentar criar seu próprio design inteligente. Você pode não saber muito sobre criptografia, mas o fato de saber que isso coloca você na frente da maioria dos desenvolvedores que conheci que não sabem muito sobre criptografia, mas pensam que podem criar sua própria solução de qualquer maneira.
Dinah

6
Atenção: Muitas das respostas nesta pergunta são apenas criptografia não autenticada. Isso significa que o invasor pode alterar os dados sem que o aplicativo perceba . Isso também leva a outras vulnerabilidades sérias (como descriptografia sem chave devido ao preenchimento do oracle). TL; DR: Não use o código nas respostas dadas, se você não concorda com isso ou não entende o que eu acabei de dizer.
usr

36
Nenhuma resposta para esta pergunta descreve criptografia segura. Use a resposta do jbtule em Criptografar e descriptografar uma string .
CodesInChaos

Respostas:


471

Outras respostas aqui funcionam bem, mas o AES é um algoritmo de criptografia mais seguro e atualizado. Esta é uma classe que eu obtive há alguns anos para executar a criptografia AES que modifiquei ao longo do tempo para ser mais amigável para aplicativos da Web (por exemplo, eu criei métodos de Criptografia / Descriptografia que funcionam com cadeias de caracteres amigáveis ​​à URL). Ele também possui os métodos que funcionam com matrizes de bytes.

NOTA: você deve usar valores diferentes nas matrizes Chave (32 bytes) e Vetor (16 bytes)! Você não gostaria que alguém descobrisse suas chaves apenas assumindo que você usou esse código como está! Tudo o que você precisa fazer é alterar alguns dos números (deve ser <= 255) nas matrizes Chave e Vetor (deixei um valor inválido na matriz Vetor para garantir que você faça isso ...). Você pode usar https://www.random.org/bytes/ para gerar um novo conjunto facilmente:

É fácil usá-lo: instancie a classe e chame (geralmente) EncryptToString (string StringToEncrypt) e DecryptString (string StringToDecrypt) como métodos. Não poderia ser mais fácil (ou mais seguro) depois que você tiver essa classe.


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 - Isso é feito de propósito, para que você altere os valores nas matrizes, como Mark observa no segundo parágrafo.
Pauk

42
Você não deve usar o IV assim. Para determinadas duas mensagens, elas não deveriam ter sido criptografadas com a mesma chave e o mesmo IV. O IV deve ser aleatório para cada mensagem, anexado ao fluxo de criptografia e lido antes da descriptografia. crypto.stackexchange.com/a/82/1934
jbtule

30
O uso de um IV aleatório para cada mensagem não é exótico ou novo, apenas importante e faz parte do design do algoritmo. Usar um IV previsível para cada mensagem é um erro criptográfico comum que não precisa ser perpetuado.
Jbtule

14
Observe também que uma conseqüência do uso do CBC como modo é que você provavelmente estará vulnerável a ataques de preenchimento de oráculo . Use criptografia autenticada e, sempre que possível, não implemente criptografia .
Stephen Touset

57
Aviso de segurança: não use este código Apesar de ser a resposta aceita, há graves problemas de segurança mencionados nos comentários acima que o autor continua a ignorar por 8 anos.
jbtule

176

Limpei o SimpleAES (acima) para o meu uso. Corrigidos métodos complicados de criptografia / descriptografia; métodos separados para codificação de buffers de bytes, seqüências de caracteres e seqüências de caracteres compatíveis com URL; fez uso de bibliotecas existentes para codificação de URL.

O código é pequeno, mais simples, mais rápido e a saída é mais concisa. Por exemplo, johnsmith@gmail.comproduz:

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
Ao decodificar, tive que substituir o espaço por + para que ele funcionasse com o QueryString no Chrome: (new SimplerAES ()). Decrypt (Request.QueryString ["myParam"]. Replace ('', '+'));
live-love

20
Nunca use um vetor de inicialização constante, consulte: crypto.stackexchange.com/questions/66/… para obter mais informações sobre o porquê. Em vez disso, gere um novo IV para cada criptografia e anexe-o ao cryptotext, muito melhor e menos difícil.
quer

2
Esteja ciente de que a saída do método EncryptToUrl nesta solução (ou qualquer uso de uma string base 64 UrlEncoded em geral) não funcionará por padrão no IIS 7 quando usado como parte de um caminho de URL (não string de consulta), como em uma rota do ASP.NET MVC, devido a uma configuração de segurança do IIS 7. Para obter mais informações, consulte: stackoverflow.com/a/2014121/12484
Jon Schneider

5
@ TomHeard Como alguém faria isso com o código acima?
MKII

26
Aviso de segurança: Não use este código Ver comentário por @TomHeard
jbtule 8/17/17

36

Sim, adicione o System.Securityassembly, importe o System.Security.Cryptographyespaço para nome. Aqui está um exemplo simples de criptografia 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);

5
Essa é uma criptografia bidirecional compacta e agradável. A única ressalva é que o DES não é mais considerado segurança de ponta. Esse título agora vai para o algoritmo AES que discuto abaixo.
22868 Mark Brittingham

@richdiet. Me desculpe, eu inaceitei sua resposta. A outra resposta com mais de 37 votos, porque é mais atual. Obrigado pela sua resposta, pois ainda é uma boa.
Matt Dawdy

14
@ MarkBrittingham: qualquer cifra de bloco sem a função de encadeamento de blocos, vetor de inicialização e preenchimento adequado é insegura. Usar o DES é o problema menos importante com esse esquema.
Hubert Kario

2
Então, onde a chave é usada?
31413 Alex

22
Aviso de segurança: Não use este código Ver comentário por @HubertKario
jbtule 8/17/17

28

Apenas pensei em acrescentar que melhorei o SimplerAES do Mud adicionando um IV aleatório que é passado de volta para dentro da cadeia criptografada. Isso melhora a criptografia, pois criptografar a mesma sequência resultará em uma saída diferente a 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();
    }
}

E teste de unidade de bônus

[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) Não use System.Randomcomo RNG. 2) Isto é completamente quebrado contra ataques de texto cifrado escolhido (em particular padding-oráculos)
CodesInChaos

21
Aviso de segurança: Não use este código veja o comentário acima por @CodesInChaos
jbtule 8/17/17

@jbtule, por favor, não se engane com todas as pessoas que não querem complicações, apenas criptografem, e também que não se preocupe com o ataque, - Por favor, não faça o pedido se quiser dar sugestões.
Virbhadrasinh

@Virbhadrasinh, não há equívocos da minha parte, na verdade, é exatamente o oposto. Se você vai usar o AES, usá-lo corretamente é muito importante, usá-lo incorretamente e dizer que não há problema em não usá-lo para algo importante, é equivocado.
Jbtule

1
@ Corey Não está gritando e seguiu a melhor prática para lidar com problemas de segurança nas respostas de estouro de pilha. Se você deseja um link, ele foi postado nos comentários da pergunta. Mas eu vou colocá-lo aqui para você também stackoverflow.com/a/10366194/637783
jbtule

12

Uma variante da resposta de Marcas (excelente)

  • Adicione "usando" s
  • Tornar a classe IDisposable
  • Remova o código de codificação de URL para tornar o exemplo mais simples.
  • Adicione um acessório de teste simples para demonstrar o uso

Espero que isto ajude

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

Boa resposta. Uma coisa no método Dispose você precisará rijndael elenco para IDisposable ou você receberá um erro de nível de proteção chamando Descarte
John ClearZ

8
Nunca use um vetor de inicialização constante, consulte: crypto.stackexchange.com/questions/66/… para obter mais informações sobre o porquê. Em vez disso, gere um novo IV para cada criptografia e anexe-o ao cryptotext, muito melhor e menos difícil.
Tom Heard

5
@Chalky Ao criptografar, você usa a classe Rijndael para gerar um IV aleatório para você ( msdn.microsoft.com/en-us/library/… ), faça sua criptografia e, em seguida, pegue o IV da instância Rijndael usando a propriedade IV . Em seguida, você anexa (ou anexa, ou funciona, desde que sua descriptografia o pegue do mesmo lado) no texto criptografado. Ao descriptografar, você obtém o IV dos dados recebidos (o tamanho da propriedade IV é o mesmo que a propriedade BlockSize dividida por 8) e, em seguida, passa-o para sua instância de descriptografia antes de descriptografar.
21414 Tom Tom Ouviu

2
@ Cally Note que o IV não precisa ser secreto, apenas precisa ser único para cada mensagem enviada.
Tom Heard

20
Aviso de segurança: Não use este código Veja os comentários acima por @TomHeard
jbtule 8/17/17

8

[EDIT] Anos depois, voltei a dizer: não faça isso! Consulte O que há de errado com a criptografia XOR? para detalhes.

Uma criptografia bidirecional muito simples e fácil é a criptografia XOR.

  1. Crie uma senha. Vamos ser assim mypass.
  2. Converta a senha em binário (de acordo com ASCII). A senha se torna 01101101 01111001 01110000 01100001 01110011 01110011.
  3. Pegue a mensagem que você deseja codificar. Converta isso em binário também.
  4. Veja o tamanho da mensagem. Se o tamanho da mensagem for 400 bytes, transforme a senha em uma sequência de 400 bytes, repetindo-a várias vezes. Tornaria-se 01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011 ... (ou mypassmypassmypass...)
  5. XOR a mensagem com a senha longa.
  6. Envie o resultado.
  7. Outra vez, XOR a mensagem criptografada com a mesma senha ( mypassmypassmypass...).
  8. Aí está a sua mensagem!

10
@Ryan Nem toda situação exige hashes criptograficamente seguros ou cifras de Rijndael. "Criptografia simples de duas vias" pode realmente significar simples , o que sugere xor ou mesmo ROT13.

1
@ Ryan: AES com chave de criptografia estática, nenhum vetor de inicialização e nenhuma função encadeamento bloco é apenas nome fantasia para criptografia XOR, você está apenas usando realmente extravagante KDF ...
Hubert Kario

17
Aviso de segurança: não use este código A criptografia XOR com uma chave repetida está trivialmente quebrada.
jbtule

7

Combinei o que achei melhor em várias respostas e comentários.

  • Vetor de inicialização aleatória anexado ao texto criptografado (@jbtule)
  • Use TransformFinalBlock () em vez de MemoryStream (@RenniePet)
  • Nenhuma chave pré-preenchida para evitar que alguém copie e cole um desastre
  • Descarte e uso adequados de padrões

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

Atualização 18/07/2015: Corrigido erro no método Encrypt () privado pelos comentários de @bpsilver e @Evereq. O IV foi acidentalmente criptografado, agora é anexado em texto não criptografado, conforme esperado por Decrypt ().


Você deve criptografar todo o inputBuffer com IV anexado, caso contrário os 16 primeiros caracteres da cadeia de caracteres para criptografia serão perdidos. Portanto, seu código deve ler:return _encryptor.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);
bpsilver

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

1
Isso faria a mesma coisa que a implementação atual, não faria?
Angularsen 17/11

1
"ME PREENCHE COM 16, 24 OU 32 CARROS" bem, não, não antes da decodificação da base 64. E uma chave deve ser aleatória. Realmente aleatório.
Maarten Bodewes

1
Percebo que o @bpsilver está certo e o código fornecido não funcionará sem sua correção: o método encrypt retorna dados criptografados sem IV (ele primeiro adiciona IV ao inputbuffer, mas depois criptografa e retorna dados sem ele). Portanto, se possível, atualize a resposta com o código dele. (Nota: eu testei apenas métodos com parâmetros byte [], não strings). Obrigado!
Evereq

6

Se você deseja apenas uma criptografia simples (ou seja, possível que um cracker determinado quebre, mas bloqueie a maioria dos usuários casuais), basta escolher duas frases secretas de igual comprimento, digamos:

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

e xou seus dados com os dois (repetindo as senhas, se necessário) (a) . Por exemplo:

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

Alguém pesquisando seu binário pode muito bem achar que a cadeia de DNA é uma chave, mas é improvável que ache que o código C seja outra coisa senão a memória não inicializada salva com seu binário.


(a) Lembre-se de que é uma criptografia muito simples e, de acordo com algumas definições, pode não ser considerada nenhuma criptografia (uma vez que a intenção da criptografia é impedir o acesso não autorizado e não apenas torná-lo mais difícil). Embora, é claro, até a criptografia mais forte seja insegura quando alguém está de pé sobre os porta-chaves com um cano de aço.

Conforme declarado na primeira frase, esse é um meio de dificultar o suficiente para o atacante casual continuar com ele. É semelhante à prevenção de roubos em sua casa - você não precisa torná-lo inexpugnável, você só precisa torná-lo menos impregnável do que a casa ao lado :-)


3
Idéia interessante. Não tenho certeza se eu "acreditaria" no código-fonte em um binário - mas que tal adaptar a idéia para usar uma mensagem de erro como senha?
Jon Skeet

1
Prefiro usar um hash md5 de alguma sequência de texto não criptografado que já exista no aplicativo (mensagem de erro ou algo assim).
Treb

2
Por que eles precisam ter o mesmo comprimento? Na verdade, parece melhor se eles tiverem comprimentos diferentes. Dessa forma, o comprimento do seu operando XOR efetivo é LCM (comprimento1, comprimento2), em vez de apenas comprimento1 (= comprimento2). O que, obviamente, se torna length1 * length2 se os comprimentos forem relativamente primos.
Fantius 27/10/11

15
Aviso de segurança: não use este código chave de repetição XOR é facilmente quebrável com apenas alguns conhecimentos gerais dos dados que estão sendo criptografados.
Jbtule 8/17

3
@ jbtule, se você lesse a pergunta, perceberia que não era necessária nenhuma criptografia mais segura. Especificamente, a referência a 'criptografia simples', 'não de missão crítica' e apenas 'mantendo pessoas honestas e honestas'. Você também deve ler o meu primeiro parágrafo, que explicitamente ressalta o fato de que não bloqueará determinados invasores.
paxdiablo

5

A criptografia é fácil: como outras pessoas apontaram, existem classes no espaço para nome System.Security.Cryptography que fazem todo o trabalho para você. Use-os em vez de qualquer solução caseira.

Mas a descriptografia também é fácil. O problema que você tem não é o algoritmo de criptografia, mas a proteção do acesso à chave usada para descriptografia.

Eu usaria uma das seguintes soluções:

  • DPAPI usando a classe ProtectedData com escopo CurrentUser. Isso é fácil, pois você não precisa se preocupar com uma chave. Os dados só podem ser descriptografados pelo mesmo usuário; portanto, não é bom compartilhar dados entre usuários ou máquinas.

  • DPAPI usando a classe ProtectedData com escopo LocalMachine. É bom, por exemplo, para proteger dados de configuração em um único servidor seguro. Mas qualquer pessoa que possa fazer login na máquina pode criptografá-la, portanto não adianta, a menos que o servidor esteja seguro.

  • Qualquer algoritmo simétrico. Normalmente, uso o método SymmetricAlgorithm.Create () estático se não me importo com o algoritmo usado (na verdade, é Rijndael por padrão). Nesse caso, você precisa proteger sua chave de alguma forma. Por exemplo, você pode ofuscá-lo de alguma forma e ocultá-lo no seu código. Mas lembre-se de que qualquer pessoa inteligente o suficiente para descompilar seu código provavelmente poderá encontrar a chave.


5

Eu queria postar minha solução, pois nenhuma das opções acima é tão simples quanto a minha. Diz-me o que pensas:

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

Isso pressupõe que a MachineKey do servidor usado para criptografar o valor seja a mesma usada para descriptografar o valor. Se desejar, você pode especificar uma MachineKey estática no Web.config para que seu aplicativo possa descriptografar / criptografar dados, independentemente de onde eles são executados (por exemplo, servidor de desenvolvimento vs. produção). Você pode gerar uma chave estática da máquina seguindo estas instruções .


note que essa abordagem só pode ser usada para o aplicativo ASP.NET.
Feru 8/03/2017

2

O espaço para nome System.Security.Cryptographycontém as classes TripleDESCryptoServiceProvidereRijndaelManaged

Não se esqueça de adicionar uma referência à System.Securitymontagem.


8
Não que eu tenha votado mal, mas por que a idade de uma pergunta importa ao votar?
precisa saber é o seguinte

2

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

Eu mudei isso :

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

para isso:

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

1

Usando a biblioteca interna de criptografia .Net, este exemplo mostra como usar o 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

Sei que você disse que não se importa com a segurança, mas se você escolher o DES, também poderá usar o AES, pois é o método de criptografia mais atualizado.


0

Eu tenho usado a resposta aceita por Mark Brittingham e isso me ajudou muito. Recentemente, tive que enviar texto criptografado para uma organização diferente e foi aí que surgiram alguns problemas. O OP não exige essas opções, mas como essa é uma pergunta popular, estou postando minha modificação ( Encrypte Decryptfunções emprestadas daqui ):

  1. IV diferente para cada mensagem - Concatena bytes IV para os bytes de cifra antes de obter o hexadecimal. Obviamente, essa é uma convenção que precisa ser transmitida às partes que recebem o texto cifrado.
  2. Permite dois construtores - um para RijndaelManagedvalores padrão e outro para valores de propriedade (com base em acordo mútuo entre as partes criptografadas e descriptografadas)

Aqui está a classe (amostra de teste no 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

e..

Aqui está a amostra de teste:

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

insira a descrição da imagem aqui


-2

Eu acho que este é o mais simples do mundo!

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

Teste

 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? Mesmo? OP até chamou o ROT13 como um exemplo do que ele não queria fazer.
User812786
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.