我想在ASP.NET中混淆一个查询字符串参数。该网站将有大量请求,因此算法不应该太慢。
我的问题是我发现的所有算法都会产生不需要的字符(比如+ / =)
这是我想要实现的一个例子:
www.domain.com/?id=1844
到
www.domain.com/?id=3GQ5DTL3oVd91WsGj74gcQ
混淆的参数应该只包含 a-z和A-Z以及0-9 字符。
我知道我可以使用base64加密,但这会产生不需要的字符,例如/
或=
或+
。
知道可以使用什么算法吗?
更新 我知道UrlEncoding,我想避免编码字符串。 因为这会在网址中生成%F2或%B2等字符。
答案 0 :(得分:5)
您可以使用HttpServerUtility.UrlTokenEncode和HttpServerUtility.UrlTokenDecode
Encode使用base64编码,但替换URL不友好的字符。
以前的SO question中有类似的答案。请参阅接受的答案。
答案 1 :(得分:5)
您可以使用三重DES来使用narow分组密码对值进行编码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace ConsoleApplication1 {
class Program {
static string ToHex(byte[] value) {
StringBuilder sb = new StringBuilder();
foreach (byte b in value)
sb.AppendFormat("{0:x2}", b);
return sb.ToString();
}
static string Encode(long value, byte[] key) {
byte[] InputBuffer = new byte[8];
byte[] OutputBuffer;
unsafe {
fixed (byte* pInputBuffer = InputBuffer) {
((long*)pInputBuffer)[0] = value;
}
}
TripleDESCryptoServiceProvider TDes = new TripleDESCryptoServiceProvider();
TDes.Mode = CipherMode.ECB;
TDes.Padding = PaddingMode.None;
TDes.Key = key;
using (ICryptoTransform Encryptor = TDes.CreateEncryptor()) {
OutputBuffer = Encryptor.TransformFinalBlock(InputBuffer, 0, 8);
}
TDes.Clear();
return ToHex(OutputBuffer);
}
static long Decode(string value, byte[] key) {
byte[] InputBuffer = new byte[8];
byte[] OutputBuffer;
for (int i = 0; i < 8; i++) {
InputBuffer[i] = Convert.ToByte(value.Substring(i * 2, 2), 16);
}
TripleDESCryptoServiceProvider TDes = new TripleDESCryptoServiceProvider();
TDes.Mode = CipherMode.ECB;
TDes.Padding = PaddingMode.None;
TDes.Key = key;
using (ICryptoTransform Decryptor = TDes.CreateDecryptor()) {
OutputBuffer = Decryptor.TransformFinalBlock(InputBuffer, 0, 8);
}
TDes.Clear();
unsafe {
fixed (byte* pOutputBuffer = OutputBuffer) {
return ((long*)pOutputBuffer)[0];
}
}
}
static void Main(string[] args) {
long NumberToEncode = (new Random()).Next();
Console.WriteLine("Number to encode = {0}.", NumberToEncode);
byte[] Key = new byte[24];
(new RNGCryptoServiceProvider()).GetBytes(Key);
Console.WriteLine("Key to encode with is {0}.", ToHex(Key));
string EncodedValue = Encode(NumberToEncode, Key);
Console.WriteLine("The encoded value is {0}.", EncodedValue);
long DecodedValue = Decode(EncodedValue, Key);
Console.WriteLine("The decoded result is {0}.", DecodedValue);
}
}
}
输出应该是这样的:
Number to encode = 873435734.
Key to encode with is 38137b6a7aa49cc6040c4297064fdb4461c79a895f40b4d1.
The encoded value is 43ba3fb809a47b2f.
The decoded result is 873435734.
请注意,编码值只有16个字符宽。
如果您真的对滥用行为有所了解,那么AES可以以类似的方式使用。在下一个例子中,我切换AES并将64位id号写入块的两侧。如果它没有在两侧以相同的值进行解码,那么它将被拒绝。这可以防止人们以随机数写入。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace ConsoleApplication1 {
class Program {
static string ToHex(byte[] value) {
StringBuilder sb = new StringBuilder();
foreach (byte b in value)
sb.AppendFormat("{0:x2}", b);
return sb.ToString();
}
static string Encode(long value, byte[] key) {
byte[] InputBuffer = new byte[16];
byte[] OutputBuffer;
unsafe {
fixed (byte* pInputBuffer = InputBuffer) {
((long*)pInputBuffer)[0] = value;
((long*)pInputBuffer)[1] = value;
}
}
AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
Aes.Mode = CipherMode.ECB;
Aes.Padding = PaddingMode.None;
Aes.Key = key;
using (ICryptoTransform Encryptor = Aes.CreateEncryptor()) {
OutputBuffer = Encryptor.TransformFinalBlock(InputBuffer, 0, 16);
}
Aes.Clear();
return ToHex(OutputBuffer);
}
static bool TryDecode(string value, byte[] key, out long result) {
byte[] InputBuffer = new byte[16];
byte[] OutputBuffer;
for (int i = 0; i < 16; i++) {
InputBuffer[i] = Convert.ToByte(value.Substring(i * 2, 2), 16);
}
AesCryptoServiceProvider Aes = new AesCryptoServiceProvider();
Aes.Mode = CipherMode.ECB;
Aes.Padding = PaddingMode.None;
Aes.Key = key;
using (ICryptoTransform Decryptor = Aes.CreateDecryptor()) {
OutputBuffer = Decryptor.TransformFinalBlock(InputBuffer, 0, 16);
}
Aes.Clear();
unsafe {
fixed (byte* pOutputBuffer = OutputBuffer) {
//return ((long*)pOutputBuffer)[0];
if (((long*)pOutputBuffer)[0] == ((long*)pOutputBuffer)[1]) {
result = ((long*)pOutputBuffer)[0];
return true;
}
else {
result = 0;
return false;
}
}
}
}
static void Main(string[] args) {
long NumberToEncode = (new Random()).Next();
Console.WriteLine("Number to encode = {0}.", NumberToEncode);
byte[] Key = new byte[24];
(new RNGCryptoServiceProvider()).GetBytes(Key);
Console.WriteLine("Key to encode with is {0}.", ToHex(Key));
string EncodedValue = Encode(NumberToEncode, Key);
Console.WriteLine("The encoded value is {0}.", EncodedValue);
long DecodedValue;
bool Success = TryDecode(EncodedValue, Key, out DecodedValue);
if (Success) {
Console.WriteLine("Successfully decoded the encoded value.");
Console.WriteLine("The decoded result is {0}.", DecodedValue);
}
else
Console.WriteLine("Failed to decode encoded value. Invalid result.");
}
}
}
现在看起来应该是这样的:
Number to encode = 1795789891.
Key to encode with is 6c90323644c841a00d40d4407e23dbb2ab56530e1a4bae43.
The encoded value is 731fceec2af2fcc2790883f2b79e9a01.
Successfully decoded the encoded value.
The decoded result is 1795789891.
另请注意,由于我们现在使用了更宽的分组密码,因此编码值现在为32个字符宽。
答案 2 :(得分:4)
所以这是一个工作示例,我将几个不同的示例放在一起,这些示例采用整数ID并将其转换为十六进制格式的加密字符串。此加密字符串不应包含对URL不友好的字符,也不包括转义字符。
这是整个工作控制台应用程序。请注意,这是一个原型,绝对不适合生产 - 这只是说明了一个解决方案,绝对需要重构。
运行代码时,输出应为:
1234 get encrypted as ZaB5GE/bWMJcNaeY/xJ6PQ==
ZaB5GE/bWMJcNaeY/xJ6PQ== encrypted is this in hex 5a61423547452f62574d4a634e6165592f784a3650513d3d
5a61423547452f62574d4a634e6165592f784a3650513d3d gets dehexed as ZaB5GE/bWMJcNaeY/xJ6PQ==
ZaB5GE/bWMJcNaeY/xJ6PQ== got decrypted as 1234
<强>来源:强>
在SO上的字节到十六进制文章:Encryption to alphanumeric in System.Security.Cryptography
加密助手班:Encrypt and decrypt a string(第四回答)
<强> Program2.cs 强>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace ConsoleApplication1
{
class Program2
{
static void Main(string[] args)
{
int theId = 1234; //the ID that's being manipulated
byte[] byteArray; //the byte array that stores
//convert the ID to an encrypted string using a Crypto helper class
string encryptedString = Crypto.EncryptStringAES(theId.ToString(), "mysecret");
Console.WriteLine("{0} get encrypted as {1}", theId.ToString(), encryptedString);
//convert the encrypted string to byte array
byteArray = ASCIIEncoding.Default.GetBytes(encryptedString);
StringBuilder result = new StringBuilder();
//convert each byte to hex and append to a stringbuilder
foreach (byte outputByte in byteArray)
{
result.Append(outputByte.ToString("x2"));
}
Console.WriteLine("{0} encrypted is this in hex {1}", encryptedString, result.ToString());
//now reverse the process, and start with converting each char in string to byte
int stringLength = result.Length;
byte[] bytes = new byte[stringLength / 2];
for (int i = 0; i < stringLength; i += 2)
{
bytes[i / 2] = System.Convert.ToByte(result.ToString().Substring(i, 2), 16);
}
//convert the byte array to de-"hexed" string
string dehexedString = ASCIIEncoding.Default.GetString(bytes);
Console.WriteLine("{0} gets dehexed as {1}", result, dehexedString);
//decrypt the de-"hexed" string using Crypto helper class
string decryptedString = Crypto.DecryptStringAES(dehexedString, "mysecret");
Console.WriteLine("{0} got decrypted as {1}", dehexedString, decryptedString);
Console.ReadLine();
}
}
public class Crypto
{
private static byte[] _salt = Encoding.ASCII.GetBytes("o6806642kbM7c5");
/// <summary>
/// Encrypt the given string using AES. The string can be decrypted using
/// DecryptStringAES(). The sharedSecret parameters must match.
/// </summary>
/// <param name="plainText">The text to encrypt.</param>
/// <param name="sharedSecret">A password used to generate a key for encryption.</param>
public static string EncryptStringAES(string plainText, string sharedSecret)
{
if (string.IsNullOrEmpty(plainText))
throw new ArgumentNullException("plainText");
if (string.IsNullOrEmpty(sharedSecret))
throw new ArgumentNullException("sharedSecret");
string outStr = null; // Encrypted string to return
RijndaelManaged aesAlg = null; // RijndaelManaged object used to encrypt the data.
try
{
// generate the key from the shared secret and the salt
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt);
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);
// 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);
}
}
outStr = Convert.ToBase64String(msEncrypt.ToArray());
}
}
finally
{
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
// Return the encrypted bytes from the memory stream.
return outStr;
}
/// <summary>
/// Decrypt the given string. Assumes the string was encrypted using
/// EncryptStringAES(), using an identical sharedSecret.
/// </summary>
/// <param name="cipherText">The text to decrypt.</param>
/// <param name="sharedSecret">A password used to generate a key for decryption.</param>
public static string DecryptStringAES(string cipherText, string sharedSecret)
{
if (string.IsNullOrEmpty(cipherText))
throw new ArgumentNullException("cipherText");
if (string.IsNullOrEmpty(sharedSecret))
throw new ArgumentNullException("sharedSecret");
// Declare the RijndaelManaged object
// used to decrypt the data.
RijndaelManaged aesAlg = null;
// Declare the string used to hold
// the decrypted text.
string plaintext = null;
try
{
// generate the key from the shared secret and the salt
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt);
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged();
aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
byte[] bytes = Convert.FromBase64String(cipherText);
using (MemoryStream msDecrypt = new MemoryStream(bytes))
{
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();
}
}
}
finally
{
// Clear the RijndaelManaged object.
if (aesAlg != null)
aesAlg.Clear();
}
return plaintext;
}
}
}
答案 3 :(得分:3)
混淆id的问题是,你需要一种去混淆的方法。这需要:
或者,保持身份清楚,但也要使用支票。
public static String ChkSumStr(int id, int reduce)
{
return string.Concat(ReduceStrength(ChkSum(id), reduce).Select(b => b.ToString("X2")).ToArray());
}
public static byte[] ChkSum(int id)
{
byte[] idBytes = Encoding.UTF8.GetBytes("This is an arbitrary salt" + id);
return SHA256.Create().ComputeHash(idBytes);
}
private static byte[] ReduceStrength(byte[] src, int reduce)
{
byte[] ret = null;
for(int i = 0; i != reduce; ++i)
{
ret = new byte[src.Length / 2];
for(int j = 0; j != ret.Length; ++j)
{
ret[j] = (byte)(src[j * 2] ^ src[j * 2 + 1]);
}
src = ret;
}
return src;
}
为reduce指定的值越高,结果越小(直到6处它继续产生空字符串)。较低的值(或0)可以提供更好的安全性,代价是更长的URI。
字符串"This is an arbitrary salt"
需要保密才能获得最佳安全性。它可以在某些用途中进行硬编码,但希望从其他人的安全源获得。
如上所述,id
为15,reduce
为3,结果为05469B1E。然后我们可以将其用作:
www.domain.com/?id=15&chk=05469B1E
在查找15是什么的处理程序中,我们再次做同样的事情,如果结果与05469B1E不同,我们可以返回403 Forbidden或可以说更合理的404 Not Found(基于我们'我收到了一个整体上没有识别任何内容的URI。
答案 4 :(得分:1)
答案 5 :(得分:0)
进行加密,然后使用HttpServerUtility.UrlTokenEncode()对字节数组进行编码。