Andreas Weygandt
Andreas Weygandt

Reputation: 113

Anonymize IPv4 and IPv6 addresses with PHP preg_replace?

I needed to anonymize IPv4 and IPv6 addresses so I coded this crude solution:

if (strlen($_SERVER['REMOTE_ADDR']) <= 15) {  // Sorry: 15 NOT 12
    echo htmlentities(substr_replace($_SERVER['REMOTE_ADDR'], 'XXX', -3), ENT_QUOTES);
} else {
    echo htmlentities(substr_replace($_SERVER['REMOTE_ADDR'], 'XXXX:XXXX', -9), ENT_QUOTES);
}

It works fine with full length IPv4 and IPv6 addresses like

207.142.131.005

2001:0db8:0000:08d3:0000:8a2e:0070:7344

but not with abbreviated addresses like

207.142.131.5

2001:0db8::8d3::8a2e:7:7344

I wonder if there is an elegant solution with preg_replace and some regular expression magic?

Upvotes: 3

Views: 3034

Answers (4)

mickmackusa
mickmackusa

Reputation: 48041

No conditional is necessary. You can write two patterns and two replacements for a single preg_replace() call to process.

Target the optional numbers after the last literal dot in the string for replacement. Then target the alphanumeric colon-delimited substrings at the end of the string.

Code: (Demo)

$tests = [
    "207.142.131.005",
    "2001:0db8:0000:08d3:0000:8a2e:0070:7344",
    "2001:0db8:0000:08d3:0000:8a2e:0070:734a",
    "207.142.131.5",
    "2001:0db8::8d3::8a2e:7:7344",
    "::1",
    "127.0.0.1"
];

$tests = preg_replace(
             ['/\.\d*$/', '/[\da-f]*:[\da-f]*$/'],
             ['.XXX', 'XXXX:XXXX'],
             $tests
         );

var_export($tests);

Output:

array (
  0 => '207.142.131.XXX',
  1 => '2001:0db8:0000:08d3:0000:8a2e:XXXX:XXXX',
  2 => '2001:0db8:0000:08d3:0000:8a2e:XXXX:XXXX',
  3 => '207.142.131.XXX',
  4 => '2001:0db8::8d3::8a2e:XXXX:XXXX',
  5 => ':XXXX:XXXX',
  6 => '127.0.0.XXX',
)

Pattern Explanations:

IPv4:

/         #Pattern delimiter
\.        #Match dot literally
\d*       #Match zero or more digits
$         #Match the end of the string
/         #Pattern delimiter

IPv6

/         #Pattern delimiter
[\da-f]*  #Match zero or more (digits or a b c d e f)
:         #Match colon
[\da-f]*  #Match zero or more (digits or a b c d e f)
$         #Match the end of the string
/         #Pattern delimiter

Upvotes: 10

Srdjan M.
Srdjan M.

Reputation: 3405

Regex: [0-9]+$ and [0-9]*:[0-9]+$ or \d+$ and \d*:\d+$

Details:

  • $ Asserts position at the end of a line
  • [] Match a single character present in the list
  • + Matches between one and unlimited times
  • * Matches between zero and unlimited times

PHP code:

function mask($ip)
{
  if (strpos($ip, ".") == true) {
    print_r(preg_replace('~[0-9]+$~', 'XXX', $ip) . "\n");
  } else {
    print_r(preg_replace('~[0-9]*:[0-9]+$~', 'XXXX:XXXX', $ip) . "\n");
  }
}

Output:

207.142.131.XXX
207.142.131.XXX
2001:0db8:0000:08d3:0000:8a2e:XXXX:XXXX
2001:0db8::8d3::8a2e:XXXX:XXXX
:XXXX:XXXX

Code demo

Upvotes: 1

mrzasa
mrzasa

Reputation: 23347

You can try:

$string = $_SERVER['REMOTE_ADDR'];
if (strlen($string) <= 12) {
    echo htmlentities(preg_replace('/(\d+\.\d+.\d+.)(\d+)/', "$1XXX",  $string), ENT_QUOTES);
} else {
    echo htmlentities(preg_replace('/((?:[0-9a-f]+:){6})(\w+:\w+)/', "$1XXXX:XXXX", $string), ENT_QUOTES);
}

Upvotes: 0

ishegg
ishegg

Reputation: 9957

Without using regular expressions, you can explode by . or : accordingly and replace the last two pieces:

<?php
function anonIp($ip)
{
    if (strpos($ip, ".") !== false) { // detect IP type by dots instead of length
        $pieces = explode(".", $ip);
        $nPieces = count($pieces);
        $pieces[$nPieces - 1] = $pieces[$nPieces - 2] = "XXX";
        return implode(".", $pieces);
    } else {
        $pieces = explode(":", $ip);
        $nPieces = count($pieces);
        $pieces[$nPieces - 1] = $pieces[$nPieces - 2] = "XXXX";
        return implode(":", $pieces);
    }
}
var_dump(anonIp("207.142.131.005")); // 207.142.XXX.XXX
var_dump(anonIp("2001:0db8:0000:08d3:0000:8a2e:0070:7344")); // 2001:0db8:0000:08d3:0000:8a2e:XXXX:XXXX
var_dump(anonIp("207.142.131.5")); // 207.142.XXX.XXX
var_dump(anonIp("2001:0db8::8d3::8a2e:7:7344")); // 2001:0db8::8d3::8a2e:XXXX:XXXX
var_dump(anonIp("::1")); // :XXXX:XXXX
var_dump(anonIp("127.0.0.1")); // 127.0.XXX.XXX

Demo

Though I'm sure there's some obscure (or not so much) case where this will break, so be sure to give it a thorough test first.

Upvotes: 1

Related Questions