4

关于使用 AES 加密 URL 链接以使用 VB.NET 2005 将用户名传递到 ASP.NET 中的另一个网站的好链接或文章是什么?仅供参考:接收网站将有权访问私钥进行解密。

4

4 回答 4

17

第一的

不要这样做!编写自己的加密系统很容易导致出错。最好使用现有系统,或者如果没有,请让知道密码学的人来做。如果您必须自己做,请阅读实用密码学

请记住:“我们已经拥有足够快但不安全的系统。” (Bruce Schneier) -- 做正确的事,以后再担心性能。

也就是说,如果您坚持使用 AES 来推出自己的产品,这里有一些建议。

初始化向量

AES 是一种分组密码。给定一个密钥和一个明文块,它将其转换为特定的密文。这样做的问题是,相同的数据块每次都会使用相同的密钥生成相同的密文。所以假设你发送这样的数据:

用户=加密(用户名)&角色=加密(用户角色)

它们是两个独立的块,无论名称如何,UserRoles 加密每次都将具有相同的密文。我所需要的只是管理员的密文,我可以用我的密码用户名将其放入。哎呀。

所以,有密码操作模式。主要思想是您将获取一个块的密文,并将其异或到下一个块的密文中。这样我们就可以进行 Encrypt(UserRoles, Username),而用户名密文会受到 UserRoles 的影响。

问题是第一个块仍然容易受到攻击——只要看到某人的密文,我就可能知道他们的角色。输入初始化向量。IV“启动”密码并确保它具有随机数据来加密流的其余部分。因此,现在 UserRoles 密文具有随机 IV XOR 的密文。问题解决了。

因此,请确保为每条消息生成一个随机 IV。IV 不敏感,可以与密文一起发送明文。使用足够大的 IV - 对于许多情况,块的大小应该没问题。

正直

AES 不提供完整性功能。任何人都可以修改您的密文,并且解密仍然有效。一般来说,它不太可能是有效数据,但可能很难知道什么是有效数据。例如,如果您要传输加密的 GUID,则很容易修改一些位并生成一个完全不同的位。这可能会导致应用程序错误等等。

修复方法是在明文上运行哈希算法(使用 SHA256 或 SHA512),并将其包含在您传输的数据中。因此,如果我的消息是 (UserName, Roles),您将发送 (UserName, Roles, Hash(UserName, Roles))。现在,如果有人通过翻转来篡改密文,哈希将不再计算,您可以拒绝该消息。

密钥派生

如果您需要从密码生成密钥,请使用内置类:System.Security.Cryptography.PasswordDeriveBytes。这提供了加盐和迭代,可以提高派生密钥的强度,并减少密钥泄露时发现密码的机会。

计时/重播

编辑:很抱歉之前没有提到这一点:P。你还需要确保你有一个反重放系统。如果您只是加密消息并传递它,那么任何收到消息的人都可以重新发送它。为避免这种情况,您应该在消息中添加时间戳。如果时间戳与某个阈值不同,则拒绝该消息。您可能还想在其中包含一个一次性 ID(这可能是 IV)并拒绝来自使用相同 ID 的其他 IP 的时间有效消息。

在包含计时信息时,确保进行哈希验证很重要。否则,如果您未检测到此类蛮力尝试,则有人可能会篡改一些密文并可能生成有效的时间戳。

示例代码

由于显然正确使用 IV 对某些人来说是有争议的,这里有一些代码可以生成随机 IV 并将它们添加到您的输出中。它还将执行身份验证步骤,确保未修改加密数据。

using System;
using System.Security.Cryptography;
using System.Text;

class AesDemo {

    const int HASH_SIZE = 32; //SHA256

    /// <summary>Performs encryption with random IV (prepended to output), and includes hash of plaintext for verification.</summary>
    public static byte[] Encrypt(string password, byte[] passwordSalt, byte[] plainText) {
        // Construct message with hash
        var msg = new byte[HASH_SIZE + plainText.Length];
        var hash = computeHash(plainText, 0, plainText.Length);
        Buffer.BlockCopy(hash, 0, msg, 0, HASH_SIZE);
        Buffer.BlockCopy(plainText, 0, msg, HASH_SIZE, plainText.Length);

        // Encrypt
        using (var aes = createAes(password, passwordSalt)) {
            aes.GenerateIV();
            using (var enc = aes.CreateEncryptor()) {

                var encBytes = enc.TransformFinalBlock(msg, 0, msg.Length);
                // Prepend IV to result
                var res = new byte[aes.IV.Length + encBytes.Length];
                Buffer.BlockCopy(aes.IV, 0, res, 0, aes.IV.Length);
                Buffer.BlockCopy(encBytes, 0, res, aes.IV.Length, encBytes.Length);
                return res;
            }
        }
    }

    public static byte[] Decrypt(string password, byte[] passwordSalt, byte[] cipherText) {
        using (var aes = createAes(password, passwordSalt)) {
            var iv = new byte[aes.IV.Length];
            Buffer.BlockCopy(cipherText, 0, iv, 0, iv.Length);
            aes.IV = iv; // Probably could copy right to the byte array, but that's not guaranteed

            using (var dec = aes.CreateDecryptor()) {
                var decBytes = dec.TransformFinalBlock(cipherText, iv.Length, cipherText.Length - iv.Length);

                // Verify hash
                var hash = computeHash(decBytes, HASH_SIZE, decBytes.Length - HASH_SIZE);
                var existingHash = new byte[HASH_SIZE];
                Buffer.BlockCopy(decBytes, 0, existingHash, 0, HASH_SIZE);
                if (!compareBytes(existingHash, hash)){
                    throw new CryptographicException("Message hash incorrect.");
                }

                // Hash is valid, we're done
                var res = new byte[decBytes.Length - HASH_SIZE];
                Buffer.BlockCopy(decBytes, HASH_SIZE, res, 0, res.Length);
                return res;
            }
        }
    }

    static bool compareBytes(byte[] a1, byte[] a2) {
        if (a1.Length != a2.Length) return false;
        for (int i = 0; i < a1.Length; i++) {
            if (a1[i] != a2[i]) return false;
        }
        return true;
    }

    static Aes createAes(string password, byte[] salt) {
        // Salt may not be needed if password is safe
        if (password.Length < 8) throw new ArgumentException("Password must be at least 8 characters.", "password");
        if (salt.Length < 8) throw new ArgumentException("Salt must be at least 8 bytes.", "salt");
        var pdb = new PasswordDeriveBytes(password, salt, "SHA512", 129);
        var key = pdb.GetBytes(16);

        var aes = Aes.Create();
        aes.Mode = CipherMode.CBC;
        aes.Key = pdb.GetBytes(aes.KeySize / 8);
        return aes;
    }

    static byte[] computeHash(byte[] data, int offset, int count) {
        using (var sha = SHA256.Create()) {
            return sha.ComputeHash(data, offset, count);
        }
    }

    public static void Main() {
        var password = "1234567890!";
        var salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
        var ct1 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct1"));
        Console.WriteLine(Convert.ToBase64String(ct1));
        var ct2 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct2"));
        Console.WriteLine(Convert.ToBase64String(ct2));

        var pt1 = Decrypt(password, salt, ct1);
        Console.WriteLine(Encoding.UTF8.GetString(pt1));
        var pt2 = Decrypt(password, salt, ct2);
        Console.WriteLine(Encoding.UTF8.GetString(pt2));

        // Now check tampering
        try {
            ct1[30]++;
            Decrypt(password, salt, ct1);
            Console.WriteLine("Error: tamper detection failed.");
        } catch (Exception ex) {
            Console.WriteLine("Success: tampering detected.");
            Console.WriteLine(ex.ToString());
        }
    }
}

输出:

JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz/y0QMz+uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg= QQvDujNJ31qTu/foDFUiVMeWTU0jKL/UJJfFAvmFtz361o3KSUlk/zH+4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE/QTofkUMHoa65/5e4= Alice; 鲍勃;Eve;: PerformAct1 爱丽丝; 鲍勃;Eve;: PerformAct2 成功:检测到篡改。System.Security.Cryptography.CryptographicException:消息哈希不正确。在 AesDemo.Decrypt(String password, Byte[] passwordSalt, Byte[] cipherText) 在 C:\Program.cs:line 46 在 AesDemo.Main() 在 C:\Program.cs:line 100

删除随机 IV 和哈希后,输出类型如下:

tZfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY= tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=

注意第一个块如何对应于“Alice; Bob; Eve;” 是一样的。确实是“角落案例”。

没有散​​列的例子

这是一个传递 64 位整数的简单示例。只需加密,您就会受到攻击。事实上,攻击很容易完成,即使使用 CBC 填充。

public static void Main() {
    var buff = new byte[8];
    new Random().NextBytes(buff);
    var v = BitConverter.ToUInt64(buff, 0);
    Console.WriteLine("Value: " + v.ToString());
    Console.WriteLine("Value (bytes): " + BitConverter.ToString(BitConverter.GetBytes(v)));
    var aes = Aes.Create();
    aes.GenerateIV();
    aes.GenerateKey();
    var encBytes = aes.CreateEncryptor().TransformFinalBlock(BitConverter.GetBytes(v), 0, 8);
    Console.WriteLine("Encrypted: " + BitConverter.ToString(encBytes));
    var dec = aes.CreateDecryptor();
    Console.WriteLine("Decrypted: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
    for (int i = 0; i < 8; i++) {
        for (int x = 0; x < 250; x++) {
            encBytes[i]++;
            try {
                Console.WriteLine("Attacked: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
                return;
            } catch { }
        }
    }
}

输出:

价值:6598637501946607785 价值

(字节):A9-38-19-D1-D8-11-93-5B

加密:

31-59-B0-25-FD-C5-13-D7-81-D8-F5-8A-33-2A-57-DD

解密:6598637501946607785

被攻击:14174658352338201502

因此,如果这是您发送的那种 ID,它可以很容易地更改为另一个值。您需要在消息之外进行身份验证。有时,消息结构不太可能落实到位,并且可以起到保护作用,但为什么要依赖可能改变的东西呢?无论应用程序如何,您都需要能够依赖您的加密货币正常工作。

于 2009-03-20T22:40:52.563 回答
2

我写了一篇博客文章,其中有一个示例项目,您可以在此处下载(虽然是 C#): http: //www.codestrider.com/blog/read/AESFileEncryptorWithRSAEncryptedKeys.aspx

该代码基本上使用 AES 加密二进制数据,然后 RSA 使用 X509Certificate 加密密钥和 IV。所以,只要有私钥证书,就可以解密Key和IV,进而解密AES加密的数据..

您可以设置您的证书存储,以便“加密器”只能访问公钥证书,而“解密器”可以访问私钥。

这使您可以每次使用不同的密钥和 IV 进行加密,并避免对任何内容进行硬编码。我认为这更安全。您的源代码中不应该有任何内容可以轻易地让某人解密您的数据 - 如果您的系统曾经受到威胁,您只需将证书换成新证书即可。无需使用新的硬编码值重新编译应用程序.. :)

示例代码可能与您的预期用途略有不同,但我认为该技术和一些代码可能对您有用。

于 2009-03-22T21:03:51.263 回答
1

Below you'll find a class that provides AES Encryption/Decryption methods that explicitly provide URL-friendly strings for use in applications like yours. It also has the methods that work with byte arrays.

NOTE: you should use different values in the Key and Vector arrays! You wouldn't want someone to figure out your keys by just assuming that you used this code as-is! All you have to do is change some of the numbers (must be <= 255) in the Key and Vector arrays.

Using it is easy: just instantiate the class and then call (usually) EncryptToString(string StringToEncrypt) and DecryptString(string StringToDecrypt) as methods. It couldn't be any easier (or more secure) once you have this class in place.


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


public class SimpleAES
{
    // Change these keys
    private byte[] Key = { 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 };
    private byte[] Vector = { 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;
    }
}

于 2009-03-20T21:08:27.900 回答
-1

Markt指出,Rijndael 使用 AES 加密算法。由于托管实现随 .net 框架一起提供(并且至少从 1.1 开始),因此使用它应该满足 OP。

API 文档有一个使用 Rijndael 作为加密和解密流的非常简单的示例。

如果您有办法将共享秘密(例如,私钥)获取到另一个网站,那么您可能可以使用普通的旧对称加密(没有公钥,双方都知道 IV 和私钥) )。如果您的大脑是共享密钥的“不安全通道”(例如,您管理两个网站),情况尤其如此。:)

查看“使用新的高级加密标准确保您的数据安全”。AES 实现不随 .NET 框架提供,但它链接到自定义实现 (AES.exe)。

1:http: //msdn.microsoft.com/en-us/magazine/cc164055.aspx

于 2009-03-20T20:47:32.740 回答