Setup
Given the following:
$s = Crypt::encryptString('a');
Is is possible to know, for a string of length 1, the possible range of lengths of $s
?
Context
Database storage - need to store an encrypted value, and would like to set validation of the input string so the longest length input string, when encrypted, is inserted into the db without truncation.
Basic tests
Running some very crude tests locally, using the following snippet:
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>';
});
I can see the following, but it varies between runs, for example, a string of 'a' will be of length of either 188 or 192 (longer values seem to be between 244 and 248).
So there must be a formula. I have seen output_size = input_size + (16 - (input_size % 16))
but doesn't account for the variance.
Output
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
Edit
Ok, so after chatting with @Luke Joshua Park below, the variance in length comes from the laravel encryption function and the way $iv
is created, which is random bytes, which can contain /
.
$value
inside the encryption method can also contain a /
.
When values that contain a /
are JSON encoded, the /
is escaped to \\\/
adding an additional 3 characters per occurrence.
The real problem - can $iv
and $value
contain more than a single '/'?
Note I'm going to award the bounty to @Luke Joshua Park as he got me closest to what ended up being the (closest thing to a) solution, which is to follow.
(Not a) solution
The answer is, there is no concrete answer, not without unknowns and variance. Across the three people looking at this at the time of writing (myself, Luke, and bartonjs) there was still some doubt to a 100% accurate solution.
The question was posed to figure out a reliable type and size to store encrypted data, ideally in a database independent fashion (I didn't want to specify a particular database, as I wanted to know and understand how to calculate a length regardless of the way it was persisted).
However, even strings of the smallest lengths turned out to be quite long in the worst case scenario (where a random $iv was created containing many slashes - unlikely or not, it was possible). Possible encrypted strings of
n=1
possibly being 400 bytes long mean that avarchar
will never be the right answer.So... what should be done?
So, instead, it seems best, most consistent and most reliable to store encrypted data as a text field and not a varchar (in mysql land), regardless of the length of the original string. This is a disappointingly boring answer with no fancy maths involved. It's not the answer I would like to accept, but makes the most sense.
But, what about passwords?
In a brief moment of stupidity, I thought, but what about the password field? That is a
varchar
. But of course that is a hashed value, not an encrypted value (I hadn't had enough coffee when that thought popped into my head, ok?)