Как рассчитать максимальную длину вывода метода шифрования Laravel?
Настроить
Учитывая следующее:
$s = Crypt::encryptString('a');
Можно ли для строки длины 1 знать возможный диапазон длин $s
?
контекст
Хранилище базы данных - необходимо хранить зашифрованное значение и хотеть установить проверку входной строки, чтобы входная строка самой длинной длины при шифровании вставлялась в БД без усечения.
Основные тесты
Локально запустив некоторые очень грубые тесты, используя следующий фрагмент:
Route::get('/test', function() {
echo '<table>';
for ($i=0; $i < 100; $i++) {
$s = str_repeat('a', $i);
$l1 = strlen($s);
$l2 = strlen(Crypt::encryptString($s));
echo "<tr><td>$l1</td><td>$l2</td></tr>";
}
echo '</table>';
});
Я вижу следующее, но оно варьируется между прогонами, например, строка 'a' будет иметь длину либо 188, либо 192 (более длинные значения кажутся между 244 и 248).
Так что должна быть формула. я видел output_size = input_size + (16 - (input_size % 16))
но не учитывает дисперсию.
Выход
0 192
1 188
2 188
3 192
4 188
5 188
6 188
7 192
8 192
9 188
10 188
11 192
12 192
13 192
14 192
15 192
16 220
17 220
18 216
19 216
20 220
редактировать
Итак, после чата с @Luke Joshua Park ниже, разница в длине зависит от функции шифрования Laravel и способа $iv
создается, который является случайным байтом, который может содержать /
,
$value
внутри метода шифрования также может содержать /
,
Когда значения, которые содержат /
в кодировке JSON, /
сбежал в \\\/
добавив дополнительные 3 символа в каждом случае.
Настоящая проблема - может $iv
а также $value
содержать более одного '/'?
3 ответа
Заметьте, я собираюсь присудить награду @Luke Joshua Park, так как он дал мне самое близкое к тому, что в итоге оказалось (самым близким к) решением, которому нужно следовать.
(Не а) решение
Ответ таков: конкретного ответа не существует, не обошлось без неизвестности и дисперсии. Три человека, которые смотрели на это во время написания статьи (я, Люк и Бартонж), все еще сомневались в 100% точном решении.
Был задан вопрос, чтобы выяснить надежный тип и размер для хранения зашифрованных данных, в идеале независимым от базы данных (я не хотел указывать конкретную базу данных, так как хотел знать и понимать, как рассчитать длину независимо от как это было сохранено).
Однако даже строки наименьшей длины оказались довольно длинными в худшем случае (где был создан случайный $iv, содержащий много слэшей - вряд ли это было или нет, это было возможно). Возможные зашифрованные строки n=1
возможно, длина 400 байт означает, что varchar
никогда не будет правильным ответом.
Так что должно быть сделано?
Таким образом, вместо этого кажется лучшим, наиболее последовательным и наиболее надежным хранить зашифрованные данные в виде текстового поля, а не varchar (на земле mysql), независимо от длины исходной строки. Это удручающе скучный ответ без какой-либо сложной математики. Это не тот ответ, который я хотел бы принять, но он имеет смысл.
Но как насчет паролей?
В недолгую глупость подумал я, а как же поле пароля? Это varchar
, Но, конечно, это хешированное значение, а не зашифрованное значение (мне не хватило кофе, когда эта мысль пришла мне в голову, хорошо?)
Реальная проблема - могут ли $iv и $value содержать более одного символа '/'?
Конечно. Ваш худший случай для IV это IV FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
(hex), значение Base64 которого равно /////////////////////w==
,
21 косая черта * дополнительные 3 байта каждый = 63 дополнительных байта.
Для HMAC-SHA-2-256 вы можете получить 32 байта 0xFF (наихудший случай), что //////////////////////////////////////////8=
в base64.
42 косых черты => 126 дополнительных байтов.
Для зашифрованного текста, опять же, весь вывод может быть (но, скорее всего, нет) FF FF ... FF
, Все однобуквенные вводы (независимо от того, какая кодировка) представляют собой один блок зашифрованного текста, что делает вывод /////////////////////w==
снова (+63).
Обобщенная формула для максимума представляется
- IV: 24 + 63 = 87
- HMAC: 24 + 63 = 87
- Названия свойств JSON: 10
- Структура JSON: 19
- зашифрованный:
ceil(ceil((n+1) / 16) * 16 / 3) * 4 * 4
(Я использовалn
в байтах. дополненный зашифрованный текст - ceil((n+1) / blockize) * размер блока, base64 - 4 * ceil(data / 3), дополнительный *4 - "все слэши") - Base64 это все снова: 4 * ceil(sum / 3)
знак равно 4 * ceil((4 * 4 * ceil(16 * ceil((n + 1) / 16) / 3) + 203) / 3)
За n=1
это производит 400 байтов. Фактический максимум - (я думаю) 388, потому что формула зашифрованного текста считает 24 косых черты как наихудший случай, а 21 - худший. Таким образом, истинный супремум должен называть зашифрованный текст чем-то более сложным, включающим пол, потолок и вычитание.
Просматривая исходный код для Crypt::encryptString
мы видим, что конечным результатом будет объект JSON в кодировке base64, имеющий следующую структуру:
{ "iv": "<128 bits in base64>", "value": "<x bits in base64>", "mac": "<256 bits in hex>" }
Где значение x
является ceil(n / 128) * 128
где n
это количество бит в исходном текстовом формате.
Это означает, что для входного открытого текста длиной 1 размер вывода должен быть:
- 24 символа для IV (base64).
- 24 символа для зашифрованного текста (base64).
- 64 символа для SHA256 mac (hex).
- 10 символов для имен полей JSON.
- 19 символов дополнительных символов JSON, например
{
,"
,:
, - Последний раунд кодирования base64 всего этого... (
ceil(141 / 3) * 4
)
Дает в общей сложности 188. Колебания до 192 странные - ваши входные данные вообще не меняются по размеру (поскольку открытый текст всегда должен быть 16 байтов в диапазоне от 0 до 15).