简单的不安全的双向数据“混淆”?

443
我正在寻找非常简单的混淆功能(类似于加密和解密,但不一定安全)来处理一些数据。这不是关键任务。我需要某种东西来保持诚实的人诚实,但比ROT13Base64更强大。
我更喜欢使用已包含在.NET框架2.0中的功能,这样我就不必担心任何外部依赖关系。
我真的不想要与公钥/私钥等打交道。我对加密知之甚少,但我确实知道,我写的任何东西都不值一文......事实上,我可能会搞砸数学并使其容易被破解。

3
嗨 Mark -- 没问题。我感到很抱歉,我不得不取消接受richdiet的答案,因为我确实使用了他的解决方案,而且它也完全有效。然而,我一直回来阅读其他答案,你的确更好。没有理由告诉人们使用一些虽然可行但不是最好方式去做一件事情,当有更好的答案可用时。 - Matt Dawdy
3
使用HttpServerUtility.UrlTokenEn/Decode可以将字节数组转换成易于在URL中使用的字符串,并且能够在两者之间进行相互转换,这样可以为你节省很多时间。 - Praesagus
33
+1表示你并没有试图自己设计一个聪明的方案。你可能不太了解加密,但是你知道这一点让你比我遇到的大多数开发人员更加先进,他们对加密的了解不多,但仍然认为自己可以创建自己的解决方案。 - Dinah
6
注意:这个问题中的许多答案只使用了未经认证的加密方式。这意味着攻击者可以在应用程序不知情的情况下更改数据。这还会导致其他严重的漏洞,比如由于填充预言而导致无需密钥即可解密。简而言之:如果你对此不满意或者不理解我刚才说的话,请不要使用这些答案中的代码。 - usr
37
这个问题的任何答案都没有描述安全加密。请使用jbtule在Encrypt and decrypt a string中的回答。 - CodesInChaos
显示剩余5条评论
17个回答

474

这里的其他答案都很好,但AES是一种更安全和最新的加密算法。这是我几年前获取的一个类,用于执行AES加密,我随着时间的推移对其进行了修改,使其更适合Web应用程序(例如,我构建了使用URL友好字符串工作的Encrypt/Decrypt方法)。它还具有可以处理字节数组的方法。

注意:在Key(32字节)和Vector(16字节)数组中应使用不同的值!您不希望有人仅通过假定您按原样使用此代码就能找出您的密钥!您只需要更改Key和Vector数组中的一些数字(必须<=255)即可(我在Vector数组中留下了一个无效值,以确保您这样做...)。您可以使用https://www.random.org/bytes/轻松生成新的设置:

使用它很容易:只需实例化该类,然后调用(通常)EncryptToString(string StringToEncrypt)和DecryptString(string StringToDecrypt)方法即可。一旦您将此类放置在适当位置,它就再也不能更容易(或更安全)了。


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


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

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


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

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

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

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

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

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


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

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

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

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

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

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

        return encrypted;
    }

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

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

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

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

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

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

54
@AndyMcKenna - 这是有意为之的,旨在让您更改数组中的值,正如Mark在第二段中所指出的那样。 - Pauk
42
不应该这样使用初始化向量(IV)。对于给定的两个消息,它们不应该使用相同的密钥和相同的IV进行加密。每条消息的IV应该是随机生成的,附加到加密数据流之前,并在解密前读取。参考链接:http://crypto.stackexchange.com/a/82/1934 - jbtule
31
为每个消息使用随机初始化向量不是什么奇特或新颖的方法,而是算法设计中重要的一部分。为每个消息使用可预测的初始化向量是一个常见的加密错误,不需要继续传承下去。 - jbtule
14
请注意,使用CBC模式的一个后果是您很可能会受到填充预言攻击的威胁。请使用经过身份验证的加密,尽可能地不要自己实现密码学 - Stephen Touset
58
安全警告:不要使用此代码。尽管被接受为答案,但上述评论中提到了严重的安全问题,作者已经忽略了8年。 - jbtule
显示剩余31条评论

188

我清理了SimpleAES(如上所述)以供我的使用。修复了混乱的加密/解密方法;将编码字节缓冲区、字符串和URL友好字符串的方法分离出来;并利用现有库进行了URL编码。

代码更小,更简单,更快,并且输出更加简洁。例如,johnsmith@gmail.com 会产生:

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

代码:

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

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

    private ICryptoTransform encryptor, decryptor;
    private UTF8Encoding encoder;

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

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

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

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

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

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

2
在解码时,我不得不将空格替换为 +,才能与 Chrome 中的 QueryString 一起使用:(new SimplerAES ()).Decrypt(Request.QueryString ["myParam"].Replace(' ','+')); - live-love
21
不要使用恒定的初始化向量,详情请参阅http://crypto.stackexchange.com/questions/66/what-are-the-details-of-the-des-weakness-of-reusing-the-same-iv-in-cbc-mode-with/82#82获取更多信息。相反,为每个加密生成一个新的IV,并将其附加到密文中,这样做更好且不太困难。 - Tom Heard
2
请注意,在此解决方案中EncryptToUrl方法的输出(或一般情况下使用UrlEncoded base 64字符串)在作为URL路径(而不是查询字符串)的一部分时,默认情况下在IIS 7下不起作用,例如在ASP.NET MVC路由中,这是由于IIS 7安全设置所致。有关更多信息,请参见:http://stackoverflow.com/a/2014121/12484 - Jon Schneider
5
如何使用上述代码实现这一点? - MKII
27
安全警告:请勿使用此代码。请参考@TomHeard的评论。 - jbtule
显示剩余2条评论

35

是的,添加System.Security程序集,导入System.Security.Cryptography命名空间。这里是一个对称(DES)算法加密的简单示例:

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

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

ICryptoTransform decryptor = des.CreateDecryptor();

// decrypt
byte[] originalAgain = decryptor.TransformFinalBlock(enc, 0, enc.Length);
Debug.Assert(originalAgain[0] == 1);

5
这是一种不错的、紧凑的双向加密方式。唯一需要注意的是,DES已不再被视为最先进的安全加密方法。现在这个头衔属于我在下面讨论的AES算法。 - Mark Brittingham
@richdiet。非常抱歉我取消了您的答案。另一个回答获得了37个以上的投票,因为它更加当前。感谢您的回答,因为它仍然是一个好的答案。 - Matt Dawdy
14
没有块链接功能、初始化向量和适当填充的任何分组密码都是不安全的。在这种方案中使用DES是最不重要的问题。 - Hubert Kario
2
那么这个密钥在哪里使用? - Alex
22
安全警告:请勿使用此代码。请参见@HubertKario的评论。 - jbtule

29

我想补充一下,我通过添加随机IV来改进Mud的SimplerAES,并将IV传回到加密字符串中。这可以改善加密,因为每次加密相同的字符串都会产生不同的输出。

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

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

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

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

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

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

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

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

        return stream.ToArray();
    }
}

以及奖励的单元测试

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

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

    // Assert
    Assert.AreEqual(originalString, decryptedString1, "Decrypted string should match original string");
    Assert.AreEqual(originalString, decryptedString2, "Decrypted string should match original string");
    Assert.AreNotEqual(originalString, encryptedString1, "Encrypted string should not match original string");
    Assert.AreNotEqual(encryptedString1, encryptedString2, "String should never be encrypted the same twice");
}

12
  1. 不要使用System.Random作为随机数生成器。
  2. 这种方法在选择明文攻击(尤其是有关填充的Oracle攻击)方面是完全不安全的。
- CodesInChaos
22
安全警告:不要使用这段代码。请参见上面@CodesInChaos的评论。 - jbtule
3
@jbtule,请不要误导那些不想复杂化的人,只需加密,而且不担心攻击的人。如果您只是想提供建议,请不要下订单。 - Virbhadrasinh
@Virbhadrasinh,我没有误导你,相反,使用 AES 时正确的使用方法非常重要,如果使用不当并说这没关系,因为我并没有用于重要事情上,那是错误的。 - jbtule
3
如果这段代码容易受到攻击——事实就是如此——那么,与其只是吆喝着不要使用它,还不如提供一些正确的代码链接。仅仅是这样说是不够的。 - Corey
2
@Corey 不是在大喊大叫,而是按照堆栈溢出答案中处理安全问题的最佳实践进行操作。如果您需要链接,它已经发布在问题评论中。但我也会在这里为您提供 https://dev59.com/RHVC5IYBdhLWcg3wtzut#10366194 - jbtule

12

这是Marks(优秀)答案的一个变种

  • 添加“using”语句
  • 将类设置为IDisposable
  • 移除URL编码代码以使示例更简单。
  • 添加简单的测试夹具以演示用法

希望这有所帮助

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

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

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

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

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

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

    public void Dispose()
    {
        if (rijndael != null)
        {
            rijndael.Dispose();
        }
    }
}

8
永远不要使用固定的初始化向量。请参考crypto.stackexchange.com/questions/66/… 了解更多信息。相反,为每个加密生成一个新的IV,并将其附加到密文中,这样做更好且不会太难。 - Tom Heard
5
在加密时,你可以使用 Rijndael 类为你生成一个随机的 IV(http://msdn.microsoft.com/en-us/library/system.security.cryptography.symmetricalgorithm.generateiv(v=vs.110).aspx),进行加密操作,然后从 Rijndael 实例中使用 IV 属性获取 IV。接着,将它作为前缀(或后缀,只要解密时从相同的位置获取即可)添加到加密文本中。在解密时,从接收到的数据中提取 IV(IV 属性的大小等于 BlockSize 属性除以 8),然后将其传递给解密实例进行解密。 - Tom Heard
2
@Chalky 注意,IV并不需要保密,只需要对于每个发送的消息是唯一的。 - Tom Heard
20
安全警告:不要使用此代码。请参见@TomHeard上方的评论。 - jbtule
1
@jbtule,问题字面上说的是“简单不安全的”双向数据“混淆”?为什么你还要在每个答案上发表评论“安全警告:不要使用此代码”-_-?有点多余和重复,如果不是直接让人感到烦恼。根据问题的标题,它并不意味着要安全或复杂... - matronator
显示剩余2条评论

11

我结合了多个答案和评论中的最佳内容。

  • 在加密文本前添加随机初始化向量 (@jbtule)
  • 使用TransformFinalBlock()代替MemoryStream (@RenniePet)
  • 不要预填密钥,以避免任何人复制粘贴导致灾难
  • 正确的处理关闭和使用模式

代码:

/// <summary>
/// Simple encryption/decryption using a random initialization vector
/// and prepending it to the crypto text.
/// </summary>
/// <remarks>Based on multiple answers in https://dev59.com/-3VC5IYBdhLWcg3w2k-I </remarks>
public class SimpleAes : IDisposable
{
    /// <summary>
    ///     Initialization vector length in bytes.
    /// </summary>
    private const int IvBytes = 16;

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

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

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

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

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

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

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

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

更新2015-07-18:根据@bpsilver和@Evereq的评论,修复了私有Encrypt()方法中的错误。IV被意外加密,现在作为明文前置,符合Decrypt()的预期。


你应该使用预置的IV加密整个inputBuffer,否则将会丢失要加密的字符串的前16个字符。因此,你的代码应该是这样的:return _encryptor.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length); - bpsilver
IV被建议以明文形式在前面添加,正如@Tom Heard及其他人所推荐的那样。这些是你所提到的16个字节。如果加密IV,则解密将无法提取IV。 - angularsen
2
在这种情况下:byte [] inputBuffer = _encryptor.TransformFinalBlock(buffer, 0, buffer.Length); return _rijndael.IV.Concat(inputBuffer).ToArray(); - bpsilver
1
那将会做与当前实现相同的事情,不是吗? - angularsen
1
“在进行Base64解码之前,请使用16、24或32个字符填充密钥。而且,密钥应该是随机的。真正的随机。” - Maarten Bodewes
1
我注意到@bpsilver是正确的,而且没有他的修复代码将无法正常工作:加密方法返回没有IV的加密数据(它首先将IV添加到输入缓冲区,但接下来加密并返回不带IV的数据)。因此,如果可能,请更新答案以包含他的代码。(注:我仅测试了具有byte[]参数的方法,而不是字符串)。谢谢! - Evereq

9

System.Security.Cryptography 中使用 TripleDESCryptoServiceProvider:

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

2
我喜欢这个解决方案,但是将密钥保留在类中作为属性并不好。因此,我在这里创建了一个gist,供关心此问题的人使用。https://gist.github.com/shukebeta/780df627a3de55c848a800cfb242a276 - shukebeta

8

[编辑] 多年后,我回来说: 不要这样做! 详情请参见什么是XOR加密的问题?

一种非常简单、易于两向加密的方法是XOR加密。

  1. 想一个密码。我们用作为示例。
  2. 按照ASCII码将密码转换成二进制。该密码变成了01101101 01111001 01110000 01100001 01110011 01110011。
  3. 将您想要编码的信息转换成二进制。
  4. 查看消息的长度。如果消息长度为400字节,则通过重复密码来将其变成一个400字节的字符串。它会变成01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011 01101101 01111001 01110000 01100001 01110011 01110011... (或者mypassmypassmypass...)
  5. 使用长密码对消息进行XOR运算。
  6. 发送结果。
  7. 另一次,使用相同的密码(mypassmypassmypass...)对加密消息进行XOR运算。
  8. 这就是您的消息!

10
并不是每种情况都需要使用加密安全性很高的哈希或Rijndael密码。 "简单的双向加密"可能实际上意味着简单,这暗示使用异或或甚至ROT13。 - user1228
1
@Ryan:使用静态加密密钥的AES,没有初始化向量和块链接函数,只是XOR加密的花哨名称,你只是在使用非常花哨的KDF... - Hubert Kario
17
安全警告:不要使用此代码。通过重复密钥的异或加密可以轻松破解。 - jbtule
2
@jbtule 这个问题明确要求使用一种“不安全”的算法。XOR加密由于其简单性而是一个完美的选择。我认为这是一个有效的答案——只需选择足够长且随机的密码即可。 - Bip901

5

我希望分享我的解决方案,因为以上的解决方案都没有我的简单。请告诉我你的想法:

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

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

可选项

假设用于加密值的服务器的 MachineKey 与用于解密值的服务器的 MachineKey 相同。如果需要,您可以在 Web.config 中指定一个静态的 MachineKey,以便您的应用程序可以在任何地方解密/加密数据(例如开发 vs. 生产服务器)。您可以按照这些说明生成静态机器密钥。


请注意,此方法仅适用于ASP.NET应用程序。 - Feru

5

加密很简单:正如其他人指出的那样,System.Security.Cryptography命名空间中有一些类可以为您完成所有工作。使用它们而不是任何自己编写的解决方案。

但是解密也很容易。您面临的问题不是加密算法,而是保护用于解密的密钥的访问。

我会使用以下解决方案之一:

  • DPAPI使用ProtectedData类和CurrentUser范围。这很容易,因为您不需要担心密钥。数据只能由相同的用户解密,因此无法用于在用户或计算机之间共享数据。

  • DPAPI使用ProtectedData类和LocalMachine范围。例如,在单个安全服务器上保护配置数据非常好。但是,任何可以登录到机器的人都可以对其进行加密,因此除非服务器安全,否则无法使用。

  • 任何对称算法。如果我不关心使用什么算法(实际上默认情况下是Rijndael),我通常使用静态SymmetricAlgorithm.Create()方法。在这种情况下,您需要以某种方式保护密钥。例如,您可以以某种方式混淆它并将其隐藏在代码中。但请注意,任何足够聪明以反编译代码的人都可能能够找到密钥。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接