Быстрый урок математики!
Вы думаете о разработке способа сопоставить одно целочисленное значение (исходное «секретное» UserId
значение) другому ((зашифрованное) «publi c» значение) и обратно. Именно это и делает блочный шифр (за исключением того, что каждый «блок» обычно имеет размер 16 байт, а не является одиночным символом или целочисленным значением). Другими словами, вы хотите создать свою собственную криптосистему.
(Обратите внимание, что даже если вы думаете о преобразовании UserId 123 в string
вместо целого числа, например, YouTube Идентификатор видео, например "dQw4w9WgXcQ"
) - это все еще целое число: потому что каждое скалярное значение, хранящееся в компьютере, включая строки, может быть представлено как целое число - отсюда и проблема «недопустимых простых чисел» еще в конце 1990-х ).
И самый большой, самый важный вывод из любого курса информатики на бакалавриате по криптографии - это никогда не создавайте свою собственную криптосистему !.
Без этого ...
При условии, что безопасность не является главной проблемой ...
... и вас беспокоит только с предотвращением раскрытия увеличивающихся значений целочисленных идентификаторов (например, ваши посетители и пользователи не видят, сколько записей в базе данных у вас действительно есть), затем используйте библиотеку Hashids: https://hashids.org/
В своем коде создайте один объект Hashids
(я бы использовал поле или свойство publi c static readonly
, или еще лучше: одноэлементный вводимый сервис) и используйте .Encode
метод преобразования любого целого числа int
/ Int32
в значение string
.
Чтобы преобразовать значение string
обратно в исходное int
/ Int32
, используйте .Decode
method.
Кстати, мне не нравится, как библиотека называется «Hashids», когда хеши предназначены для односторонних функций - потому что значения по-прежнему обратимы - хотя и с использованием секретное значение "соли" (почему оно не называется "ключом"?) на самом деле это не ha sh, imo.
Если безопасность действительно имеет значение ... .
Затем вам нужно рассматривать каждое целочисленное значение как дискретный блок в блочном шифре (а не потоковый шифр, потому что каждое значение должно быть зашифровано и дешифровано независимо само).
В целях практичности необходимо использовать блочный шифр symri c с небольшим размером блока. К сожалению, многие блочные шифры с маленькими размерами блоков не очень хороши (TripleDES имеет размер блока 64 бита, но сегодня он слаб), поэтому давайте придерживаться AES.
AES имеет размер блока 128 бит (16 байт) - это то же самое, что и два Int64
целых числа, соединенных друг с другом. Предполагая, что вы используете кодировку base64url
для 16-байтового значения, ваш вывод будет иметь длину 22 символа (поскольку Base64 использует 6 бит на символ). Если вас устраивают струны такой длины, то все готово. Самая короткая URL-безопасная строка, которую вы можете сгенерировать из 128-битного значения, - 21 (вряд ли улучшение вообще) потому что Base-73 - это максимум, который вы можете безопасно использовать в URL, который переживет все современные URL -системы передачи (никогда автоматически не предполагайте, что Unicode поддерживается где-либо при работе с открытым текстом). использование таких методов, как режим CTR, означает, что сгенерированный вывод должен включать дополнительную информацию о состоянии (IV, счетчик и т. д. c), которая в конечном итоге займет столько же места, сколько было получено .
Вот код:
Очень важные примечания :
private static readonly Byte[] _key = new Byte[] { }. // Must be 128, 192 or 256 bits (16, 24, or 32 bytes) in length.
private static readonly Byte[] _iv = new Byte[8]; // You could use the default all-zeroes.
// Note that this method works with Int32 arguments.
private static Byte[] ProcessBlock( Byte[] inputBlock, Boolean encrypt )
{
Byte[] outputBlock;
using( Aes aes = Aes.Create() )
{
aes.Key = _key;
aes.IV = _iv;
using( ICryptoTransform xform = encrypt ? aes.CreateEncryptor() : aes.CreateDecryptor() )
{
outputBlock = xform.TransformFinalBlock( inputBlock, 0, inputBlock.Length );
}
}
}
public static Byte[] EncryptInteger( Int64 value )
{
Byte[] inputBlock = new Byte[16];
inputBlock[0] = (Byte)(value >> 0 & 0xFF);
inputBlock[1] = (Byte)(value >> 8 & 0xFF);
inputBlock[2] = (Byte)(value >> 16 & 0xFF);
inputBlock[3] = (Byte)(value >> 24 & 0xFF);
inputBlock[4] = (Byte)(value >> 32 & 0xFF);
inputBlock[5] = (Byte)(value >> 40 & 0xFF);
inputBlock[6] = (Byte)(value >> 48 & 0xFF);
inputBlock[7] = (Byte)(value >> 56 & 0xFF);
return ProcessBlock( inputBlock, encrypt: true );
}
public static Int64 DecryptInteger( Byte[] block )
{
Byte[] outputBlock = ProcessInteger( value, encrypt: false );
return
(Int64)outputBlock[0] << 0 |
(Int64)outputBlock[1] << 8 |
(Int64)outputBlock[2] << 16 |
(Int64)outputBlock[3] << 24 |
(Int64)outputBlock[4] << 32 |
(Int64)outputBlock[5] << 40 |
(Int64)outputBlock[6] << 48 |
(Int64)outputBlock[7] << 56;
};
public static String EncryptIntegerToString( Int64 value ) => Convert.ToBase64String( EncryptInteger( value ) ).Replace( '+', '-' ).Replace( '/', '_' );
public static Int64 DecryptIntegerFromString( String base64Url )
{
if( String.IsNullOrWhiteSpace( base64Url ) ) throw new ArgumentException( message: "Invalid string.", paramName: nameof(base64Url) );
// Convert Base64Url to Base64:
String base64 = base64Url.Replace( '-', '+' ).Replace( '_', '/' );
Byte[] block = Convert.FromBase64String( base64 );
return DecryptInteger( block );
}