Шифрование с MachineKey не является постоянным
Я использую MachineKey.Protect()
метод для шифрования идентификатора, переданного в виде строки запроса в моем приложении asp.net MVC.
Вот код, который я использую для шифрования / дешифрования:
public static string Encrypt(this string expression)
{
if (string.IsNullOrEmpty(expression))
return string.Empty;
byte[] stream = Encoding.Unicode.GetBytes(expression);
byte[] encodedValue = MachineKey.Protect(stream);
return HttpServerUtility.UrlTokenEncode(encodedValue);
}
public static string Decrypt(this string expression)
{
if (string.IsNullOrEmpty(expression))
return string.Empty;
byte[] stream = HttpServerUtility.UrlTokenDecode(expression);
byte[] decodedValue = MachineKey.Unprotect(stream);
return Encoding.Unicode.GetString(decodedValue);
}
И вот MachineKey
элемент в моем web.config
файл:
<system.web>
.
.
.
<machineKey validationKey="xxx" decryptionKey="xxx" validation="SHA1" decryption="AES" />
</system.web>
Проблема в том, что зашифрованный идентификатор не является постоянным. Каждый раз, когда я вызываю метод, я получаю новое зашифрованное выражение. Как мне сделать это постоянным?
2 ответа
Резюме:
Если вы хотите получать один и тот же результат каждый раз, вам нужно использовать другой метод для защиты ваших данных. MachineKey.Protect
использует разные IV для каждого запуска, каждый раз получая разные результаты.
подробность
Microsoft делает исходный код для многих платформ dot net свободно доступным для просмотра в Интернете.
Начиная сверху: MachineKey
Метод защиты использует AspNetCryptoServiceProvider
Если вы будете следовать коду через AspNetCryptoServiceProvider.GetCryptoService
в NetFXCryptoService вы найдете это:
public byte[] Protect(byte[] clearData) {
// The entire operation is wrapped in a 'checked' block because any overflows should be treated as failures.
checked {
// These SymmetricAlgorithm instances are single-use; we wrap it in a 'using' block.
using (SymmetricAlgorithm encryptionAlgorithm = _cryptoAlgorithmFactory.GetEncryptionAlgorithm()) {
// Initialize the algorithm with the specified key and an appropriate IV
encryptionAlgorithm.Key = _encryptionKey.GetKeyMaterial();
if (_predictableIV) {
// The caller wanted the output to be predictable (e.g. for caching), so we'll create an
// appropriate IV directly from the input buffer. The IV length is equal to the block size.
encryptionAlgorithm.IV = CryptoUtil.CreatePredictableIV(clearData, encryptionAlgorithm.BlockSize);
}
else {
// If the caller didn't ask for a predictable IV, just let the algorithm itself choose one.
encryptionAlgorithm.GenerateIV();
}
byte[] iv = encryptionAlgorithm.IV;
using (MemoryStream memStream = new MemoryStream()) {
memStream.Write(iv, 0, iv.Length);
// At this point:
// memStream := IV
// Write the encrypted payload to the memory stream.
using (ICryptoTransform encryptor = encryptionAlgorithm.CreateEncryptor()) {
using (CryptoStream cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write)) {
cryptoStream.Write(clearData, 0, clearData.Length);
cryptoStream.FlushFinalBlock();
// At this point:
// memStream := IV || Enc(Kenc, IV, clearData)
// These KeyedHashAlgorithm instances are single-use; we wrap it in a 'using' block.
using (KeyedHashAlgorithm signingAlgorithm = _cryptoAlgorithmFactory.GetValidationAlgorithm()) {
// Initialize the algorithm with the specified key
signingAlgorithm.Key = _validationKey.GetKeyMaterial();
// Compute the signature
byte[] signature = signingAlgorithm.ComputeHash(memStream.GetBuffer(), 0, (int)memStream.Length);
// At this point:
// memStream := IV || Enc(Kenc, IV, clearData)
// signature := Sign(Kval, IV || Enc(Kenc, IV, clearData))
// Append the signature to the encrypted payload
memStream.Write(signature, 0, signature.Length);
// At this point:
// memStream := IV || Enc(Kenc, IV, clearData) || Sign(Kval, IV || Enc(Kenc, IV, clearData))
// Algorithm complete
byte[] protectedData = memStream.ToArray();
return protectedData;
}
}
}
}
}
}
}
Класс был инициализирован с параметрами по умолчанию, поэтому _predictableIV
ложно
Поэтому он использует новый IV каждый раз, что означает, что результат будет отличаться каждый раз, даже с одним и тем же вводом.
IV включен в результат, поэтому Unprotect
Метод может изменить шифрование.
Попробуйте добавить целевой аргумент в метод Machine.Key.Proctect! Вот так.
public static class Key
{
public static string EncryptWithAPurpose(this string expression, string[] purpose)
{
if (string.IsNullOrEmpty(expression))
return string.Empty;
byte[] stream = Encoding.Unicode.GetBytes(expression);
byte[] encodedValue = MachineKey.Protect(stream, purpose);
return HttpServerUtility.UrlTokenEncode(encodedValue);
}
public static string DecryptWithAPurpose(this string expression, string[] purpose)
{
if (string.IsNullOrEmpty(expression))
return string.Empty;
byte[] stream = HttpServerUtility.UrlTokenDecode(expression);
byte[] decodedValue = MachineKey.Unprotect(stream,purpose);
return Encoding.Unicode.GetString(decodedValue);
}
}
Для тестирования:
- Добавить ссылку -> @Html.ActionLink("Получить новый ключ", "GetKey", новый { id = Guid.NewGuid()})
- Добавить метод контроллера
- Добавьте ViewModel
- Добавить вид.
{
public ActionResult GetKey(Guid id)
{
var vm = new vmGetKey();
vm.Guid = id;
//create a purpose
var purpose = new string[] { "Test", "WithAPurpose" };
//encrypt key1
vm.key1 = Key.EncryptWithAPurpose(id.ToString(), purpose);
//encrypt Key2
vm.key2 = Key.EncryptWithAPurpose(id.ToString(), purpose);
//decrypt key1
vm.key1_DecryptResult = Key.DecryptWithAPurpose(vm.key1, purpose);
//decrypt key2
vm.key2_DecryptResult = Key.DecryptWithAPurpose(vm.key2, purpose);
return View(vm);
}
public class vmGetKey
{
public Guid Guid { get; set; }
public string key1 { get; set; }
public string key2 { get; set; }
public string key1_DecryptResult { get; set; }
public string key2_DecryptResult { get; set; }
}
}