Не такий вже ти і страшний, XTS-AES
Вітаю,% username%!
Сьогоднішня стаття навіяна думками написати безкоштовний аналог програми для шифрування файлів в DropBox, а саме аспектом режиму шифрування файлів посекторно (для можливості читати\писати з/в довільне місце)
Ми поговоримо про режим шифрування XTS-AES, що застосовується у всіх популярних дискошифровалках (TrueCrypt, DiskCryptor).
Він описаний в IEEE P1619 ./D16 (Standard for Cryptographic Protection of Data on Block-Oriented Storage Devices) і вважається найбезпечнішим способом зберігати дані посекторно.
Першо-напершо визначимо вхідні дані для роботи:
- 256/512 біт ключа (може бути SHA-256/512 (сіль + пароль) або що-небудь на зразок KDF)
- Адреса (номер) сектору
- Собсно блок даних довжини кратної 128 битам (розмір блоку AES)
Спрощено, алгоритм наступний:
- Розбивка ключа на два. Перша частина стає ключем шифрування даних (k1), друга - ключем для генерації tweak value (k2)
Таким чином, якщо у нас використовується ключ в 512 біт, то ми його пиляємо на 2х256 і використовуємо в AES-256
- Конвертуємо номер сектору в масив байт і шифруємо його ключем k2. Це наше tweak value
- Йдемо по масиву даних блоками розміром по 16 байт і для кожного блоку:
- ксоримо його з tweak value
- шифруємо/розшифровуємо його ключем k1
- Знову ксорим вже (рас) шифрований блок даних з tweak value. Зберігаємо його, це і буде потрібний нам (за/рас) шифрований блок сектора
- Множимо tweak value на поліном ^ = x128 + x7 + x2 + x + 1
Найбільш незрозуміле тут (для мене, зокрема) це множення на поліном. Алгоритмічно це просто зсув всього масиву на 1 біт + xor на останньому кроці.
- private const int GF_128_FDBK = 0x87;
- private const int AES_BLK_BYTES = 16;
- ...
- //множимо T (weak value)
- Cin = 0 ;//біт перенесення
- for (j = 0; j < AES_BLK_BYTES; j++)
- {
- Cout = (T[j] >> 7) & 1;
- T[j] = (byte)(((T[j] << 1) + Cin) & 0xFF);
- Cin = Cout;
- }
- if (Cout != 0)
- {
- T[0] ^= GF_128_FDBK;
- }
В принципі, ніякого криміналу. Робиться це для того, щоб tweak value був різним для кожного блоку даних всередині сектора.
Питання до математично підкованої аудиторії: Чому вибрано саме множення на поліном у GF (2) за модулем x128 + x7 + x2 + x + 1? Моїх (не) знань вистачає лише припустити, що тут замішані циклічні групи, і все це певний аналог циклічного зрушення.
Робочий код на C #, який навіть проходить стандартні тести:
- class XTS
- {
- private const int GF_128_FDBK = 0x87;
- private const int AES_BLK_BYTES = 16;
- public static byte[] encryptSector(byte[] inData, byte[] dataEncryptionKey, byte[] tweakEncryptionKey, UInt64 sectorNumber, bool encrypt)
- {
- byte [] outData = new byte [inData.Length] ;//тут буде результат. Розмір inData має бути коротким 32!
- uint i, j; // local counters
- var T = new byte[AES_BLK_BYTES]; // tweak value
- var x = new byte [AES _ BLK _ BYTES] ;//буфер для шифрованого блоку даних
- //конвертуємо номер сектора в масив байт
- Copy(BitConverter.GetBytes(sectorNumber), T, 8);
- //після шифрування в T у нас tweak value. true означає шифрувати
- processAES(tweakEncryptionKey, T, true);
- //Обробляємо по AES_BLK_BYTES байт за раз
- for (i = 0; i < inData.Length; i += AES_BLK_BYTES)
- {
- //ксорим tweak value з шматком даних
- for (j = 0; j < AES_BLK_BYTES; j++)
- {
- x[j] = (byte)(inData[i + j] ^ T[j]);
- }
- //шифруємо/розшифровуємо блок
- processAES(dataEncryptionKey, x, encrypt);
- //ксорим tweak value з обробленим блоком даних
- for (j = 0; j < AES_BLK_BYTES; j++)
- {
- outData[i + j] = (byte)(x[j] ^ T[j]);
- }
- //Множимо tweak value на
- j = AES_BLK_BYTES;
- int t = T[AES_BLK_BYTES - 1];
- while (--j != 0)
- T[j] = (byte)((T[j] << 1) | ((T[j - 1] & 0x80) != 0 ? 1 : 0));
- T[0] = (byte)((T[0] << 1) ^ ((t & 0x80) != 0 ? 0x87 : 0x00));
- }
- return outData;
- }
- private static void processAES(byte[] k, byte[] T, bool encrypt)
- {
- / * AesFastEngine взято з BouncyCastle. Ви можете замінити на стандартну
- * реалізацію, або взагалі використовувати інший алгоритм, тільки враховуйте
- * Розмір блоку шифрування.
- */
- var engine = new AesFastEngine();
- Init(encrypt, new KeyParameter(k));
- ProcessBlock(T, 0, T, 0);
- }
- }
______________________
Використовувати AES, як видно, не обов'язково. Правда, про це нічого не сказано в стандарті.
Домашнім завданням буде реалізувати обробку сектора для розмірів не кратних 32.
А я продовжу написання шифрувалки для DropBox)