Приветствую, %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 байт и для каждого блока:
- 1.Ксорим его с tweak value
- 2.Шифруем/расшифровываем его ключом k1
- 3.Опять ксорим уже (рас)шифрованный блок данных с tweak value. Сохраняем его, это и будет нужный нам (за/рас)шифрованный блок сектора
- 4.Умножаем 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]; // буфер для (за/рас)шифрованного блока данных
- // конвертируем номер сектора в массив байт
- Array.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();
- engine.Init(encrypt, new KeyParameter(k));
- engine.ProcessBlock(T, 0, T, 0);
- }
- }
______________________
Использовать AES, как видно, не обязательно. Правда, об этом ничего не сказано в стандарте.
Домашним заданием будет реализовать обработку сектора для размеров не кратных 32.
А я продолжу написание шифровалки для DropBox)