ASP.NET 中的 AES 与 VB.NET

时间:2023-03-28
本文介绍了ASP.NET 中的 AES 与 VB.NET的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

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

What is a good link or article on encrypting a URL link with AES to pass username to another web site in ASP.NET using VB.NET 2005? FYI: The receiving web site will have access to the private KEY to decrypt.

推荐答案

First

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

请记住:我们已经有足够快但不安全的系统了."(布鲁斯·施奈尔)——做正确的事,以后再担心性能.

And please, remember: "We already have enough fast, insecure systems." (Bruce Schneier) -- Do things correct and worry about performance later.

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

That said, if you are stuck on using AES to roll your own, here are a few pointers.

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

AES is a block cipher. Given a key and a block of plaintext, it converts it to a specific ciphertext. The problem with this is that the same blocks of data will generate the same ciphertext with the same key, every time. So suppose you send data like this:

user=Encrypt(Username)&roles=Encrypt(UserRoles)

user=Encrypt(Username)&roles=Encrypt(UserRoles)

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

They're two separate blocks, and the UserRoles encryption will have the same ciphertext each time, regardless of the name. All I need is the ciphertext for an admin, and I can drop it right in with my cipher'd username. Oops.

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

So, there are cipher operation modes. The main idea is that you'll take the ciphertext of one block, and XOR it into the ciphertext of the next block. That way we'll do Encrypt(UserRoles, Username), and the Username ciphertext is affected by the UserRoles.

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

The problem is that the first block is still vulnerable - just by seeing someone's ciphertext, I might know their roles. Enter the initialization vector. The IV "starts up" the cipher and ensures it has random data to encrypt the rest of the stream. So now the UserRoles ciphertext has the ciphertext of the random IV XOR'd in. Problem solved.

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

So, make sure you generate a random IV for each message. The IV is not sensitive and can be sent plaintext with the ciphertext. Use an IV large enough -- the size of the block should be fine for many cases.

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

AES doesn't provide integrity features. Anyone can modify your ciphertext, and the decrypt will still work. It's unlikely it'll be valid data in general, but it might be hard to know what valid data is. For instance, if you're transmitting a GUID encrypted, it'd be easy to modify some bits and generate a completely different one. That could lead to application errors and so on.

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

The fix there is to run a hash algorithm (use SHA256 or SHA512) on the plaintext, and include that in the data you transmit. So if my message is (UserName, Roles), you'll send (UserName, Roles, Hash(UserName, Roles)). Now if someone tampers with the ciphertext by flipping a bit, the hash will no longer compute and you can reject the message.

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

If you need to generate a key from a password, use the built-in class: System.Security.Cryptography.PasswordDeriveBytes. This provides salting and iterations, which can improve the strength of derived keys and reduce the chance of discovering the password if the key is compromised.

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

Sorry for not mentioning this earlier :P. You also need to make sure you have an anti-replay system. If you simply encrypt the message and pass it around, anyone who gets the message can just resend it. To avoid this, you should add a timestamp to the message. If the timestamp is different by a certain threshold, reject the message. You may also want to include a one-time ID with it (this could be the IV) and reject time-valid messages that come from other IPs using the same ID.

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

It's important to make sure you do the hash verification when you include the timing information. Otherwise, someone could tamper with a bit of the ciphertext and potentially generate a valid timestamp if you don't detect such brute force attempts.

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

Since apparently using an IV correctly is controversial for some folks, here's some code that'll generate random IVs and add them to your output for you. It'll also perform the authentication step, making sure the encrypted data wasn't modified.

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=爱丽丝;鲍勃;Eve;: PerformAct1 爱丽丝;鲍勃;Eve;: PerformAct2 成功:检测到篡改.System.Security.Cryptography.CryptographicException:消息哈希不正确.在AesDemo.Decrypt(字符串密码,字节[]密码盐,字节[]密文)在C:Program.cs:行46 在 AesDemo.Main() 中C:Program.cs:行100

JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz/y0QMz+uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg= QQvDujNJ31qTu/foDFUiVMeWTU0jKL/UJJfFAvmFtz361o3KSUlk/zH+4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE/QTofkUMHoa65/5e4= Alice; Bob; Eve;: PerformAct1 Alice; Bob; Eve;: PerformAct2 Success: tampering detected. System.Security.Cryptography.CryptographicException: Message hash incorrect. at AesDemo.Decrypt(String password, Byte[] passwordSalt, Byte[] cipherText) in C:Program.cs:line 46 at AesDemo.Main() in C:Program.cs:line 100

去掉随机IV和哈希后,输出的类型如下:

After removing the random IV and the hash, here's the type of output:

tZfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY=tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=

tZfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY= tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=

注意第一个块如何对应Alice; Bob; Eve;"是一样的.确实是角落案例".

Notice how the first block, corresponding to "Alice; Bob; Eve;" is the same. "Corner case" indeed.

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

Here's a simple example of passing a 64-bit integer. Just encrypt and you're open to attack. In fact, the attack is easily done, even with CBC padding.

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 值

Value: 6598637501946607785 Value

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

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

加密:

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

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

解密:6598637501946607785

Decrypted: 6598637501946607785

被攻击:14174658352338201502

Attacked: 14174658352338201502

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

So, if that's the kind of ID you're sending, it could quite easily be changed to another value. You need to authenticate outside of your message. Sometimes, the message structure is unlikely to fall into place and can sorta act as a safeguard, but why rely on something that could possibly change? You need to be able to rely on your crypto working correctly regardless of the application.

这篇关于ASP.NET 中的 AES 与 VB.NET的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:如何将公钥存储在机器级 RSA 密钥容器中 下一篇:RijndaelManaged“填充无效,无法移除"仅在生产中解密时发生

相关文章