Hashing is one-way; when you must get the plaintext back, you encrypt. Two rules: never invent your own scheme, and always use authenticated encryption, which detects tampering as well as hiding data. The mcrypt extension older code reaches for was removed in PHP 7.2; use OpenSSL or libsodium. With OpenSSL, use AES-256-GCM, whose tag verifies integrity; generate a fresh IV (nonce) per message and never reuse one with a key. Libsodium is easier to use correctly and is the recommended default: its secretbox does authenticated symmetric encryption in one call, and its sealed box lets anyone encrypt to your public key without a shared secret, so only your private key opens the result.
$key = random_bytes(32); $iv = random_bytes(12); // 12-byte GCM nonce
$ct = openssl_encrypt('card ending 4242', 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag);
echo openssl_decrypt($ct, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag), "\n";
var_dump(openssl_decrypt($ct, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, strrev($tag)));
$m = 'card ending 4242'; $k = sodium_crypto_secretbox_keygen();
$n = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
echo sodium_crypto_secretbox_open(sodium_crypto_secretbox($m, $n, $k), $n, $k), "\n";
$kp = sodium_crypto_box_keypair(); // recipient's key pair
echo sodium_crypto_box_seal_open(sodium_crypto_box_seal($m,
sodium_crypto_box_publickey($kp)), $kp), "\n"; // only the private key opens itcard ending 4242 bool(false) card ending 4242 card ending 4242
The genuine GCM tag decrypted; a tampered tag made openssl_decrypt() return false instead of corrupt data. The hard part is the key: keep it out of source control, load it from an environment variable or a secrets manager (Subsection 4.17.17), and rotate it.