Since first posting this answer nearly 3 years ago, I am older and wiser so I decided to review my snippet. At first, I thought that strpos()
on @
would get me the length of the "local" part of the address and I could use substr_replace()
to simply inject the asterisks BUT a valid email address can have multiple @
in the local part AND multibyte support is a necessary inclusion for any real-world project. This means that mb_strpos()
would be an adequate replacement for strpos()
, but there isn't yet a native mb_substr_replace()
function in php, so the convolution of devising a non-regex snippet became increasingly unattractive.
If you want to see my original answer, you can check the edit history, but I no longer endorse its use. My original answer also detailed how other answers on this page fail to obfuscate email addresses which have 1 or 2 characters in the local substring. If you are considering using any other answers, but sure to test against a@example.com
and ab@example.com
as preliminary unit tests.
My snippet to follow DOES NOT validate an email address; it is assumed that your project will use appropriate methods to validate the address before bothering to allow it into your system. The power/utility of this snippet is that it is multibyte-safe and it will add asterisks in all scenarios and when there is only a single character in the local part, the leading character is repeated before the @
so that the mutated address is harder to guess. Oh, and the number of asterisks to be added is declared as a variable for simpler maintenance.
Code: (Demo) (Regex Demo)
$minFill = 4;
echo preg_replace_callback(
'/^(.)(.*?)([^@]?)(?=@[^@]+$)/u',
function ($m) use ($minFill) {
return $m[1]
. str_repeat("*", max($minFill, mb_strlen($m[2], 'UTF-8')))
. ($m[3] ?: $m[1]);
},
$email
);
Input/Output:
'a@example.com' => 'a****a@example.com',
'ab@example.com' => 'a****b@example.com',
'abc@example.com' => 'a****c@example.com',
'abcd@example.com' => 'a****d@example.com',
'abcde@example.com' => 'a****e@example.com',
'abcdef@example.com' => 'a****f@example.com',
'abcdefg@example.com' => 'a*****g@example.com',
'Ф@example.com' => 'Ф****Ф@example.com',
'Ф?@example.com' => 'Ф****?@example.com',
'Ф?Д@example.com' => 'Ф****Д@example.com',
'Ф?Д????@example.com' => 'Ф*****?@example.com',
'"a@tricky@one"@example.com' => '"************"@example.com',
Regex-planation:
/ #pattern delimiter
^ #start of string
(.) #capture group #1 containing the first character
(.*?) #capture group #2 containing zero or more characters (lazy, aka non-greedy)
([^@]?) #capture group #3 containing an optional single non-@ character
(?=@[^@]+$) #require that the next character is @ then one or more @ until the end of the string
/ #pattern delimiter
u #unicode/multibyte pattern modifier
Callback explanation:
$m[1]
the first character (capture group #1)
str_repeat("*", max($minFill, mb_strlen($m[2], 'UTF-8')))
measure the multibyte length of capture group #2 using UTF-8
encoding, then use the higher value between that calculated length and the declared $minFill
, then repeat the character *
the number of times returned from the max()
call.
($m[3] ?: $m[1])
the last character before the @
(capture group #3); if the element is empty in the $m
array, then use the first element's value -- it will always be populated.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…