32

I am building a simple friend/buddy system, and when someone tries to search for new friends, I want to show partially hidden email addresses, so as to give an idea about who the user might be, without revealing the actual details.

So I want abcdlkjlkjk@hotmail.com to become abcdl******@hotmail.com.

As a test I wrote:

<?php
$email = "abcdlkjlkjk@hotmail.com";

$em = explode("@",$email);
$name = $em[0];
$len = strlen($name);
$showLen = floor($len/2);
$str_arr = str_split($name);
for($ii=$showLen;$ii<$len;$ii++){
    $str_arr[$ii] = '*';
}
$em[0] = implode('',$str_arr); 
$new_name = implode('@',$em);
echo $new_name;

This works, but I was wondering if there was any easier/shorter way of applying the same logic? Like a regex maybe?

Bluemagica
  • 5,000
  • 12
  • 47
  • 73
  • 1
    But what if the user's actual email address is just `abc@example.com`? You've then revealed the whole thing. An auto-suggest with usernames is probably fine, but an auto-suggest on email addresses seems like a privacy nightmare. – Michael Berkowski Dec 12 '13 at 13:52
  • 1
    @MichaelBerkowski That's why I am using `$showLen = floor($len/2)`, so that about half of the email would be hidden always, no matter how small. – Bluemagica Dec 12 '13 at 13:57
  • **Nowadays E-Mail addresses are allowed to contain Unicode** (like Chinese), which makes all these ASCII answers invalid, see: [Hint or partially hide email address with stars (\*) in PHP](https://stackoverflow.com/a/43904214/8740349) – Top-Master Jun 05 '22 at 02:43

20 Answers20

68

here's something quick:

function obfuscate_email($email)
{
    $em   = explode("@",$email);
    $name = implode('@', array_slice($em, 0, count($em)-1));
    $len  = floor(strlen($name)/2);

    return substr($name,0, $len) . str_repeat('*', $len) . "@" . end($em);   
}

// to see in action:
$emails = ['"Abc\@def"@iana.org', 'abcdlkjlkjk@hotmail.com'];

foreach ($emails as $email) 
{
    echo obfuscate_email($email) . "\n";
}

echoes:

"Abc\*****@iana.org
abcdl*****@hotmail.com

uses substr() and str_repeat()

kks21199
  • 1,116
  • 2
  • 10
  • 29
msturdy
  • 10,479
  • 11
  • 41
  • 52
  • Thanks, that's what I was looking for, though you really need `floor($len/2)`, cause without it, if you use odd number of characters, you would get 1 less asterisk than the actual number of character. – Bluemagica Dec 12 '13 at 14:10
  • This works great, although I would add that to get the number of asterisks correct, you'd need to allow for two different `$len` values. Calculate `$len` as above (including `floor`), then calculate `$len2 = strlen($name) - $len`. Finally, return the string as `return substr($name,0, $len) . str_repeat('*', $len2) . "@" . end($em)` – Dr Marble Jan 22 '15 at 00:05
  • It doesn't work for email like `1@xyz.com`, it returns only `@xyz.com`. Ideally it should return `*@xyz.com` – Ankit Sharma Sep 05 '15 at 18:15
  • When email is like: v@gmail.com i made little fix to output: \*@gmail.com fixed: function obfuscate_email($email) { $em = explode("@",$email); $name = implode(array_slice($em, 0, count($em)-1), '@'); $len = floor(strlen($name)/2); return substr($name,0, $len) . str_repeat('*', ($len > 1 ? $len : 1)) . "@" . end($em); } – liutis Mar 08 '20 at 11:40
  • @liutis Your implode parameters are inverted, the separator goes first and the array second. – kissumisha Apr 29 '22 at 02:39
26

Maybe this is not what you want, but I would go for this:

<?php

    /*

    Here's the logic:

    We want to show X numbers.
    If length of STR is less than X, hide all.
    Else replace the rest with *.

    */

function mask($str, $first, $last) {
    $len = strlen($str);
    $toShow = $first + $last;
    return substr($str, 0, $len <= $toShow ? 0 : $first).str_repeat("*", $len - ($len <= $toShow ? 0 : $toShow)).substr($str, $len - $last, $len <= $toShow ? 0 : $last);
}

function mask_email($email) {
    $mail_parts = explode("@", $email);
    $domain_parts = explode('.', $mail_parts[1]);

    $mail_parts[0] = mask($mail_parts[0], 2, 1); // show first 2 letters and last 1 letter
    $domain_parts[0] = mask($domain_parts[0], 2, 1); // same here
    $mail_parts[1] = implode('.', $domain_parts);

    return implode("@", $mail_parts);
}

$emails = array(
    'a@a.com',
    'ab@aa.com',
    'abc@aaa.com',
    'abcd@aaaa.com',
    'abcde@aaaaa.com',
    'abcdef@aaaaaa.com',
    'abcdefg@aaaaaaa.com',
    'abcdefgh@aaaaaaaa.com',
    'abcdefghi@aaaaaaaaa.com'
);

foreach ($emails as $email){
    echo '<b>'.$email.'</b><br>'.mask_email($email).'<br><hr>';
}

Result:

a@a.com
*@*.com

ab@aa.com
**@**.com

abc@aaa.com
***@***.com

abcd@aaaa.com
ab*d@aa*a.com

abcde@aaaaa.com
ab**e@aa**a.com

abcdef@aaaaaa.com
ab***f@aa***a.com

abcdefg@aaaaaaa.com
ab****g@aa****a.com

abcdefgh@aaaaaaaa.com
ab*****h@aa*****a.com

abcdefghi@aaaaaaaaa.com
ab******i@aa******a.com
Szymon Marczak
  • 1,055
  • 1
  • 16
  • 31
13

Here's my alternate solution for this.

I wouldn't use the exact number of mask characters to match the original length of the email, but rather use a fixed length mask for privacy reasons. I would also set the maximum allowed characters to show as well as never show more than half of the email. I would also mask all emails less than a minimum length.

With those rules in mind, here's my function with optional parameters:

function maskEmail($email, $minLength = 3, $maxLength = 10, $mask = "***") {
    $atPos = strrpos($email, "@");
    $name = substr($email, 0, $atPos);
    $len = strlen($name);
    $domain = substr($email, $atPos);

    if (($len / 2) < $maxLength) $maxLength = ($len / 2);

    $shortenedEmail = (($len > $minLength) ? substr($name, 0, $maxLength) : "");
    return  "{$shortenedEmail}{$mask}{$domain}";
}

Tests:

$email = "";
$tests = [];
for ($i=0; $i < 22; $i++) {
    $email .= chr(97 + $i);

    $tests[] = $email . " -> " . maskEmail("{$email}@example.com");
}
print_r($tests);

Results:

Array
(
    [0] => a -> ***@example.com
    [1] => ab -> ***@example.com
    [2] => abc -> ***@example.com
    [3] => abcd -> ab***@example.com
    [4] => abcde -> ab***@example.com
    [5] => abcdef -> abc***@example.com
    [6] => abcdefg -> abc***@example.com
    [7] => abcdefgh -> abcd***@example.com
    [8] => abcdefghi -> abcd***@example.com
    [9] => abcdefghij -> abcde***@example.com
    [10] => abcdefghijk -> abcde***@example.com
    [11] => abcdefghijkl -> abcdef***@example.com
    [12] => abcdefghijklm -> abcdef***@example.com
    [13] => abcdefghijklmn -> abcdefg***@example.com
    [14] => abcdefghijklmno -> abcdefg***@example.com
    [15] => abcdefghijklmnop -> abcdefgh***@example.com
    [16] => abcdefghijklmnopq -> abcdefgh***@example.com
    [17] => abcdefghijklmnopqr -> abcdefghi***@example.com
    [18] => abcdefghijklmnopqrs -> abcdefghi***@example.com
    [19] => abcdefghijklmnopqrst -> abcdefghij***@example.com
    [20] => abcdefghijklmnopqrstu -> abcdefghij***@example.com
    [21] => abcdefghijklmnopqrstuv -> abcdefghij***@example.com
)
Hein Andre Grønnestad
  • 6,885
  • 2
  • 31
  • 43
6

For instance :

substr($email, 0, 3).'****'.substr($email, strpos($email, "@"));

Which will give you something like:

abc****@hotmail.com

Igoooor
  • 397
  • 2
  • 9
  • Im a little confused as to why this got -2. I think this is why in order to downvote, the downvoter should have to leave a comment. As far as I can tell, this does exactly what the OP asked. – Jdahern Jun 02 '15 at 21:53
  • Guess what "me@trash.com" will be masked into. – garkin Aug 16 '17 at 10:43
  • don't allow username with 2 characters @garkin its highly unlikely to see username with 2 letters. – Danish Jan 03 '19 at 12:21
4

I'm using this:

 function secret_mail($email)
{

$prop=2;
    $domain = substr(strrchr($email, "@"), 1);
    $mailname=str_replace($domain,'',$email);
    $name_l=strlen($mailname);
    $domain_l=strlen($domain);
        for($i=0;$i<=$name_l/$prop-1;$i++)
        {
        $start.='x';
        }

        for($i=0;$i<=$domain_l/$prop-1;$i++)
        {
        $end.='x';
        }

    return substr_replace($mailname, $start, 2, $name_l/$prop).substr_replace($domain, $end, 2, $domain_l/$prop);
}

Will output something like: cyxxxxxone@gmxxxxcom

niki
  • 41
  • 1
  • 4
4

I created a function can help someone

    function hideEmail($email)
{
    $mail_parts = explode("@", $email);
    $length = strlen($mail_parts[0]);
    $show = floor($length/2);
    $hide = $length - $show;
    $replace = str_repeat("*", $hide);

    return substr_replace ( $mail_parts[0] , $replace , $show, $hide ) . "@" . substr_replace($mail_parts[1], "**", 0, 2);
}

hideEmail("name@example.com"); // output: na**@**ample.com
hideEmail("something@example.com"); // output: some*****@**ample.com

You can customize as you want .. something like this (if length is 4 or less display only the first)

    function hideEmail($email) {
    $mail_parts = explode("@", $email);
    $length = strlen($mail_parts[0]);

    if($length <= 4 & $length > 1)
    {
        $show = 1;
        }else{
        $show = floor($length/2);       
    }

    $hide = $length - $show;
    $replace = str_repeat("*", $hide);

    return substr_replace ( $mail_parts[0] , $replace , $show, $hide ) . "@" . substr_replace($mail_parts[1], "**", 0, 2);  
}

hideEmail("name@example.com"); // output: n***@**ample.com
hideEmail("something@example.com"); // output: some*****@**ample.com
Hani
  • 41
  • 3
4

Very simple RegExp way:

$email = preg_replace('/\B[^@.]/', '*', $email)

Results:

john@smith.com: j***@s*****.c**

abcdef@example.org: a*****@e******.o**

abcdef: a*****

Riki137
  • 2,076
  • 2
  • 23
  • 26
2

Sometimes its good to show the last character too.

ABCDEFZ@gmail.com becomes A*****Z@gmail.com

I will suggest you keep things simple. Maybe something like this is simple enough https://github.com/fedmich/PHP_Codes/blob/master/mask_email.php

Masks an email to show first 3 characters and then the last character before the @ sign

function mask_email( $email ) {
    /*
    Author: Fed
    Simple way of masking emails
    */

    $char_shown = 3;

    $mail_parts = explode("@", $email);
    $username = $mail_parts[0];
    $len = strlen( $username );

    if( $len <= $char_shown ){
        return implode("@", $mail_parts );  
    }

    //Logic: show asterisk in middle, but also show the last character before @
    $mail_parts[0] = substr( $username, 0 , $char_shown )
        . str_repeat("*", $len - $char_shown - 1 )
        . substr( $username, $len - $char_shown + 2 , 1  )
        ;

    return implode("@", $mail_parts );
}
fedmich
  • 5,343
  • 3
  • 37
  • 52
  • as you want the last char only, for the last part, `substr($username, -1)` should do the work. innit? – ssi-anik Jul 28 '21 at 13:26
2

I m using femich answer above and tweak it a bit for my

function mask_email($email, $char_shown_front = 1, $char_shown_back = 1)
{
    $mail_parts = explode('@', $email);
    $username = $mail_parts[0];
    $len = strlen($username);

    if ($len < $char_shown_front or $len < $char_shown_back) {
        return implode('@', $mail_parts);
    }

    //Logic: show asterisk in middle, but also show the last character before @
    $mail_parts[0] = substr($username, 0, $char_shown_front)
        . str_repeat('*', $len - $char_shown_front - $char_shown_back)
        . substr($username, $len - $char_shown_back, $char_shown_back);

    return implode('@', $mail_parts);
}

test123@gmail.com -> t*****3@gmail.com

you can pass in the number of character to show in the front and in the back

Michael Nguyen
  • 1,691
  • 2
  • 18
  • 33
  • this is the most useful answer and should be the accepted one as well +1 helped me a LOT – Fipsi Jul 26 '19 at 08:20
1

You can also try this....

<?php 
$email = "abcdlkjlkjk@hotmail.com";
$resultmob = substr($email,0,5);
$resultmob .= "**********";
$resultmob .= substr($email,strpos($email, "@"));
echo $resultmob;
?>

Answer:-

abcdl******@hotmail.com
1

Another variant that was heavily influenced by the answers already shared.

This has two key extra benefits:

  • It keeps the first characters after a defined set of delimiters, making it more readable while still preserving privacy.
  • It works with longer domain endings such as .org.uk and .com.au

Example: firstname.lastname@example.co.uk becomes f********.l*******@e*****.c*.u*

function mask_email( $email ) {
    
    $masked = '';

    $show_next = true;

    foreach ( str_split( $email ) as $chr ) {

      if ( $show_next ) {
        $masked .= $chr;
        $show_next = false;
      }
      else if ( in_array( $chr, array('.', '@', '+') ) ) {
        $masked .= $chr;
        $show_next = true;
      }
      else {
        $masked .= '*';
        $show_next = false;
      }

    }

    return $masked;
  }
0

Try this function. This will work with valid emails, such as "Abc\@def"@iana.org.

function hideEmail($email){
    $prefix = substr($email, 0, strrpos($email, '@'));
    $suffix = substr($email, strripos($email, '@'));
    $len  = floor(strlen($prefix)/2);

    return substr($prefix, 0, $len) . str_repeat('*', $len) . $suffix;
}

echo hideEmail('abcdljtrsjtrsjlkjk@hotmail.com');
echo hideEmail('"abc\@def"@iana.org');

Returns

abcdljtrs*********@hotmail.com
"abc\*****@iana.org
Ben Fortune
  • 31,623
  • 10
  • 79
  • 80
0

I have a function

function hide_email($email){
$final_str = '';
$string = explode('@', $email);
$leftlength = strlen($string[0]);
$string2 = explode('.', $string[1]);
$string2len = strlen($string2[0]);
$leftlength_new = $leftlength-1;
$first_letter = substr($string[0], 0,1);
$stars = '';
$stars2 = '';
for ($i=0; $i < $leftlength_new; $i++) { 
    $stars .= '*';
}
for ($i=0; $i < $string2len; $i++) { 
    $stars2 .= '*';
}
$stars;
return $final_str .= $first_letter.$stars.'@'.$stars2.'.'.$string2[1];

}

echo hide_email('Hello@PHP.com');

Daud khan
  • 2,413
  • 2
  • 14
  • 18
  • This code replaces characters with stars after first char. Also, replace mail domain with stars. Example:Hello@PHP.com become H****@***.com – Daud khan Jan 31 '17 at 09:53
0

There was an issue in case if there would be 1 character before @. I have fixed in below function.

function obfuscate_email($email)

{  

   $em   = explode("@",$email);
   if(strlen($em[0])==1){
       return   '*'.'@'.$em[1];
   }
   $name = implode(array_slice($em, 0, count($em)-1), '@');
   $len  = floor(strlen($name)/2);
   return substr($name,0, $len) . str_repeat('*', $len) . "@" . end($em);

}
Waqas
  • 164
  • 3
  • 12
0

Though this is an old thread & has many answers already. I want to share my own snippet too.

Which checks if it's a valid email or not. How much characters to censor & to show. What character should be used to censor.

function get_censored_email($email, $show_chars = 3, $censor_char = '*'){
    if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
    
        $char_length = strlen($email);
        $censor_count = $char_length - $show_chars;
    
        $return_email = substr($email, 0, $show_chars);
        $return_email .= str_repeat("*", $censor_count);
    
        return $return_email;
    }
}
$email = 'noman.ibrahim115@gmail.com';
echo get_censored_email($email, 3, '*'); // returns nom***********************
Fenil Shah
  • 149
  • 1
  • 10
Muhammad Noman
  • 136
  • 1
  • 6
  • The `$censor_char` variable is not used, you should probably put it where the "*" is in your code. – dmnkhhn Mar 15 '22 at 11:25
0

Here is version with only 2 lines (if you remove function stuff).

<?php
function censor_email($str,$amount=2, $char='*') {
    list($local, $domain)=explode("@",$str);
    return substr($local,0,$amount).str_repeat($char,strlen($local)-$amount)."@".$domain;
}
?>
MrDarkHooD
  • 11
  • 4
0
function maskEmail($email) {
    preg_match('/^.?(.*)?.@.+$/', $email, $matches);
    return str_replace($matches[1], str_repeat('*', strlen($matches[1])), $email);
}

echo maskEmail('abcdefgh@example.com')
echo maskEmail('abh@example.com')
echo maskEmail('ah@example.com')
echo maskEmail('a@example.com')

returns

a******h@example.com
a*h@example.com
ah@example.com
a@example.com
  • An elegant solution that would benefit from the addition of a little bit of explanation walking through how the regex works. The examples do a nice job of showing the limitations of this design. – Jason Aller May 26 '20 at 14:17
0

This is what I did, as I required exact number of string count same as plain email.

This function only shows first & last two characters before "@"

function mask_email($email)
{
    $em   = explode("@",$email);
    $len  = strlen($em[0]);
    $substr_count = 1;
    if($len > 6)
        $substr_count = 2;
    
    $first = substr($em[0], 0,$substr_count);
    $last = substr($em[0], -$substr_count);
    $no_of_star = $len - ($substr_count * 2);
    
    return $first.str_repeat('*', $no_of_star).$last."@".end($em);   
}
Fenil Shah
  • 149
  • 1
  • 10
0

Method 1:

<?php
  
function hideEmailAddress($email) {
    if(filter_var($email, FILTER_VALIDATE_EMAIL)) {
        list($first, $last) = explode('@', $email);

        $first = str_replace(substr($first, '3'), str_repeat('*', strlen($first)-3), $first);

        $last = explode('.', $last);

        $last_domain = str_replace(substr($last['0'], '1'), str_repeat('*', strlen($last['0'])-1), $last['0']);
        
        $hideEmailAddress = $first.'@'.$last_domain.'.'.$last['1'];
        return $hideEmailAddress;
    }
}
   
$email = "test@example.com";
   
echo hideEmailAddress($email);
?>

Method 2:

<?php
  
function hideEmailAddress($email) {
    $em   = explode("@",$email);
    $name = implode(array_slice($em, 0, count($em)-1), '@');
    $len  = floor(strlen($name)/2);
    return substr($name,0, $len) . str_repeat('*', $len) . "@" . end($em);   
}
  
$email = 'test@example.com';
  
echo hideEmailAddress($email);
   
?>
Ankit Jindal
  • 3,672
  • 3
  • 25
  • 37
0

Yet another answer based from @user11860704

function mask_email( $email ) 
{    
    $masked = '';
    $show_next = true;  
    foreach ( str_split( $email ) as $chr ) 
    {
      if ( $show_next ) 
      {
        $masked .= $chr;
        $show_next = false;
      }
      else if ( in_array( $chr, array('.', '@', '+') ) ) 
      {
        $masked .= $chr;
        $show_next = true;
        $run_once = 0;
      }
      else 
      {
        if($run_once ==0)
        {
            $masked .= '*';
        }
        $run_once = 1;
        $show_next = false;
      }
    }
    return $masked;
}

Mine differs in that you'll get email returns like the following:

s*@g*.c*
s*.m*@a*.b*.c*.d*
s*.m*@h*.c*

It stops you from guessing the email name lengths even more.

vr_driver
  • 1,867
  • 28
  • 39