关于加密:使用VB.NET的ASP.NET中的AES

AES in ASP.NET with VB.NET

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


弗斯特

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

请记住:"我们已经有了足够快的、不安全的系统。"(布鲁斯施耐尔)——做正确的事情,稍后再担心性能。好的。

也就是说,如果您坚持使用AES来滚动自己的代码,这里有一些提示。好的。初始化向量

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

user=encrypt(用户名)&roles=encrypt(用户角色)好的。

它们是两个独立的块,用户角色加密每次都将具有相同的密文,而不管名称如何。我只需要一个管理员的密文,我可以用我的密码用户名直接输入。哎呀。好的。

所以,有密码操作模式。主要思想是将一个块的密文取出来,并将其XOR到下一个块的密文中。这样我们就可以进行加密(userroles,username),username密文受userroles的影响。好的。

问题是,第一个块仍然是脆弱的-只要看到某人的密文,我就可能知道他们的角色。输入初始化向量。IV"启动"密码并确保它有随机数据来加密流的其余部分。所以现在用户角色ciphertext有了随机iv-xor'd的ciphertext。问题解决了。好的。

所以,确保为每条消息生成一个随机的IV。IV不敏感,可以用明文和密文发送。使用一个足够大的静脉注射——对于许多情况来说,静脉注射块的大小应该是合适的。好的。完整性

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

修复方法是在纯文本上运行哈希算法(使用sha256或sha512),并将其包含在您传输的数据中。因此,如果我的消息是(用户名、角色),您将发送(用户名、角色、哈希(用户名、角色))。现在,如果有人通过翻转一点来篡改密文,散列将不再计算,您可以拒绝该消息。好的。导出密钥

如果需要从密码生成密钥,请使用内置类:System.Security.Cryptography.PasswordDeriveBytes。这提供了跳跃和迭代,可以提高派生密钥的强度,并减少在密钥被破坏时发现密码的机会。好的。定时/重放

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

在包含时间信息时,确保进行哈希验证是很重要的。否则,如果您没有检测到这种蛮力尝试,可能有人会篡改一点密文,并可能生成有效的时间戳。好的。样例代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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; 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

Ok.

删除随机IV和散列之后,输出类型如下:好的。

tZfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY=
tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=

Ok.

注意第一个块,对应于"alice;bob;eve;"的方式是相同的。"确实如此。好的。不带哈希的示例

下面是传递64位整数的简单示例。只需加密就可以攻击。事实上,即使使用CBC填充,攻击也很容易完成。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 { }
        }
    }
}

输出:好的。< Buff行情>

值: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类型,那么很容易将其更改为另一个值。您需要在消息之外进行身份验证。有时,消息结构不太可能落实到位,Sorta可以起到保护作用,但为什么依赖可能发生变化的东西呢?无论应用程序如何,您都需要能够正确地依赖您的加密操作。好的。好啊。


我写了一篇博客文章,其中有一个示例项目,您可以在这里下载(C尽管):http://www.codesterr.com/blog/read/aesfileencryptorwithrsaencryptedkeys.aspx

代码基本上使用aes加密二进制数据,然后rsa使用x509certificate加密密钥和iv。因此,只要私钥证书可用,就可以对密钥和IV进行解密,然后依次对AES加密的数据进行解密。

您可以设置证书存储,以便"加密程序"只能访问公钥证书,而"解密程序"可以访问私钥。

这允许您每次使用不同的密钥和IV进行加密,并避免硬编码任何内容。我认为这更安全。您的源代码中不应该有任何东西可以轻易地让别人解密您的数据——如果您的系统曾经受到破坏,您只需要换出新的证书。无需使用新的硬编码值重新编译应用程序。:)

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


下面您将找到一个类,它提供AES加密/解密方法,这些方法显式地提供URL友好的字符串,以便在类似您的应用程序中使用。它还具有处理字节数组的方法。

注意:您应该在键和向量数组中使用不同的值!您不希望有人通过假设您按原样使用此代码来计算您的密钥!您所要做的就是更改键和向量数组中的一些数字(必须小于等于255)。

使用它很简单:只需实例化类,然后(通常)调用encryptToString(StringStringToEncrypt)和decryptString(StringStringToDecrypt)作为方法。一旦你有了这个课程,它就变得更容易(或更安全)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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;
    }
}


Markt指出Rijndael使用了AES加密算法。由于托管实现随.NET框架一起提供(并且自至少1.1以来具有),因此使用它应该满足OP。

api文档有一个非常简单的示例,将rijndael用作加密和解密流。

如果你有办法把共享的秘密(例如,私钥)拿到另一个网站上,那么你就可以通过使用简单的旧对称加密(没有公钥,双方都知道IV和私钥)逃走。如果你的大脑是共享密钥的"不安全通道"(例如,你管理两个网站),这种情况尤其明显。:)

Have a look at"Keep Your Data Secure
with the New Advanced Encryption
Standard". An AES implementation
doesn't ship with the .NET framework
but it links to a custom
implementation (AES.exe).

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